Refactor "parts" to contain MMS/PDU madness to MMS code paths.

Closes #4248
// FREEBIE
fork-5.53.8
Moxie Marlinspike 2015-10-12 18:25:05 -07:00
rodzic 84fa2d1a34
commit 09e52834a6
67 zmienionych plików z 2160 dodań i 2083 usunięć

Wyświetl plik

@ -94,7 +94,6 @@ import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.providers.CaptureProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -563,8 +562,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
.setType(GroupContext.Type.QUIT)
.build();
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(self, getRecipients(),
context, null);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(getRecipients(), context, null);
MessageSender.send(self, masterSecret, outgoingMessage, threadId, false);
DatabaseFactory.getGroupDatabase(self).remove(groupId, TextSecurePreferences.getLocalNumber(self));
initializeEnabledCheck();
@ -1246,30 +1244,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendMediaMessage(final boolean forceSms)
throws InvalidMessageException
{
final Context context = getApplicationContext();
SlideDeck slideDeck;
if (attachmentManager.isAttachmentPresent()) {
Slide mediaSlide = attachmentManager.getSlideDeck().getThumbnailSlide();
MediaConstraints constraints = getCurrentMediaConstraints();
if (mediaSlide != null &&
!constraints.isSatisfied(this, masterSecret, mediaSlide.getPart()) &&
!constraints.canResize(mediaSlide.getPart()))
{
Toast.makeText(context,
R.string.ConversationActivity_attachment_exceeds_size_limits,
Toast.LENGTH_SHORT).show();
return;
}
slideDeck = new SlideDeck(attachmentManager.getSlideDeck());
} else {
slideDeck = new SlideDeck();
}
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(this, recipients, slideDeck,
getMessage(), distributionType);
final Context context = getApplicationContext();
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(recipients,
attachmentManager.getSlideDeck(),
getMessage(),
System.currentTimeMillis(),
distributionType);
if (isSecureText && !forceSms) {
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);

Wyświetl plik

@ -47,7 +47,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@ -262,10 +262,10 @@ public class ConversationItem extends LinearLayout
setNotificationMmsAttributes((NotificationMmsMessageRecord) messageRecord);
} else if (hasMedia(messageRecord)) {
mediaThumbnail.setVisibility(View.VISIBLE);
mediaThumbnail.setImageResource(masterSecret, messageRecord.getId(),
messageRecord.getDateReceived(),
((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture());
mediaThumbnail.hideControls(messageRecord.isFailed() || (messageRecord.isOutgoing() && !messageRecord.isPending()));
mediaThumbnail.setImageResource(masterSecret,
((MediaMmsMessageRecord)messageRecord).getSlideDeckFuture(),
!messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending()),
false);
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
mediaThumbnail.setVisibility(View.GONE);
@ -401,7 +401,9 @@ public class ConversationItem extends LinearLayout
private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener {
@Override public void onClick(View v, final Slide slide) {
DatabaseFactory.getPartDatabase(context).setTransferState(messageRecord.getId(), slide.getPart().getPartId(), PartDatabase.TRANSFER_PROGRESS_STARTED);
DatabaseFactory.getAttachmentDatabase(context).setTransferState(messageRecord.getId(),
slide.asAttachment(),
AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
}
}
@ -410,7 +412,7 @@ public class ConversationItem extends LinearLayout
Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(PartAuthority.getPublicPartUri(slide.getUri()), slide.getContentType());
intent.setDataAndType(PartAuthority.getAttachmentPublicUri(slide.getUri()), slide.getContentType());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException anfe) {

Wyświetl plik

@ -27,6 +27,7 @@ import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
@ -34,7 +35,7 @@ import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase.Reader;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@ -51,8 +52,6 @@ import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import ws.com.google.android.mms.pdu.PduPart;
public class DatabaseUpgradeActivity extends BaseActivity {
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
@ -236,23 +235,23 @@ public class DatabaseUpgradeActivity extends BaseActivity {
}
private void schedulePendingIncomingParts(Context context) {
final PartDatabase partDb = DatabaseFactory.getPartDatabase(context);
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
final List<PduPart> pendingParts = DatabaseFactory.getPartDatabase(context).getPendingParts();
final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context);
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments();
Log.w(TAG, pendingParts.size() + " pending parts.");
for (PduPart part : pendingParts) {
final Reader reader = mmsDb.readerFor(masterSecret, mmsDb.getMessage(part.getMmsId()));
Log.w(TAG, pendingAttachments.size() + " pending parts.");
for (DatabaseAttachment attachment : pendingAttachments) {
final Reader reader = mmsDb.readerFor(masterSecret, mmsDb.getMessage(attachment.getMmsId()));
final MessageRecord record = reader.getNext();
if (part.getDataUri() != null) {
Log.w(TAG, "corrected a pending media part " + part.getPartId() + "that already had data.");
partDb.setTransferState(part.getMmsId(), part.getPartId(), PartDatabase.TRANSFER_PROGRESS_DONE);
if (attachment.hasData()) {
Log.w(TAG, "corrected a pending media part " + attachment.getAttachmentId() + "that already had data.");
attachmentDb.setTransferState(attachment.getMmsId(), attachment.getAttachmentId(), AttachmentDatabase.TRANSFER_PROGRESS_DONE);
} else if (record != null && !record.isOutgoing() && record.isPush()) {
Log.w(TAG, "queuing new attachment download job for incoming push part " + part.getPartId() + ".");
Log.w(TAG, "queuing new attachment download job for incoming push part " + attachment.getAttachmentId() + ".");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, part.getMmsId(), part.getPartId()));
.add(new AttachmentDownloadJob(context, attachment.getMmsId(), attachment.getAttachmentId()));
}
reader.close();
}

Wyświetl plik

@ -46,16 +46,20 @@ import com.bumptech.glide.request.target.SimpleTarget;
import com.google.protobuf.ByteString;
import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.NotInDirectoryException;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.RoundedCorners;
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
@ -80,6 +84,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
@ -469,8 +474,10 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
.addAllMembers(e164numbers)
.build();
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(this, groupRecipient, context, avatar);
long threadId = MessageSender.send(this, masterSecret, outgoingMessage, -1, false);
Uri avatarUri = SingleUseBlobProvider.getInstance().createUri(avatar);
Attachment avatarAttachment = new UriAttachment(avatarUri, ContentType.IMAGE_JPEG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length);
OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, context, avatarAttachment);
long threadId = MessageSender.send(this, masterSecret, outgoingMessage, -1, false);
return new Pair<>(threadId, groupRecipient);
}

Wyświetl plik

@ -31,14 +31,12 @@ import org.thoughtcrime.securesms.ImageMediaAdapter.ViewHolder;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.PartDatabase.ImageRecord;
import org.thoughtcrime.securesms.database.ImageDatabase.ImageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.MediaUtil;
import ws.com.google.android.mms.pdu.PduPart;
public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
private static final String TAG = ImageMediaAdapter.class.getSimpleName();
@ -67,43 +65,38 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter<ViewHolder> {
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final @NonNull Cursor cursor) {
final ThumbnailView imageView = viewHolder.imageView;
final ImageRecord imageRecord = ImageRecord.from(cursor);
final ImageRecord imageRecord = ImageRecord.from(cursor);
PduPart part = new PduPart();
Slide slide = MediaUtil.getSlideForAttachment(getContext(), imageRecord.getAttachment());
part.setDataUri(imageRecord.getUri());
part.setContentType(imageRecord.getContentType().getBytes());
part.setPartId(imageRecord.getPartId());
Slide slide = MediaUtil.getSlideForPart(getContext(), part, imageRecord.getContentType());
if (slide != null) {
imageView.setImageResource(slide, masterSecret);
imageView.setImageResource(masterSecret, slide, false, false);
}
imageView.setOnClickListener(new OnMediaClickListener(imageRecord));
}
private class OnMediaClickListener implements OnClickListener {
private ImageRecord record;
private final ImageRecord imageRecord;
private OnMediaClickListener(ImageRecord record) {
this.record = record;
private OnMediaClickListener(ImageRecord imageRecord) {
this.imageRecord = imageRecord;
}
@Override
public void onClick(View v) {
Intent intent = new Intent(getContext(), MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, record.getDate());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, imageRecord.getDate());
if (!TextUtils.isEmpty(record.getAddress())) {
if (!TextUtils.isEmpty(imageRecord.getAddress())) {
Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(),
record.getAddress(),
imageRecord.getAddress(),
true);
if (recipients != null && recipients.getPrimaryRecipient() != null) {
intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, recipients.getPrimaryRecipient().getRecipientId());
}
}
intent.setDataAndType(record.getUri(), record.getContentType());
intent.setDataAndType(imageRecord.getAttachment().getDataUri(), imageRecord.getContentType());
getContext().startActivity(intent);
}

Wyświetl plik

@ -175,7 +175,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity i
@Override
public Cursor getCursor() {
return DatabaseFactory.getPartDatabase(getContext()).getImagesForThread(threadId);
return DatabaseFactory.getImageDatabase(getContext()).getImagesForThread(threadId);
}
}
}

Wyświetl plik

@ -0,0 +1,88 @@
package org.thoughtcrime.securesms.attachments;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
public abstract class Attachment {
@NonNull
private final String contentType;
private final int transferState;
private final long size;
@Nullable
private final String location;
@Nullable
private final String key;
@Nullable
private final String relay;
// XXX - This shouldn't be here.
@Nullable
private Bitmap thumbnail;
public Attachment(@NonNull String contentType, int transferState, long size,
@Nullable String location, @Nullable String key, @Nullable String relay)
{
this.contentType = contentType;
this.transferState = transferState;
this.size = size;
this.location = location;
this.key = key;
this.relay = relay;
}
@Nullable
public abstract Uri getDataUri();
@Nullable
public abstract Uri getThumbnailUri();
public int getTransferState() {
return transferState;
}
public boolean isInProgress() {
return transferState != AttachmentDatabase.TRANSFER_PROGRESS_DONE &&
transferState != AttachmentDatabase.TRANSFER_PROGRESS_FAILED;
}
public long getSize() {
return size;
}
@NonNull
public String getContentType() {
return contentType;
}
@Nullable
public String getLocation() {
return location;
}
@Nullable
public String getKey() {
return key;
}
@Nullable
public String getRelay() {
return relay;
}
public void setThumbnail(@Nullable Bitmap thumbnail) {
this.thumbnail = thumbnail;
}
@Nullable
public Bitmap getThumbnail() {
return thumbnail;
}
}

Wyświetl plik

@ -0,0 +1,50 @@
package org.thoughtcrime.securesms.attachments;
import org.thoughtcrime.securesms.util.Util;
public class AttachmentId {
private final long rowId;
private final long uniqueId;
public AttachmentId(long rowId, long uniqueId) {
this.rowId = rowId;
this.uniqueId = uniqueId;
}
public long getRowId() {
return rowId;
}
public long getUniqueId() {
return uniqueId;
}
public String[] toStrings() {
return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)};
}
public String toString() {
return "(row id: " + rowId + ", unique ID: " + uniqueId + ")";
}
public boolean isValid() {
return rowId >= 0 && uniqueId >= 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttachmentId attachmentId = (AttachmentId)o;
if (rowId != attachmentId.rowId) return false;
return uniqueId == attachmentId.uniqueId;
}
@Override
public int hashCode() {
return Util.hashCode(rowId, uniqueId);
}
}

Wyświetl plik

@ -0,0 +1,59 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.mms.PartAuthority;
public class DatabaseAttachment extends Attachment {
private final AttachmentId attachmentId;
private final long mmsId;
private final boolean hasData;
public DatabaseAttachment(AttachmentId attachmentId, long mmsId, boolean hasData,
String contentType, int transferProgress, long size,
String location, String key, String relay)
{
super(contentType, transferProgress, size, location, key, relay);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.mmsId = mmsId;
}
@Override
@NonNull
public Uri getDataUri() {
return PartAuthority.getAttachmentDataUri(attachmentId);
}
@Override
@NonNull
public Uri getThumbnailUri() {
return PartAuthority.getAttachmentThumbnailUri(attachmentId);
}
public AttachmentId getAttachmentId() {
return attachmentId;
}
@Override
public boolean equals(Object other) {
return other != null &&
other instanceof DatabaseAttachment &&
((DatabaseAttachment) other).attachmentId.equals(this.attachmentId);
}
@Override
public int hashCode() {
return attachmentId.hashCode();
}
public long getMmsId() {
return mmsId;
}
public boolean hasData() {
return hasData;
}
}

Wyświetl plik

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MediaKey;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import java.util.LinkedList;
import java.util.List;
public class PointerAttachment extends Attachment {
public PointerAttachment(@NonNull String contentType, int transferState, long size,
@NonNull String location, @NonNull String key, @NonNull String relay)
{
super(contentType, transferState, size, location, key, relay);
}
@Nullable
@Override
public Uri getDataUri() {
return null;
}
@Nullable
@Override
public Uri getThumbnailUri() {
return null;
}
public static List<Attachment> forPointers(@NonNull MasterSecretUnion masterSecret, Optional<List<TextSecureAttachment>> pointers) {
List<Attachment> results = new LinkedList<>();
if (pointers.isPresent()) {
for (TextSecureAttachment pointer : pointers.get()) {
if (pointer.isPointer()) {
String encryptedKey = MediaKey.getEncrypted(masterSecret, pointer.asPointer().getKey());
results.add(new PointerAttachment(pointer.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING,
pointer.asPointer().getSize().or(0),
String.valueOf(pointer.asPointer().getId()),
encryptedKey, pointer.asPointer().getRelay().orNull()));
}
}
}
return results;
}
}

Wyświetl plik

@ -0,0 +1,42 @@
package org.thoughtcrime.securesms.attachments;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException;
import java.io.InputStream;
public class UriAttachment extends Attachment {
private final Uri dataUri;
private final Uri thumbnailUri;
public UriAttachment(Uri uri, String contentType, int transferState, long size) {
this(uri, uri, contentType, transferState, size);
}
public UriAttachment(Uri dataUri, Uri thumbnailUri,
String contentType, int transferState, long size)
{
super(contentType, transferState, size, null, null, null);
this.dataUri = dataUri;
this.thumbnailUri = thumbnailUri;
}
@Override
@NonNull
public Uri getDataUri() {
return dataUri;
}
@Override
@NonNull
public Uri getThumbnailUri() {
return thumbnailUri;
}
}

Wyświetl plik

@ -8,14 +8,13 @@ import android.graphics.Color;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.DrawableTypeRequest;
import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable;
@ -24,8 +23,9 @@ import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.RoundedCorners;
import org.thoughtcrime.securesms.mms.Slide;
@ -36,12 +36,9 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libaxolotl.util.guava.Optional;
import ws.com.google.android.mms.pdu.PduPart;
public class ThumbnailView extends FrameLayout {
private static final String TAG = ThumbnailView.class.getSimpleName();
private boolean hideControls;
private ImageView image;
private ImageView removeButton;
private int backgroundColorHint;
@ -53,7 +50,6 @@ public class ThumbnailView extends FrameLayout {
private SlideDeckListener slideDeckListener = null;
private ThumbnailClickListener thumbnailClickListener = null;
private ThumbnailClickListener downloadClickListener = null;
private String slideId = null;
private Slide slide = null;
public ThumbnailView(Context context) {
@ -78,21 +74,25 @@ public class ThumbnailView extends FrameLayout {
}
}
@Override public void setOnClickListener(OnClickListener l) {
@Override
public void setOnClickListener(OnClickListener l) {
parentClickListener = l;
}
@Override public void setFocusable(boolean focusable) {
@Override
public void setFocusable(boolean focusable) {
super.setFocusable(focusable);
if (transferControls.isPresent()) transferControls.get().setFocusable(focusable);
}
@Override public void setClickable(boolean clickable) {
@Override
public void setClickable(boolean clickable) {
super.setClickable(clickable);
if (transferControls.isPresent()) transferControls.get().setClickable(clickable);
}
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (removeButton != null) {
final int paddingHorizontal = removeButton.getWidth() / 2;
@ -117,32 +117,30 @@ public class ThumbnailView extends FrameLayout {
this.backgroundColorHint = color;
}
public void setImageResource(@Nullable MasterSecret masterSecret,
long id,
long timestamp,
@NonNull ListenableFutureTask<SlideDeck> slideDeckFuture)
public void setImageResource(@NonNull MasterSecret masterSecret,
@NonNull ListenableFutureTask<SlideDeck> slideDeckFuture,
boolean showControls, boolean showRemove)
{
if (this.slideDeckFuture != null && this.slideDeckListener != null) {
this.slideDeckFuture.removeListener(this.slideDeckListener);
}
String slideId = id + "::" + timestamp;
if (!slideId.equals(this.slideId)) {
if (!slideDeckFuture.equals(this.slideDeckFuture)) {
if (transferControls.isPresent()) getTransferControls().clear();
image.setImageDrawable(null);
this.slide = null;
this.slideId = slideId;
this.slide = null;
}
this.slideDeckListener = new SlideDeckListener(masterSecret);
this.slideDeckListener = new SlideDeckListener(masterSecret, showControls, showRemove);
this.slideDeckFuture = slideDeckFuture;
this.slideDeckFuture.addListener(this.slideDeckListener);
}
public void setImageResource(@NonNull Slide slide, @Nullable MasterSecret masterSecret) {
public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Slide slide,
boolean showControls, boolean showRemove)
{
if (Util.equals(slide, this.slide)) {
Log.w(TAG, "Not re-loading slide " + slide.getPart().getPartId());
Log.w(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri());
return;
}
@ -151,16 +149,21 @@ public class ThumbnailView extends FrameLayout {
return;
}
Log.w(TAG, "loading part with id " + slide.getPart().getPartId()
+ ", progress " + slide.getTransferProgress());
this.slide = slide;
loadInto(slide, masterSecret, image);
if (!hideControls) {
if (showControls) {
getTransferControls().setSlide(slide);
getTransferControls().setDownloadClickListener(new DownloadClickDispatcher());
} else if (transferControls.isPresent()) {
getTransferControls().setVisibility(View.GONE);
}
Log.w(TAG, "loading part with id " + slide.asAttachment().getDataUri()
+ ", progress " + slide.getTransferState());
this.slide = slide;
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(slide, masterSecret, showRemove).into(image);
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(slide).into(image);
else Glide.clear(image);
}
public void setThumbnailClickListener(ThumbnailClickListener listener) {
@ -182,16 +185,10 @@ public class ThumbnailView extends FrameLayout {
if (slideDeckFuture != null) slideDeckFuture.removeListener(slideDeckListener);
if (transferControls.isPresent()) getTransferControls().clear();
slide = null;
slideId = null;
slideDeckFuture = null;
slideDeckListener = null;
}
public void hideControls(boolean hideControls) {
this.hideControls = hideControls;
if (hideControls && transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
}
public void showProgressSpinner() {
getTransferControls().showProgressSpinner();
}
@ -203,48 +200,19 @@ public class ThumbnailView extends FrameLayout {
!((Activity)getContext()).isDestroyed();
}
private void loadInto(@NonNull Slide slide,
@Nullable MasterSecret masterSecret,
@NonNull ImageView view)
{
if (slide.getThumbnailUri() != null) {
buildThumbnailGlideRequest(slide, masterSecret).into(view);
} else if (!slide.isInProgress()) {
buildPlaceholderGlideRequest(slide).into(view);
} else {
Glide.clear(view);
private GenericRequestBuilder buildThumbnailGlideRequest(@NonNull Slide slide, @NonNull MasterSecret masterSecret, boolean showRemove) {
DrawableRequestBuilder<DecryptableUri> builder = Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
.crossFade()
.transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint));
if (showRemove) {
builder = builder.listener(new ThumbnailSetListener(slide.asAttachment()));
}
}
private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) {
final GenericRequestBuilder builder;
if (slide.isDraft()) builder = buildDraftGlideRequest(slide, masterSecret);
else builder = buildPartGlideRequest(slide, masterSecret);
if (slide.isInProgress()) return builder;
else return builder.error(R.drawable.ic_missing_thumbnail_picture);
}
private GenericRequestBuilder buildDraftGlideRequest(Slide slide, MasterSecret masterSecret) {
final DrawableTypeRequest<?> request;
if (masterSecret == null) request = Glide.with(getContext()).load(slide.getThumbnailUri());
else request = Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri()));
return request.transform(new RoundedCorners(getContext(), false, radius, backgroundColorHint))
.listener(new PduThumbnailSetListener(slide.getPart()));
}
private GenericRequestBuilder buildPartGlideRequest(Slide slide, MasterSecret masterSecret) {
if (masterSecret == null) {
throw new IllegalStateException("null MasterSecret when loading non-draft thumbnail");
}
return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
.crossFade()
.transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint));
}
private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) {
return Glide.with(getContext()).load(slide.getPlaceholderRes(getContext().getTheme()))
.asBitmap()
@ -253,9 +221,13 @@ public class ThumbnailView extends FrameLayout {
private class SlideDeckListener implements FutureTaskListener<SlideDeck> {
private final MasterSecret masterSecret;
private final boolean showControls;
private final boolean showRemove;
public SlideDeckListener(MasterSecret masterSecret) {
public SlideDeckListener(@NonNull MasterSecret masterSecret, boolean showControls, boolean showRemove) {
this.masterSecret = masterSecret;
this.showControls = showControls;
this.showRemove = showRemove;
}
@Override
@ -268,7 +240,7 @@ public class ThumbnailView extends FrameLayout {
Util.runOnMain(new Runnable() {
@Override
public void run() {
setImageResource(slide, masterSecret);
setImageResource(masterSecret, slide, showControls, showRemove);
}
});
} else {
@ -302,10 +274,10 @@ public class ThumbnailView extends FrameLayout {
private class ThumbnailClickDispatcher implements View.OnClickListener {
@Override
public void onClick(View view) {
if (thumbnailClickListener != null &&
slide != null &&
slide.getPart().getDataUri() != null &&
slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_DONE)
if (thumbnailClickListener != null &&
slide != null &&
slide.asAttachment().getDataUri() != null &&
slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE)
{
thumbnailClickListener.onClick(view, slide);
} else if (parentClickListener != null) {
@ -323,11 +295,12 @@ public class ThumbnailView extends FrameLayout {
}
}
private class PduThumbnailSetListener implements RequestListener<Object, GlideDrawable> {
private PduPart part;
private class ThumbnailSetListener implements RequestListener<Object, GlideDrawable> {
public PduThumbnailSetListener(@NonNull PduPart part) {
this.part = part;
private final Attachment attachment;
public ThumbnailSetListener(@NonNull Attachment attachment) {
this.attachment = attachment;
}
@Override
@ -339,7 +312,7 @@ public class ThumbnailView extends FrameLayout {
public boolean onResourceReady(GlideDrawable resource, Object model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
if (resource instanceof GlideBitmapDrawable) {
Log.w(TAG, "onResourceReady() for a Bitmap. Saving.");
part.setThumbnail(((GlideBitmapDrawable)resource).getBitmap());
attachment.setThumbnail(((GlideBitmapDrawable) resource).getBitmap());
}
LayoutParams layoutParams = (LayoutParams) getRemoveButton().getLayoutParams();
if (resource.getIntrinsicWidth() < getWidth()) {

Wyświetl plik

@ -21,7 +21,7 @@ import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.jobs.PartProgressEvent;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.util.Util;
@ -92,7 +92,7 @@ public class TransferControlView extends FrameLayout {
public void setSlide(final @NonNull Slide slide) {
this.slide = slide;
if (slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_STARTED) {
if (slide.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
showProgressSpinner();
} else if (slide.isPendingDownload()) {
downloadDetails.setText(slide.getContentDescription());
@ -164,7 +164,7 @@ public class TransferControlView extends FrameLayout {
@SuppressWarnings("unused")
public void onEventAsync(final PartProgressEvent event) {
if (this.slide != null && event.partId.equals(this.slide.getPart().getPartId())) {
if (this.slide != null && event.attachment.equals(this.slide.asAttachment())) {
Util.runOnMain(new Runnable() {
@Override
public void run() {

Wyświetl plik

@ -0,0 +1,530 @@
/**
* 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 android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VisibleForTesting;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import ws.com.google.android.mms.MmsException;
public class AttachmentDatabase extends Database {
private static final String TAG = AttachmentDatabase.class.getSimpleName();
static final String TABLE_NAME = "part";
static final String ROW_ID = "_id";
static final String MMS_ID = "mid";
static final String CONTENT_TYPE = "ct";
private static final String NAME = "name";
private static final String CONTENT_DISPOSITION = "cd";
private static final String CONTENT_LOCATION = "cl";
static final String DATA = "_data";
static final String TRANSFER_STATE = "pending_push";
static final String SIZE = "data_size";
private static final String THUMBNAIL = "thumbnail";
static final String THUMBNAIL_ASPECT_RATIO = "aspect_ratio";
static final String UNIQUE_ID = "unique_id";
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
public static final int TRANSFER_PROGRESS_AUTO_PENDING = 2;
public static final int TRANSFER_PROGRESS_FAILED = 3;
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + "seq" + " INTEGER DEFAULT 0, " +
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + "chset" + " INTEGER, " +
CONTENT_DISPOSITION + " TEXT, " + "fn" + " TEXT, " + "cid" + " TEXT, " +
CONTENT_LOCATION + " TEXT, " + "ctt_s" + " INTEGER, " +
"ctt_t" + " TEXT, " + "encrypted" + " INTEGER, " +
TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
THUMBNAIL + " TEXT, " + THUMBNAIL_ASPECT_RATIO + " REAL, " + UNIQUE_ID + " INTEGER NOT NULL);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + TRANSFER_STATE + ");",
};
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
public AttachmentDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public @NonNull InputStream getAttachmentStream(MasterSecret masterSecret, AttachmentId attachmentId)
throws IOException
{
InputStream dataStream = getDataStream(masterSecret, attachmentId, DATA);
if (dataStream == null) throw new IOException("No stream for: " + attachmentId);
else return dataStream;
}
public @NonNull InputStream getThumbnailStream(@NonNull MasterSecret masterSecret, @NonNull AttachmentId attachmentId)
throws IOException
{
Log.w(TAG, "getThumbnailStream(" + attachmentId + ")");
InputStream dataStream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
if (dataStream != null) {
return dataStream;
}
try {
InputStream generatedStream = thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId)).get();
if (generatedStream == null) throw new IOException("No thumbnail stream available: " + attachmentId);
else return generatedStream;
} catch (InterruptedException ie) {
throw new AssertionError("interrupted");
} catch (ExecutionException ee) {
Log.w(TAG, ee);
throw new IOException(ee);
}
}
public void setTransferProgressFailed(AttachmentId attachmentId, long mmsId)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_FAILED);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
}
public @Nullable Attachment getAttachment(AttachmentId attachmentId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, PART_ID_WHERE, attachmentId.toStrings(), null, null, null);
if (cursor != null && cursor.moveToFirst()) return getAttachment(cursor);
else return null;
} finally {
if (cursor != null)
cursor.close();
}
}
public @NonNull List<DatabaseAttachment> getAttachmentsForMessage(long mmsId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<DatabaseAttachment> results = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""},
null, null, null);
while (cursor != null && cursor.moveToNext()) {
results.add(getAttachment(cursor));
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
public @NonNull List<DatabaseAttachment> getPendingAttachments() {
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
final List<DatabaseAttachment> attachments = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
attachments.add(getAttachment(cursor));
}
} finally {
if (cursor != null) cursor.close();
}
return attachments;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAttachmentsForMessage(long mmsId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL}, MMS_ID + " = ?",
new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
String data = cursor.getString(0);
String thumbnail = cursor.getString(1);
if (!TextUtils.isEmpty(data)) {
new File(data).delete();
}
if (!TextUtils.isEmpty(thumbnail)) {
new File(thumbnail).delete();
}
}
} finally {
if (cursor != null)
cursor.close();
}
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId + ""});
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAllAttachments() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
File attachmentsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File[] attachments = attachmentsDirectory.listFiles();
for (File attachment : attachments) {
attachment.delete();
}
}
public long insertAttachmentsForPlaceholder(@NonNull MasterSecret masterSecret, long mmsId,
@NonNull AttachmentId attachmentId,
@NonNull InputStream inputStream)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = setAttachmentData(masterSecret, inputStream);
ContentValues values = new ContentValues();
values.put(DATA, partData.first.getAbsolutePath());
values.put(SIZE, partData.second);
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
values.put(CONTENT_LOCATION, (String)null);
values.put(CONTENT_DISPOSITION, (String)null);
values.put(NAME, (String) null);
if (database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings()) == 0) {
//noinspection ResultOfMethodCallIgnored
partData.first.delete();
} else {
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(mmsId));
}
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, attachmentId));
return partData.second;
}
void insertAttachmentsForMessage(@NonNull MasterSecretUnion masterSecret,
long mmsId,
@NonNull List<Attachment> attachments)
throws MmsException
{
Log.w(TAG, "insertParts(" + attachments.size() + ")");
for (Attachment attachment : attachments) {
AttachmentId attachmentId = insertAttachment(masterSecret, mmsId, attachment);
Log.w(TAG, "Inserted attachment at ID: " + attachmentId);
}
}
public @NonNull Attachment updateAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull Attachment attachment,
@NonNull InputStream inputStream)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
DatabaseAttachment databaseAttachment = (DatabaseAttachment) attachment;
File dataFile = getAttachmentDataFile(databaseAttachment.getAttachmentId(), DATA);
if (dataFile == null) {
throw new MmsException("No attachment data found!");
}
long dataSize = setAttachmentData(masterSecret, dataFile, inputStream);
ContentValues contentValues = new ContentValues();
contentValues.put(SIZE, dataSize);
database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings());
return new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(),
databaseAttachment.hasData(),
databaseAttachment.getContentType(),
databaseAttachment.getTransferState(),
dataSize, databaseAttachment.getLocation(),
databaseAttachment.getKey(),
databaseAttachment.getRelay());
}
public void markAttachmentUploaded(long messageId, Attachment attachment) {
ContentValues values = new ContentValues(1);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
database.update(TABLE_NAME, values, PART_ID_WHERE, ((DatabaseAttachment)attachment).getAttachmentId().toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
public void setTransferState(long messageId, @NonNull Attachment attachment, int transferState) {
if (!(attachment instanceof DatabaseAttachment)) {
throw new AssertionError("Attempt to update attachment that doesn't belong to DB!");
}
setTransferState(messageId, ((DatabaseAttachment) attachment).getAttachmentId(), transferState);
}
public void setTransferState(long messageId, @NonNull AttachmentId attachmentId, int transferState) {
final ContentValues values = new ContentValues(1);
final SQLiteDatabase database = databaseHelper.getWritableDatabase();
values.put(TRANSFER_STATE, transferState);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
ApplicationContext.getInstance(context).notifyMediaControlEvent();
}
@VisibleForTesting
@Nullable InputStream getDataStream(MasterSecret masterSecret, AttachmentId attachmentId, String dataType)
{
File dataFile = getAttachmentDataFile(attachmentId, dataType);
try {
if (dataFile != null) return new DecryptingPartInputStream(dataFile, masterSecret);
else return null;
} catch (FileNotFoundException e) {
Log.w(TAG, e);
return null;
}
}
private @Nullable File getAttachmentDataFile(@NonNull AttachmentId attachmentId,
@NonNull String dataType)
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[]{dataType}, PART_ID_WHERE, attachmentId.toStrings(),
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
if (cursor.isNull(0)) {
return null;
}
return new File(cursor.getString(0));
} else {
return null;
}
} finally {
if (cursor != null)
cursor.close();
}
}
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull Uri uri)
throws MmsException
{
try {
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
return setAttachmentData(masterSecret, inputStream);
} catch (IOException e) {
throw new MmsException(e);
}
}
private @NonNull Pair<File, Long> setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull InputStream in)
throws MmsException
{
try {
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
return new Pair<>(dataFile, setAttachmentData(masterSecret, dataFile, in));
} catch (IOException e) {
throw new MmsException(e);
}
}
private long setAttachmentData(@NonNull MasterSecret masterSecret,
@NonNull File destination,
@NonNull InputStream in)
throws MmsException
{
try {
OutputStream out = new EncryptingPartOutputStream(destination, masterSecret);
return Util.copy(in, out);
} catch (IOException e) {
throw new MmsException(e);
}
}
private DatabaseAttachment getAttachment(Cursor cursor) {
return new DatabaseAttachment(new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID))),
cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)),
!cursor.isNull(cursor.getColumnIndexOrThrow(DATA)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
cursor.getString(cursor.getColumnIndexOrThrow(NAME)));
}
private AttachmentId insertAttachment(MasterSecretUnion masterSecret, long mmsId, Attachment attachment)
throws MmsException
{
Log.w(TAG, "Inserting attachment for mms id: " + mmsId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = null;
long uniqueId = System.currentTimeMillis();
if (masterSecret.getMasterSecret().isPresent() && attachment.getDataUri() != null) {
partData = setAttachmentData(masterSecret.getMasterSecret().get(), attachment.getDataUri());
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
}
ContentValues contentValues = new ContentValues();
contentValues.put(MMS_ID, mmsId);
contentValues.put(CONTENT_TYPE, attachment.getContentType());
contentValues.put(TRANSFER_STATE, attachment.getTransferState());
contentValues.put(UNIQUE_ID, uniqueId);
contentValues.put(CONTENT_LOCATION, attachment.getLocation());
contentValues.put(CONTENT_DISPOSITION, attachment.getKey());
contentValues.put(NAME, attachment.getRelay());
if (partData != null) {
contentValues.put(DATA, partData.first.getAbsolutePath());
contentValues.put(SIZE, partData.second);
}
long rowId = database.insert(TABLE_NAME, null, contentValues);
AttachmentId attachmentId = new AttachmentId(rowId, uniqueId);
if (attachment.getThumbnail() != null && masterSecret.getMasterSecret().isPresent()) {
Log.w(TAG, "inserting pre-generated thumbnail");
ThumbnailData data = new ThumbnailData(attachment.getThumbnail());
updateAttachmentThumbnail(masterSecret.getMasterSecret().get(), attachmentId, data.toDataStream(), data.getAspectRatio());
} else if (!attachment.isInProgress()) {
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), attachmentId));
}
return attachmentId;
}
@VisibleForTesting
void updateAttachmentThumbnail(MasterSecret masterSecret, AttachmentId attachmentId, InputStream in, float aspectRatio)
throws MmsException
{
Log.w(TAG, "updating part thumbnail for #" + attachmentId);
Pair<File, Long> thumbnailFile = setAttachmentData(masterSecret, in);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(2);
values.put(THUMBNAIL, thumbnailFile.first.getAbsolutePath());
values.put(THUMBNAIL_ASPECT_RATIO, aspectRatio);
database.update(TABLE_NAME, values, PART_ID_WHERE, attachmentId.toStrings());
}
@VisibleForTesting
class ThumbnailFetchCallable implements Callable<InputStream> {
private final MasterSecret masterSecret;
private final AttachmentId attachmentId;
public ThumbnailFetchCallable(MasterSecret masterSecret, AttachmentId attachmentId) {
this.masterSecret = masterSecret;
this.attachmentId = attachmentId;
}
@Override
public @Nullable InputStream call() throws Exception {
final InputStream stream = getDataStream(masterSecret, attachmentId, THUMBNAIL);
if (stream != null) {
return stream;
}
Attachment attachment = getAttachment(attachmentId);
if (attachment == null || attachment.isInProgress()) {
return null;
}
ThumbnailData data = MediaUtil.generateThumbnail(context, masterSecret, attachment.getContentType(), attachment.getDataUri());
if (data == null) {
return null;
}
updateAttachmentThumbnail(masterSecret, attachmentId, data.toDataStream(), data.getAspectRatio());
return getDataStream(masterSecret, attachmentId, THUMBNAIL);
}
}
}

Wyświetl plik

@ -78,7 +78,8 @@ public class DatabaseFactory {
private final SmsDatabase sms;
private final EncryptingSmsDatabase encryptingSms;
private final MmsDatabase mms;
private final PartDatabase part;
private final AttachmentDatabase attachments;
private final ImageDatabase image;
private final ThreadDatabase thread;
private final CanonicalAddressDatabase address;
private final MmsAddressDatabase mmsAddress;
@ -123,8 +124,12 @@ public class DatabaseFactory {
return getInstance(context).encryptingSms;
}
public static PartDatabase getPartDatabase(Context context) {
return getInstance(context).part;
public static AttachmentDatabase getAttachmentDatabase(Context context) {
return getInstance(context).attachments;
}
public static ImageDatabase getImageDatabase(Context context) {
return getInstance(context).image;
}
public static MmsAddressDatabase getMmsAddressDatabase(Context context) {
@ -160,7 +165,8 @@ public class DatabaseFactory {
this.sms = new SmsDatabase(context, databaseHelper);
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
this.mms = new MmsDatabase(context, databaseHelper);
this.part = new PartDatabase(context, databaseHelper);
this.attachments = new AttachmentDatabase(context, databaseHelper);
this.image = new ImageDatabase(context, databaseHelper);
this.thread = new ThreadDatabase(context, databaseHelper);
this.address = CanonicalAddressDatabase.getInstance(context);
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
@ -180,7 +186,7 @@ public class DatabaseFactory {
this.sms.reset(databaseHelper);
this.encryptingSms.reset(databaseHelper);
this.mms.reset(databaseHelper);
this.part.reset(databaseHelper);
this.attachments.reset(databaseHelper);
this.thread.reset(databaseHelper);
this.mmsAddress.reset(databaseHelper);
this.mmsSmsDatabase.reset(databaseHelper);
@ -377,6 +383,7 @@ public class DatabaseFactory {
body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is);
//noinspection ResultOfMethodCallIgnored
dataFile.delete();
db.delete("part", "_id = ?", new String[] {partId+""});
} catch (IOException e) {
@ -491,7 +498,7 @@ public class DatabaseFactory {
public void onCreate(SQLiteDatabase db) {
db.execSQL(SmsDatabase.CREATE_TABLE);
db.execSQL(MmsDatabase.CREATE_TABLE);
db.execSQL(PartDatabase.CREATE_TABLE);
db.execSQL(AttachmentDatabase.CREATE_TABLE);
db.execSQL(ThreadDatabase.CREATE_TABLE);
db.execSQL(MmsAddressDatabase.CREATE_TABLE);
db.execSQL(IdentityDatabase.CREATE_TABLE);
@ -502,7 +509,7 @@ public class DatabaseFactory {
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, PartDatabase.CREATE_INDEXS);
executeStatements(db, AttachmentDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, MmsAddressDatabase.CREATE_INDEXS);
executeStatements(db, DraftDatabase.CREATE_INDEXS);

Wyświetl plik

@ -0,0 +1,100 @@
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
public class ImageDatabase extends Database {
private final static String IMAGES_QUERY = "SELECT " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL_ASPECT_RATIO + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.TRANSFER_STATE + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.SIZE + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.NORMALIZED_DATE_RECEIVED + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.ADDRESS + " "
+ "FROM " + AttachmentDatabase.TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME
+ " ON " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " "
+ "WHERE " + AttachmentDatabase.MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
+ " FROM " + MmsDatabase.TABLE_NAME
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND "
+ AttachmentDatabase.CONTENT_TYPE + " LIKE 'image/%' "
+ "ORDER BY " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " DESC";
public ImageDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Cursor getImagesForThread(long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.rawQuery(IMAGES_QUERY, new String[]{threadId+""});
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public static class ImageRecord {
private final AttachmentId attachmentId;
private final long mmsId;
private final boolean hasData;
private final String contentType;
private final String address;
private final long date;
private final int transferState;
private final long size;
private ImageRecord(AttachmentId attachmentId, long mmsId, boolean hasData,
String contentType, String address, long date,
int transferState, long size)
{
this.attachmentId = attachmentId;
this.mmsId = mmsId;
this.hasData = hasData;
this.contentType = contentType;
this.address = address;
this.date = date;
this.transferState = transferState;
this.size = size;
}
public static ImageRecord from(Cursor cursor) {
AttachmentId attachmentId = new AttachmentId(cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID)));
return new ImageRecord(attachmentId,
cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID)),
!cursor.isNull(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA)),
cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)),
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)),
cursor.getInt(cursor.getColumnIndexOrThrow(AttachmentDatabase.TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.SIZE)));
}
public Attachment getAttachment() {
return new DatabaseAttachment(attachmentId, mmsId, hasData, contentType, transferState, size, null, null, null);
}
public String getContentType() {
return contentType;
}
public String getAddress() {
return address;
}
public long getDate() {
return date;
}
}
}

Wyświetl plik

@ -21,21 +21,17 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.support.annotation.NonNull;
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 ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import ws.com.google.android.mms.pdu.PduHeaders;
public class MmsAddressDatabase extends Database {
private static final String TAG = MmsAddressDatabase.class.getSimpleName();
@ -59,83 +55,77 @@ public class MmsAddressDatabase extends Database {
super(context, databaseHelper);
}
private void insertAddress(long messageId, int type, EncodedStringValue address) {
if (address != null) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(MMS_ID, messageId);
contentValues.put(TYPE, type);
contentValues.put(ADDRESS, toIsoString(address.getTextString()));
contentValues.put(ADDRESS_CHARSET, address.getCharacterSet());
database.insert(TABLE_NAME, null, contentValues);
private void insertAddress(long messageId, int type, @NonNull String value) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(MMS_ID, messageId);
contentValues.put(TYPE, type);
contentValues.put(ADDRESS, value);
contentValues.put(ADDRESS_CHARSET, "UTF-8");
database.insert(TABLE_NAME, null, contentValues);
}
private void insertAddress(long messageId, int type, @NonNull List<String> addresses) {
for (String address : addresses) {
insertAddress(messageId, type, address);
}
}
private void insertAddress(long messageId, int type, EncodedStringValue[] addresses) {
if (addresses != null) {
for (int i=0;i<addresses.length;i++) {
insertAddress(messageId, type, addresses[i]);
}
public void insertAddressesForId(long messageId, MmsAddresses addresses) {
if (addresses.getFrom() != null) {
insertAddress(messageId, PduHeaders.FROM, addresses.getFrom());
}
insertAddress(messageId, PduHeaders.TO, addresses.getTo());
insertAddress(messageId, PduHeaders.CC, addresses.getCc());
insertAddress(messageId, PduHeaders.BCC, addresses.getBcc());
}
private void addAddress(Cursor cursor, PduHeaders headers) {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(TYPE));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
long charset = cursor.getLong(cursor.getColumnIndexOrThrow(ADDRESS_CHARSET));
EncodedStringValue encodedAddress = new EncodedStringValue((int)charset, getBytes(address));
if (type == PduHeaders.FROM)
headers.setEncodedStringValue(encodedAddress, PduHeaders.FROM);
else
headers.appendEncodedStringValue(encodedAddress, (int)type);
}
public void insertAddressesForId(long messageId, PduHeaders headers) {
insertAddress(messageId, PduHeaders.FROM, headers.getEncodedStringValue(PduHeaders.FROM));
insertAddress(messageId, PduHeaders.TO, headers.getEncodedStringValues(PduHeaders.TO));
insertAddress(messageId, PduHeaders.CC, headers.getEncodedStringValues(PduHeaders.CC));
insertAddress(messageId, PduHeaders.BCC, headers.getEncodedStringValues(PduHeaders.BCC));
}
public void getAddressesForId(long messageId, PduHeaders headers) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {messageId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
addAddress(cursor, headers);
}
} finally {
if (cursor != null)
cursor.close();
}
}
public List<String> getAddressesForId(long messageId) {
List<String> results = new LinkedList<String>();
public MmsAddresses getAddressesForId(long messageId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
String from = null;
List<String> to = new LinkedList<>();
List<String> cc = new LinkedList<>();
List<String> bcc = new LinkedList<>();
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {messageId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
results.add(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(TYPE));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
if (type == PduHeaders.FROM) from = address;
if (type == PduHeaders.TO) to.add(address);
if (type == PduHeaders.CC) cc.add(address);
if (type == PduHeaders.BCC) bcc.add(address);
}
} finally {
if (cursor != null)
cursor.close();
}
return new MmsAddresses(from, to, cc, bcc);
}
public List<String> getAddressesListForId(long messageId) {
List<String> results = new LinkedList<>();
MmsAddresses addresses = getAddressesForId(messageId);
if (addresses.getFrom() != null) {
results.add(addresses.getFrom());
}
results.addAll(addresses.getTo());
results.addAll(addresses.getCc());
results.addAll(addresses.getBcc());
return results;
}
public Recipients getRecipientsForId(long messageId) {
List<String> numbers = getAddressesForId(messageId);
List<String> numbers = getAddressesListForId(messageId);
List<Recipient> results = new LinkedList<>();
for (String number : numbers) {
@ -158,22 +148,4 @@ public class MmsAddressDatabase extends Database {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
}
private byte[] getBytes(String data) {
try {
return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
Log.e("PduHeadersBuilder", "ISO_8859_1 must be supported!", e);
return new byte[0];
}
}
private String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
} catch (UnsupportedEncodingException e) {
Log.e("MmsDatabase", "ISO_8859_1 must be supported!", e);
return "";
}
}
}

Wyświetl plik

@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.database;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
public class MmsAddresses {
private final @Nullable String from;
private final @NonNull List<String> to;
private final @NonNull List<String> cc;
private final @NonNull List<String> bcc;
public MmsAddresses(@Nullable String from, @NonNull List<String> to,
@NonNull List<String> cc, @NonNull List<String> bcc)
{
this.from = from;
this.to = to;
this.cc = cc;
this.bcc = bcc;
}
@NonNull
public List<String> getTo() {
return to;
}
@NonNull
public List<String> getCc() {
return cc;
}
@NonNull
public List<String> getBcc() {
return bcc;
}
@Nullable
public String getFrom() {
return from;
}
public static MmsAddresses forTo(@NonNull List<String> to) {
return new MmsAddresses(null, to, new LinkedList<String>(), new LinkedList<String>());
}
public static MmsAddresses forBcc(@NonNull List<String> bcc) {
return new MmsAddresses(null, new LinkedList<String>(), new LinkedList<String>(), bcc);
}
public static MmsAddresses forFrom(@NonNull String from) {
return new MmsAddresses(from, new LinkedList<String>(), new LinkedList<String>(), new LinkedList<String>());
}
}

Wyświetl plik

@ -23,6 +23,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@ -32,14 +33,16 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -48,9 +51,8 @@ import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
@ -67,7 +69,6 @@ import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet;
@ -78,25 +79,13 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduHeaders;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.SendReq;
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumberOrGroup;
// XXXX Clean up MMS efficiency:
// 1) We need to be careful about how much memory we're using for parts. SoftRefereences.
// 2) How many queries do we make? calling getMediaMessageForId() from within an existing query
// seems wasteful.
public class MmsDatabase extends MessagingDatabase {
private static final String TAG = MmsDatabase.class.getSimpleName();
@ -105,47 +94,29 @@ public class MmsDatabase extends MessagingDatabase {
static final String DATE_SENT = "date";
static final String DATE_RECEIVED = "date_received";
public static final String MESSAGE_BOX = "msg_box";
private static final String MESSAGE_ID = "m_id";
private static final String SUBJECT = "sub";
private static final String SUBJECT_CHARSET = "sub_cs";
static final String CONTENT_TYPE = "ct_t";
static final String CONTENT_LOCATION = "ct_l";
static final String EXPIRY = "exp";
private static final String MESSAGE_CLASS = "m_cls";
public static final String MESSAGE_TYPE = "m_type";
private static final String MMS_VERSION = "v";
static final String MESSAGE_SIZE = "m_size";
private static final String PRIORITY = "pri";
private static final String READ_REPORT = "rr";
private static final String REPORT_ALLOWED = "rpt_a";
private static final String RESPONSE_STATUS = "resp_st";
static final String STATUS = "st";
static final String TRANSACTION_ID = "tr_id";
private static final String RETRIEVE_STATUS = "retr_st";
private static final String RETRIEVE_TEXT = "retr_txt";
private static final String RETRIEVE_TEXT_CS = "retr_txt_cs";
private static final String READ_STATUS = "read_status";
private static final String CONTENT_CLASS = "ct_cls";
private static final String RESPONSE_TEXT = "resp_txt";
private static final String DELIVERY_TIME = "d_tm";
private static final String DELIVERY_REPORT = "d_rpt";
static final String PART_COUNT = "part_count";
static final String NETWORK_FAILURE = "network_failures";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
THREAD_ID + " INTEGER, " + DATE_SENT + " INTEGER, " + DATE_RECEIVED + " INTEGER, " + MESSAGE_BOX + " INTEGER, " +
READ + " INTEGER DEFAULT 0, " + MESSAGE_ID + " TEXT, " + SUBJECT + " TEXT, " +
SUBJECT_CHARSET + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " +
CONTENT_TYPE + " TEXT, " + CONTENT_LOCATION + " TEXT, " + ADDRESS + " TEXT, " +
READ + " INTEGER DEFAULT 0, " + "m_id" + " TEXT, " + "sub" + " TEXT, " +
"sub_cs" + " INTEGER, " + BODY + " TEXT, " + PART_COUNT + " INTEGER, " +
"ct_t" + " TEXT, " + CONTENT_LOCATION + " TEXT, " + ADDRESS + " TEXT, " +
ADDRESS_DEVICE_ID + " INTEGER, " +
EXPIRY + " INTEGER, " + MESSAGE_CLASS + " TEXT, " + MESSAGE_TYPE + " INTEGER, " +
MMS_VERSION + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + PRIORITY + " INTEGER, " +
READ_REPORT + " INTEGER, " + REPORT_ALLOWED + " INTEGER, " + RESPONSE_STATUS + " INTEGER, " +
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
EXPIRY + " INTEGER, " + "m_cls" + " TEXT, " + MESSAGE_TYPE + " INTEGER, " +
"v" + " INTEGER, " + MESSAGE_SIZE + " INTEGER, " + "pri" + " INTEGER, " +
"rr" + " INTEGER, " + "rpt_a" + " INTEGER, " + "resp_st" + " INTEGER, " +
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + "retr_st" + " INTEGER, " +
"retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " +
"ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " INTEGER, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " +
NETWORK_FAILURE + " TEXT DEFAULT NULL," + DELIVERY_REPORT + " INTEGER);";
NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -159,11 +130,10 @@ public class MmsDatabase extends MessagingDatabase {
private static final String[] MMS_PROJECTION = new String[] {
ID, THREAD_ID, DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
MESSAGE_BOX, READ, MESSAGE_ID, SUBJECT, SUBJECT_CHARSET, CONTENT_TYPE,
CONTENT_LOCATION, EXPIRY, MESSAGE_CLASS, MESSAGE_TYPE, MMS_VERSION,
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
MESSAGE_BOX, READ,
CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE,
MESSAGE_SIZE, STATUS, TRANSACTION_ID,
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE
};
@ -226,7 +196,7 @@ public class MmsDatabase extends MessagingDatabase {
while (cursor.moveToNext()) {
if (Types.isOutgoingMessageType(cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)))) {
List<String> addresses = addressDatabase.getAddressesForId(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
List<String> addresses = addressDatabase.getAddressesListForId(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
for (String storedAddress : addresses) {
try {
@ -280,19 +250,13 @@ public class MmsDatabase extends MessagingDatabase {
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
}
try {
PduHeaders headers = retrieved.getPduHeaders();
Set<String> group = new HashSet<String>();
Set<String> group = new HashSet<>();
EncodedStringValue encodedFrom = headers.getEncodedStringValue(PduHeaders.FROM);
EncodedStringValue[] encodedCcList = headers.getEncodedStringValues(PduHeaders.CC);
EncodedStringValue[] encodedToList = headers.getEncodedStringValues(PduHeaders.TO);
if (encodedFrom == null) {
if (retrieved.getAddresses().getFrom() == null) {
throw new MmsException("FROM value in PduHeaders did not exist.");
}
group.add(new String(encodedFrom.getTextString(), CharacterSets.MIMENAME_ISO_8859_1));
group.add(retrieved.getAddresses().getFrom());
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String localNumber = telephonyManager.getLine1Number();
@ -301,46 +265,35 @@ public class MmsDatabase extends MessagingDatabase {
localNumber = TextSecurePreferences.getLocalNumber(context);
}
if (encodedCcList != null) {
for (EncodedStringValue encodedCc : encodedCcList) {
String cc = new String(encodedCc.getTextString(), CharacterSets.MIMENAME_ISO_8859_1);
for (String cc : retrieved.getAddresses().getCc()) {
PhoneNumberUtil.MatchType match;
PhoneNumberUtil.MatchType match;
if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH;
else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, cc);
if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH;
else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, cc);
if (match == PhoneNumberUtil.MatchType.NO_MATCH ||
match == PhoneNumberUtil.MatchType.NOT_A_NUMBER)
{
group.add(cc);
}
if (match == PhoneNumberUtil.MatchType.NO_MATCH ||
match == PhoneNumberUtil.MatchType.NOT_A_NUMBER)
{
group.add(cc);
}
}
if (encodedToList != null && (encodedToList.length > 1 || group.size() > 1)) {
for (EncodedStringValue encodedTo : encodedToList) {
String to = new String(encodedTo.getTextString(), CharacterSets.MIMENAME_ISO_8859_1);
for (String to : retrieved.getAddresses().getTo()) {
PhoneNumberUtil.MatchType match;
PhoneNumberUtil.MatchType match;
if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH;
else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, to);
if (localNumber == null) match = PhoneNumberUtil.MatchType.NO_MATCH;
else match = PhoneNumberUtil.getInstance().isNumberMatch(localNumber, to);
if (match == PhoneNumberUtil.MatchType.NO_MATCH ||
match == PhoneNumberUtil.MatchType.NOT_A_NUMBER)
{
group.add(to);
}
if (match == PhoneNumberUtil.MatchType.NO_MATCH ||
match == PhoneNumberUtil.MatchType.NOT_A_NUMBER)
{
group.add(to);
}
}
String recipientsList = Util.join(group, ",");
Recipients recipients = RecipientFactory.getRecipientsFromString(context, recipientsList, false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
private long getThreadIdFor(@NonNull NotificationInd notification) {
@ -354,7 +307,7 @@ public class MmsDatabase extends MessagingDatabase {
public Cursor getMessage(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {messageId+""},
Cursor cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {messageId + ""},
null, null, null);
setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId));
return cursor;
@ -399,13 +352,7 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsSent(long messageId, byte[] mmsId, long status) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(RESPONSE_STATUS, status);
contentValues.put(MESSAGE_ID, new String(mmsId));
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId+""});
public void markAsSent(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE);
notifyConversationListeners(getThreadIdForMessage(messageId));
}
@ -498,69 +445,74 @@ public class MmsDatabase extends MessagingDatabase {
}
public Optional<NotificationInd> getNotification(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
Cursor cursor = null;
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[] {String.valueOf(messageId)}, null, null, null);
if (cursor != null && cursor.moveToNext()) {
PduHeaders headers = getHeadersFromCursor(cursor);
addressDatabase.getAddressesForId(messageId, headers);
PduHeaders headers = new PduHeaders();
PduHeadersBuilder builder = new PduHeadersBuilder(headers, cursor);
builder.addText(CONTENT_LOCATION, PduHeaders.CONTENT_LOCATION);
builder.addLong(NORMALIZED_DATE_SENT, PduHeaders.DATE);
builder.addLong(EXPIRY, PduHeaders.EXPIRY);
builder.addLong(MESSAGE_SIZE, PduHeaders.MESSAGE_SIZE);
builder.addText(TRANSACTION_ID, PduHeaders.TRANSACTION_ID);
return Optional.of(new NotificationInd(headers));
} else {
return Optional.absent();
}
} catch (InvalidHeaderValueException e) {
Log.w("MmsDatabase", e);
return Optional.absent();
} finally {
if (cursor != null)
cursor.close();
}
}
public SendReq getOutgoingMessage(MasterSecret masterSecret, long messageId)
public OutgoingMediaMessage getOutgoingMessage(MasterSecret masterSecret, long messageId)
throws MmsException, NoSuchMessageException
{
MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context);
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
SQLiteDatabase database = databaseHelper.getReadableDatabase();
MasterCipher masterCipher = new MasterCipher(masterSecret);
Cursor cursor = null;
String selection = ID_WHERE;
String[] selectionArgs = new String[]{String.valueOf(messageId)};
MmsAddressDatabase addr = DatabaseFactory.getMmsAddressDatabase(context);
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, MMS_PROJECTION, selection, selectionArgs, null, null, null);
cursor = database.query(TABLE_NAME, MMS_PROJECTION, ID_WHERE, new String[]{String.valueOf(messageId)}, null, null, null);
if (cursor != null && cursor.moveToNext()) {
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
PduHeaders headers = getHeadersFromCursor(cursor);
addr.getAddressesForId(messageId, headers);
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(messageId));
MmsAddresses addresses = addr.getAddressesForId(messageId);
List<String> destinations = new LinkedList<>();
String body = getDecryptedBody(masterSecret, messageText, outboxType);
PduBody body = getPartsAsBody(partDatabase.getParts(messageId));
destinations.addAll(addresses.getBcc());
destinations.addAll(addresses.getCc());
destinations.addAll(addresses.getTo());
try {
if (!TextUtils.isEmpty(messageText) && Types.isSymmetricEncryption(outboxType)) {
body.addPart(new TextSlide(context, masterCipher.decryptBody(messageText)).getPart());
} else if (!TextUtils.isEmpty(messageText)) {
body.addPart(new TextSlide(context, messageText).getPart());
}
} catch (InvalidMessageException e) {
Log.w("MmsDatabase", e);
Recipients recipients = RecipientFactory.getRecipientsFromStrings(context, destinations, false);
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
return new OutgoingGroupMediaMessage(recipients, body, attachments, timestamp);
}
return new SendReq(headers, body, messageId, outboxType, timestamp);
OutgoingMediaMessage message = new OutgoingMediaMessage(recipients, body, attachments, timestamp,
!addresses.getBcc().isEmpty() ? ThreadDatabase.DistributionTypes.BROADCAST :
ThreadDatabase.DistributionTypes.DEFAULT);
if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message);
}
return message;
}
throw new NoSuchMessageException("No record found for id: " + messageId);
} catch (IOException e) {
throw new MmsException(e);
} finally {
if (cursor != null)
cursor.close();
@ -569,20 +521,35 @@ public class MmsDatabase extends MessagingDatabase {
public long copyMessageInbox(MasterSecret masterSecret, long messageId) throws MmsException {
try {
SendReq request = getOutgoingMessage(masterSecret, messageId);
ContentValues contentValues = getContentValuesFromHeader(request.getPduHeaders());
OutgoingMediaMessage request = getOutgoingMessage(masterSecret, messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(ADDRESS, request.getRecipients().getPrimaryRecipient().getNumber());
contentValues.put(DATE_SENT, request.getSentTimeMillis());
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.ENCRYPTION_SYMMETRIC_BIT);
contentValues.put(THREAD_ID, getThreadIdForMessage(messageId));
contentValues.put(READ, 1);
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
for (int i = 0; i < request.getBody().getPartsNum(); i++) {
request.getBody().getPart(i).setTransferProgress(PartDatabase.TRANSFER_PROGRESS_DONE);
List<Attachment> attachments = new LinkedList<>();
for (Attachment attachment : request.getAttachments()) {
DatabaseAttachment databaseAttachment = (DatabaseAttachment)attachment;
attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(),
databaseAttachment.hasData(),
databaseAttachment.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
databaseAttachment.getSize(),
databaseAttachment.getLocation(),
databaseAttachment.getKey(),
databaseAttachment.getRelay()));
}
return insertMediaMessage(new MasterSecretUnion(masterSecret), request.getPduHeaders(),
request.getBody(), contentValues);
return insertMediaMessage(new MasterSecretUnion(masterSecret),
MmsAddresses.forTo(request.getRecipients().toNumberStringList(false)),
request.getBody(),
attachments,
contentValues);
} catch (NoSuchMessageException e) {
throw new MmsException(e);
}
@ -594,11 +561,6 @@ public class MmsDatabase extends MessagingDatabase {
long threadId, long mailbox)
throws MmsException
{
PduHeaders headers = retrieved.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
((mailbox & Types.SECURE_MESSAGE_BIT) != 0);
if (threadId == -1 || retrieved.isGroupMessage()) {
try {
threadId = getThreadIdFor(retrieved);
@ -609,24 +571,28 @@ public class MmsDatabase extends MessagingDatabase {
}
}
ContentValues contentValues = new ContentValues();
contentValues.put(DATE_SENT, retrieved.getSentTimeMillis());
contentValues.put(ADDRESS, retrieved.getAddresses().getFrom());
contentValues.put(MESSAGE_BOX, mailbox);
contentValues.put(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
contentValues.put(THREAD_ID, threadId);
contentValues.put(CONTENT_LOCATION, contentLocation);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(READ, unread ? 0 : 1);
contentValues.put(READ, 0);
if (!contentValues.containsKey(DATE_SENT)) {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
}
long messageId = insertMediaMessage(masterSecret, retrieved.getPduHeaders(),
retrieved.getBody(), contentValues);
if (unread) {
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
}
long messageId = insertMediaMessage(masterSecret, retrieved.getAddresses(),
retrieved.getBody(), retrieved.getAttachments(),
contentValues);
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId);
notifyConversationListeners(threadId);
jobManager.add(new TrimThreadJob(context, threadId));
@ -677,12 +643,27 @@ public class MmsDatabase extends MessagingDatabase {
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
long threadId = getThreadIdFor(notification);
PduHeaders headers = notification.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
long threadId = getThreadIdFor(notification);
PduHeaders headers = notification.getPduHeaders();
ContentValues contentValues = new ContentValues();
ContentValuesBuilder contentBuilder = new ContentValuesBuilder(contentValues);
Log.w(TAG, "Message received type: " + headers.getOctet(PduHeaders.MESSAGE_TYPE));
contentBuilder.add(CONTENT_LOCATION, headers.getTextString(PduHeaders.CONTENT_LOCATION));
contentBuilder.add(DATE_SENT, headers.getLongInteger(PduHeaders.DATE) * 1000L);
contentBuilder.add(EXPIRY, headers.getLongInteger(PduHeaders.EXPIRY));
contentBuilder.add(MESSAGE_SIZE, headers.getLongInteger(PduHeaders.MESSAGE_SIZE));
contentBuilder.add(TRANSACTION_ID, headers.getTextString(PduHeaders.TRANSACTION_ID));
contentBuilder.add(MESSAGE_TYPE, headers.getOctet(PduHeaders.MESSAGE_TYPE));
if (headers.getEncodedStringValue(PduHeaders.FROM) != null) {
contentBuilder.add(ADDRESS, headers.getEncodedStringValue(PduHeaders.FROM).getTextString());
} else {
contentBuilder.add(ADDRESS, null);
}
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE);
contentValues.put(THREAD_ID, threadId);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
@ -693,7 +674,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
long messageId = db.insert(TABLE_NAME, null, contentValues);
addressDatabase.insertAddressesForId(messageId, headers);
addressDatabase.insertAddressesForId(messageId, MmsAddresses.forFrom(Util.toIsoString(notification.getFrom().getTextString())));
return new Pair<>(messageId, threadId);
}
@ -711,7 +692,7 @@ public class MmsDatabase extends MessagingDatabase {
public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret,
@NonNull OutgoingMediaMessage message,
long threadId, boolean forceSms, long timestamp)
long threadId, boolean forceSms)
throws MmsException
{
long type = Types.BASE_OUTBOX_TYPE;
@ -727,26 +708,21 @@ public class MmsDatabase extends MessagingDatabase {
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
}
SendReq sendRequest = new SendReq();
sendRequest.setDate(timestamp / 1000L);
sendRequest.setBody(message.getPduBody());
sendRequest.setContentType(ContentType.MULTIPART_MIXED.getBytes());
List<String> recipientNumbers = message.getRecipients().toNumberStringList(true);
String[] recipientsArray = message.getRecipients().toNumberStringArray(true);
EncodedStringValue[] encodedNumbers = EncodedStringValue.encodeStrings(recipientsArray);
MmsAddresses addresses;
if (message.getRecipients().isSingleRecipient()) {
sendRequest.setTo(encodedNumbers);
} else if (message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST) {
sendRequest.setBcc(encodedNumbers);
} else if (message.getDistributionType() == ThreadDatabase.DistributionTypes.CONVERSATION ||
message.getDistributionType() == 0)
if (!message.getRecipients().isSingleRecipient() &&
message.getDistributionType() == ThreadDatabase.DistributionTypes.BROADCAST)
{
sendRequest.setTo(encodedNumbers);
addresses = MmsAddresses.forBcc(recipientNumbers);
} else {
addresses = MmsAddresses.forTo(recipientNumbers);
}
PduHeaders headers = sendRequest.getPduHeaders();
ContentValues contentValues = getContentValuesFromHeader(headers);
ContentValues contentValues = new ContentValues();
contentValues.put(DATE_SENT, message.getSentTimeMillis());
contentValues.put(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
contentValues.put(MESSAGE_BOX, type);
contentValues.put(THREAD_ID, threadId);
@ -754,15 +730,9 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
contentValues.remove(ADDRESS);
if (sendRequest.getBody() != null) {
for (int i = 0; i < sendRequest.getBody().getPartsNum(); i++) {
sendRequest.getBody().getPart(i).setTransferProgress(PartDatabase.TRANSFER_PROGRESS_STARTED);
}
}
long messageId = insertMediaMessage(masterSecret, addresses, message.getBody(),
message.getAttachments(), contentValues);
long messageId = insertMediaMessage(masterSecret,
sendRequest.getPduHeaders(),
sendRequest.getBody(), contentValues);
jobManager.add(new TrimThreadJob(context, threadId));
return messageId;
@ -776,35 +746,50 @@ public class MmsDatabase extends MessagingDatabase {
}
}
private long insertMediaMessage(MasterSecretUnion masterSecret,
PduHeaders headers,
PduBody body,
ContentValues contentValues)
private @Nullable String getDecryptedBody(@NonNull MasterSecret masterSecret,
@Nullable String body, long outboxType)
{
try {
if (!TextUtils.isEmpty(body) && Types.isSymmetricEncryption(outboxType)) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
return masterCipher.decryptBody(body);
} else {
return body;
}
} catch (InvalidMessageException e) {
Log.w(TAG, e);
}
return null;
}
private long insertMediaMessage(@NonNull MasterSecretUnion masterSecret,
@NonNull MmsAddresses addresses,
@Nullable String body,
@NonNull List<Attachment> attachments,
@NonNull ContentValues contentValues)
throws MmsException
{
SQLiteDatabase db = databaseHelper.getWritableDatabase();
PartDatabase partsDatabase = DatabaseFactory.getPartDatabase(context);
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)) ||
Types.isAsymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)))
{
String messageText = PartParser.getMessageText(body);
body = PartParser.getSupportedMediaParts(body);
if (!TextUtils.isEmpty(messageText)) {
contentValues.put(BODY, getEncryptedBody(masterSecret, messageText));
if (!TextUtils.isEmpty(body)) {
contentValues.put(BODY, getEncryptedBody(masterSecret, body));
}
}
contentValues.put(PART_COUNT, PartParser.getSupportedMediaPartCount(body));
contentValues.put(PART_COUNT, attachments.size());
db.beginTransaction();
try {
long messageId = db.insert(TABLE_NAME, null, contentValues);
addressDatabase.insertAddressesForId(messageId, headers);
partsDatabase.insertParts(masterSecret, messageId, body);
addressDatabase.insertAddressesForId(messageId, addresses);
partsDatabase.insertAttachmentsForMessage(masterSecret, messageId, attachments);
notifyConversationListeners(contentValues.getAsLong(THREAD_ID));
DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID));
@ -817,10 +802,10 @@ public class MmsDatabase extends MessagingDatabase {
}
public boolean delete(long messageId) {
long threadId = getThreadIdForMessage(messageId);
MmsAddressDatabase addrDatabase = DatabaseFactory.getMmsAddressDatabase(context);
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
partDatabase.deleteParts(messageId);
long threadId = getThreadIdForMessage(messageId);
MmsAddressDatabase addrDatabase = DatabaseFactory.getMmsAddressDatabase(context);
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
attachmentDatabase.deleteAttachmentsForMessage(messageId);
addrDatabase.deleteAddressesForId(messageId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@ -831,7 +816,7 @@ public class MmsDatabase extends MessagingDatabase {
}
public void deleteThread(long threadId) {
Set<Long> singleThreadSet = new HashSet<Long>();
Set<Long> singleThreadSet = new HashSet<>();
singleThreadSet.add(threadId);
deleteThreads(singleThreadSet);
}
@ -889,7 +874,7 @@ public class MmsDatabase extends MessagingDatabase {
public void deleteAllThreads() {
DatabaseFactory.getPartDatabase(context).deleteAllParts();
DatabaseFactory.getAttachmentDatabase(context).deleteAllAttachments();
DatabaseFactory.getMmsAddressDatabase(context).deleteAllAddresses();
SQLiteDatabase database = databaseHelper.getWritableDatabase();
@ -911,72 +896,6 @@ public class MmsDatabase extends MessagingDatabase {
}
}
private PduHeaders getHeadersFromCursor(Cursor cursor) throws InvalidHeaderValueException {
PduHeaders headers = new PduHeaders();
PduHeadersBuilder phb = new PduHeadersBuilder(headers, cursor);
phb.add(RETRIEVE_TEXT, RETRIEVE_TEXT_CS, PduHeaders.RETRIEVE_TEXT);
phb.add(SUBJECT, SUBJECT_CHARSET, PduHeaders.SUBJECT);
phb.addText(CONTENT_LOCATION, PduHeaders.CONTENT_LOCATION);
phb.addText(CONTENT_TYPE, PduHeaders.CONTENT_TYPE);
phb.addText(MESSAGE_CLASS, PduHeaders.MESSAGE_CLASS);
phb.addText(MESSAGE_ID, PduHeaders.MESSAGE_ID);
phb.addText(RESPONSE_TEXT, PduHeaders.RESPONSE_TEXT);
phb.addText(TRANSACTION_ID, PduHeaders.TRANSACTION_ID);
phb.addOctet(CONTENT_CLASS, PduHeaders.CONTENT_CLASS);
phb.addOctet(DELIVERY_REPORT, PduHeaders.DELIVERY_REPORT);
phb.addOctet(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE);
phb.addOctet(MMS_VERSION, PduHeaders.MMS_VERSION);
phb.addOctet(PRIORITY, PduHeaders.PRIORITY);
phb.addOctet(READ_STATUS, PduHeaders.READ_STATUS);
phb.addOctet(REPORT_ALLOWED, PduHeaders.REPORT_ALLOWED);
phb.addOctet(RETRIEVE_STATUS, PduHeaders.RETRIEVE_STATUS);
phb.addOctet(STATUS, PduHeaders.STATUS);
phb.addLong(NORMALIZED_DATE_SENT, PduHeaders.DATE);
phb.addLong(DELIVERY_TIME, PduHeaders.DELIVERY_TIME);
phb.addLong(EXPIRY, PduHeaders.EXPIRY);
phb.addLong(MESSAGE_SIZE, PduHeaders.MESSAGE_SIZE);
headers.setLongInteger(headers.getLongInteger(PduHeaders.DATE) / 1000L, PduHeaders.DATE);
return headers;
}
private ContentValues getContentValuesFromHeader(PduHeaders headers) {
ContentValues contentValues = new ContentValues();
ContentValuesBuilder cvb = new ContentValuesBuilder(contentValues);
cvb.add(RETRIEVE_TEXT, RETRIEVE_TEXT_CS, headers.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT));
cvb.add(SUBJECT, SUBJECT_CHARSET, headers.getEncodedStringValue(PduHeaders.SUBJECT));
cvb.add(CONTENT_LOCATION, headers.getTextString(PduHeaders.CONTENT_LOCATION));
cvb.add(CONTENT_TYPE, headers.getTextString(PduHeaders.CONTENT_TYPE));
cvb.add(MESSAGE_CLASS, headers.getTextString(PduHeaders.MESSAGE_CLASS));
cvb.add(MESSAGE_ID, headers.getTextString(PduHeaders.MESSAGE_ID));
cvb.add(RESPONSE_TEXT, headers.getTextString(PduHeaders.RESPONSE_TEXT));
cvb.add(TRANSACTION_ID, headers.getTextString(PduHeaders.TRANSACTION_ID));
cvb.add(CONTENT_CLASS, headers.getOctet(PduHeaders.CONTENT_CLASS));
cvb.add(DELIVERY_REPORT, headers.getOctet(PduHeaders.DELIVERY_REPORT));
cvb.add(MESSAGE_TYPE, headers.getOctet(PduHeaders.MESSAGE_TYPE));
cvb.add(MMS_VERSION, headers.getOctet(PduHeaders.MMS_VERSION));
cvb.add(PRIORITY, headers.getOctet(PduHeaders.PRIORITY));
cvb.add(READ_REPORT, headers.getOctet(PduHeaders.READ_REPORT));
cvb.add(READ_STATUS, headers.getOctet(PduHeaders.READ_STATUS));
cvb.add(REPORT_ALLOWED, headers.getOctet(PduHeaders.REPORT_ALLOWED));
cvb.add(RETRIEVE_STATUS, headers.getOctet(PduHeaders.RETRIEVE_STATUS));
cvb.add(STATUS, headers.getOctet(PduHeaders.STATUS));
cvb.add(DATE_SENT, headers.getLongInteger(PduHeaders.DATE) * 1000L);
cvb.add(DELIVERY_TIME, headers.getLongInteger(PduHeaders.DELIVERY_TIME));
cvb.add(EXPIRY, headers.getLongInteger(PduHeaders.EXPIRY));
cvb.add(MESSAGE_SIZE, headers.getLongInteger(PduHeaders.MESSAGE_SIZE));
if (headers.getEncodedStringValue(PduHeaders.FROM) != null)
cvb.add(ADDRESS, headers.getEncodedStringValue(PduHeaders.FROM).getTextString());
else
cvb.add(ADDRESS, null);
return cvb.getContentValues();
}
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
return new Reader(masterSecret, cursor);
}
@ -1171,11 +1090,16 @@ public class MmsDatabase extends MessagingDatabase {
Callable<SlideDeck> task = new Callable<SlideDeck>() {
@Override
public SlideDeck call() throws Exception {
PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
PduBody body = getPartsAsBody(partDatabase.getParts(id));
SlideDeck slideDeck = new SlideDeck(context, body);
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(id));
SlideDeck slideDeck = new SlideDeck(context, attachments);
boolean progress = false;
if (!body.containsPushInProgress()) {
for (Attachment attachment : attachments) {
if (attachment.isInProgress()) progress = true;
}
if (!progress) {
slideCache.put(timestamp + "::" + id, new SoftReference<>(slideDeck));
}
@ -1183,7 +1107,7 @@ public class MmsDatabase extends MessagingDatabase {
}
};
future = new ListenableFutureTask<>(task);
future = new ListenableFutureTask<>(task, timestamp + "::" + id);
slideResolver.execute(future);
return future;
@ -1222,15 +1146,4 @@ public class MmsDatabase extends MessagingDatabase {
final long time = System.currentTimeMillis();
return time - (time % 1000);
}
private PduBody getPartsAsBody(List<PduPart> parts) {
PduBody body = new PduBody();
for (PduPart part : parts) {
body.addPart(part);
}
return body;
}
}

Wyświetl plik

@ -1,713 +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 android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirement;
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirementProvider;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VisibleForTesting;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import de.greenrobot.event.EventBus;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
public class PartDatabase extends Database {
private static final String TAG = PartDatabase.class.getSimpleName();
private static final String TABLE_NAME = "part";
private static final String ROW_ID = "_id";
private static final String MMS_ID = "mid";
private static final String SEQUENCE = "seq";
private static final String CONTENT_TYPE = "ct";
private static final String NAME = "name";
private static final String CHARSET = "chset";
private static final String CONTENT_DISPOSITION = "cd";
private static final String FILENAME = "fn";
private static final String CONTENT_ID = "cid";
private static final String CONTENT_LOCATION = "cl";
private static final String CONTENT_TYPE_START = "ctt_s";
private static final String CONTENT_TYPE_TYPE = "ctt_t";
private static final String ENCRYPTED = "encrypted";
private static final String DATA = "_data";
private static final String TRANSFER_STATE = "pending_push";
private static final String SIZE = "data_size";
private static final String THUMBNAIL = "thumbnail";
private static final String ASPECT_RATIO = "aspect_ratio";
private static final String UNIQUE_ID = "unique_id";
public static final int TRANSFER_PROGRESS_DONE = 0;
public static final int TRANSFER_PROGRESS_STARTED = 1;
public static final int TRANSFER_PROGRESS_AUTO_PENDING = 2;
public static final int TRANSFER_PROGRESS_FAILED = 3;
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " +
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " +
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " +
TRANSFER_STATE + " INTEGER, "+ DATA + " TEXT, " + SIZE + " INTEGER, " +
THUMBNAIL + " TEXT, " + ASPECT_RATIO + " REAL, " + UNIQUE_ID + " INTEGER NOT NULL);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
"CREATE INDEX IF NOT EXISTS pending_push_index ON " + TABLE_NAME + " (" + TRANSFER_STATE + ");",
};
private final static String IMAGES_QUERY = "SELECT " + TABLE_NAME + "." + ROW_ID + ", "
+ TABLE_NAME + "." + CONTENT_TYPE + ", "
+ TABLE_NAME + "." + ASPECT_RATIO + ", "
+ TABLE_NAME + "." + UNIQUE_ID + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.NORMALIZED_DATE_RECEIVED + ", "
+ MmsDatabase.TABLE_NAME + "." + MmsDatabase.ADDRESS + " "
+ "FROM " + TABLE_NAME + " LEFT JOIN " + MmsDatabase.TABLE_NAME
+ " ON " + TABLE_NAME + "." + MMS_ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " "
+ "WHERE " + MMS_ID + " IN (SELECT " + MmsSmsColumns.ID
+ " FROM " + MmsDatabase.TABLE_NAME
+ " WHERE " + MmsDatabase.THREAD_ID + " = ?) AND "
+ CONTENT_TYPE + " LIKE 'image/%' "
+ "ORDER BY " + TABLE_NAME + "." + ROW_ID + " DESC";
private final ExecutorService thumbnailExecutor = Util.newSingleThreadedLifoExecutor();
public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public InputStream getPartStream(MasterSecret masterSecret, PartId partId)
throws FileNotFoundException
{
return getDataStream(masterSecret, partId, DATA);
}
public void updateFailedDownloadedPart(long messageId, PartId partId, PduPart part)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
part.setContentDisposition(new byte[0]);
part.setTransferProgress(TRANSFER_PROGRESS_FAILED);
ContentValues values = getContentValuesForPart(part);
values.put(DATA, (String)null);
database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
public PduPart getPart(PartId partId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, PART_ID_WHERE, partId.toStrings(), null, null, null);
if (cursor != null && cursor.moveToFirst()) return getPart(cursor);
else return null;
} finally {
if (cursor != null)
cursor.close();
}
}
public Cursor getImagesForThread(long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.rawQuery(IMAGES_QUERY, new String[]{threadId+""});
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public List<PduPart> getParts(long mmsId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
List<PduPart> results = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""},
null, null, null);
while (cursor != null && cursor.moveToNext()) {
results.add(getPart(cursor));
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteParts(long mmsId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL}, MMS_ID + " = ?",
new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
String data = cursor.getString(0);
String thumbnail = cursor.getString(1);
if (!TextUtils.isEmpty(data)) {
new File(data).delete();
}
if (!TextUtils.isEmpty(thumbnail)) {
new File(thumbnail).delete();
}
}
} finally {
if (cursor != null)
cursor.close();
}
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId + ""});
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void deleteAllParts() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File[] parts = partsDirectory.listFiles();
for (File part : parts) {
part.delete();
}
}
void insertParts(MasterSecretUnion masterSecret, long mmsId, PduBody body) throws MmsException {
Log.w(TAG, "insertParts(" + body.getPartsNum() + ")");
for (int i=0;i<body.getPartsNum();i++) {
PduPart part = body.getPart(i);
PartId partId = insertPart(masterSecret, part, mmsId, part.getThumbnail());
Log.w(TAG, "Inserted part at ID: " + partId);
}
}
private void getPartValues(PduPart part, Cursor cursor) {
part.setRowId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)));
part.setUniqueId(cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID)));
part.setMmsId(cursor.getLong(cursor.getColumnIndexOrThrow(MMS_ID)));
int charsetColumn = cursor.getColumnIndexOrThrow(CHARSET);
if (!cursor.isNull(charsetColumn))
part.setCharset(cursor.getInt(charsetColumn));
int contentTypeColumn = cursor.getColumnIndexOrThrow(CONTENT_TYPE);
if (!cursor.isNull(contentTypeColumn))
part.setContentType(Util.toIsoBytes(cursor.getString(contentTypeColumn)));
int nameColumn = cursor.getColumnIndexOrThrow(NAME);
if (!cursor.isNull(nameColumn))
part.setName(Util.toIsoBytes(cursor.getString(nameColumn)));
int fileNameColumn = cursor.getColumnIndexOrThrow(FILENAME);
if (!cursor.isNull(fileNameColumn))
part.setFilename(Util.toIsoBytes(cursor.getString(fileNameColumn)));
int contentDispositionColumn = cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION);
if (!cursor.isNull(contentDispositionColumn))
part.setContentDisposition(Util.toIsoBytes(cursor.getString(contentDispositionColumn)));
int contentIdColumn = cursor.getColumnIndexOrThrow(CONTENT_ID);
if (!cursor.isNull(contentIdColumn))
part.setContentId(Util.toIsoBytes(cursor.getString(contentIdColumn)));
int contentLocationColumn = cursor.getColumnIndexOrThrow(CONTENT_LOCATION);
if (!cursor.isNull(contentLocationColumn))
part.setContentLocation(Util.toIsoBytes(cursor.getString(contentLocationColumn)));
int encryptedColumn = cursor.getColumnIndexOrThrow(ENCRYPTED);
if (!cursor.isNull(encryptedColumn))
part.setEncrypted(cursor.getInt(encryptedColumn) == 1);
int transferStateColumn = cursor.getColumnIndexOrThrow(TRANSFER_STATE);
if (!cursor.isNull(transferStateColumn))
part.setTransferProgress(cursor.getInt(transferStateColumn));
int sizeColumn = cursor.getColumnIndexOrThrow(SIZE);
if (!cursor.isNull(sizeColumn))
part.setDataSize(cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)));
int dataColumn = cursor.getColumnIndexOrThrow(DATA);
if (!cursor.isNull(dataColumn))
part.setDataUri(PartAuthority.getPartUri(part.getPartId()));
}
private ContentValues getContentValuesForPart(PduPart part) throws MmsException {
ContentValues contentValues = new ContentValues();
if (part.getCharset() != 0 ) {
contentValues.put(CHARSET, part.getCharset());
}
if (part.getContentType() != null) {
contentValues.put(CONTENT_TYPE, Util.toIsoString(part.getContentType()));
if (Util.toIsoString(part.getContentType()).equals(ContentType.APP_SMIL)) {
contentValues.put(SEQUENCE, -1);
}
} else {
throw new MmsException("There is no content type for this part.");
}
if (part.getName() != null) {
contentValues.put(NAME, new String(part.getName()));
}
if (part.getFilename() != null) {
contentValues.put(FILENAME, new String(part.getFilename()));
}
if (part.getContentDisposition() != null) {
contentValues.put(CONTENT_DISPOSITION, Util.toIsoString(part.getContentDisposition()));
}
if (part.getContentId() != null) {
contentValues.put(CONTENT_ID, Util.toIsoString(part.getContentId()));
}
if (part.getContentLocation() != null) {
contentValues.put(CONTENT_LOCATION, Util.toIsoString(part.getContentLocation()));
}
contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0);
contentValues.put(TRANSFER_STATE, part.getTransferProgress());
contentValues.put(UNIQUE_ID, part.getUniqueId());
return contentValues;
}
private InputStream getPartInputStream(MasterSecret masterSecret, File path)
throws FileNotFoundException
{
Log.w(TAG, "Getting part at: " + path.getAbsolutePath());
return new DecryptingPartInputStream(path, masterSecret);
}
protected OutputStream getPartOutputStream(MasterSecret masterSecret, File path, PduPart part)
throws FileNotFoundException
{
Log.w(TAG, "Writing part to: " + path.getAbsolutePath());
part.setEncrypted(true);
return new EncryptingPartOutputStream(path, masterSecret);
}
@VisibleForTesting InputStream getDataStream(MasterSecret masterSecret, PartId partId, String dataType)
throws FileNotFoundException
{
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[]{dataType}, PART_ID_WHERE, partId.toStrings(),
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
if (cursor.isNull(0)) {
return null;
}
return getPartInputStream(masterSecret, new File(cursor.getString(0)));
} else {
throw new FileNotFoundException("No part for id: " + partId);
}
} finally {
if (cursor != null)
cursor.close();
}
}
private Pair<File, Long> writePartData(MasterSecret masterSecret, PduPart part, InputStream in)
throws MmsException
{
try {
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File dataFile = File.createTempFile("part", ".mms", partsDirectory);
OutputStream out = getPartOutputStream(masterSecret, dataFile, part);
long plaintextLength = Util.copy(in, out);
return new Pair<>(dataFile, plaintextLength);
} catch (IOException e) {
throw new MmsException(e);
}
}
private Pair<File, Long> writePartData(MasterSecret masterSecret, PduPart part)
throws MmsException
{
try {
if (part.getData() != null) {
Log.w(TAG, "Writing part data from buffer");
return writePartData(masterSecret, part, new ByteArrayInputStream(part.getData()));
} else if (part.getDataUri() != null) {
Log.w(TAG, "Writing part data from URI");
InputStream in = PartAuthority.getPartStream(context, masterSecret, part.getDataUri());
return writePartData(masterSecret, part, in);
} else {
throw new MmsException("Part is empty!");
}
} catch (IOException e) {
throw new MmsException(e);
}
}
public InputStream getThumbnailStream(MasterSecret masterSecret, PartId partId) throws IOException {
Log.w(TAG, "getThumbnailStream(" + partId + ")");
final InputStream dataStream = getDataStream(masterSecret, partId, THUMBNAIL);
if (dataStream != null) {
return dataStream;
}
try {
return thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId)).get();
} catch (InterruptedException ie) {
throw new AssertionError("interrupted");
} catch (ExecutionException ee) {
Log.w(TAG, ee);
throw new IOException(ee);
}
}
private PduPart getPart(Cursor cursor) {
PduPart part = new PduPart();
getPartValues(part, cursor);
return part;
}
public @NonNull List<PduPart> getPendingParts() {
final SQLiteDatabase database = databaseHelper.getReadableDatabase();
final List<PduPart> parts = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, TRANSFER_STATE + " = ?", new String[] {String.valueOf(TRANSFER_PROGRESS_STARTED)}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
parts.add(getPart(cursor));
}
} finally {
if (cursor != null) cursor.close();
}
return parts;
}
private PartId insertPart(MasterSecretUnion masterSecret, PduPart part, long mmsId, Bitmap thumbnail) throws MmsException {
Log.w(TAG, "inserting part to mms " + mmsId);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = null;
if ((part.getData() != null || part.getDataUri() != null) && masterSecret.getMasterSecret().isPresent()) {
partData = writePartData(masterSecret.getMasterSecret().get(), part);
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
}
ContentValues contentValues = getContentValuesForPart(part);
contentValues.put(MMS_ID, mmsId);
if (partData != null) {
contentValues.put(DATA, partData.first.getAbsolutePath());
contentValues.put(SIZE, partData.second);
}
long partRowId = database.insert(TABLE_NAME, null, contentValues);
PartId partId = new PartId(partRowId, part.getUniqueId());
if (thumbnail != null && masterSecret.getMasterSecret().isPresent()) {
Log.w(TAG, "inserting pre-generated thumbnail");
ThumbnailData data = new ThumbnailData(thumbnail);
updatePartThumbnail(masterSecret.getMasterSecret().get(), partId, part, data.toDataStream(), data.getAspectRatio());
} else if (!part.isInProgress()) {
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), partId));
}
return partId;
}
public void updateDownloadedPart(MasterSecret masterSecret, long messageId,
PartId partId, PduPart part, InputStream data)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = writePartData(masterSecret, part, data);
part.setContentDisposition(new byte[0]);
part.setTransferProgress(TRANSFER_PROGRESS_DONE);
ContentValues values = getContentValuesForPart(part);
if (partData != null) {
values.put(DATA, partData.first.getAbsolutePath());
values.put(SIZE, partData.second);
}
database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings());
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId));
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
public void markPartUploaded(long messageId, PduPart part) {
ContentValues values = new ContentValues(1);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
part.setTransferProgress(TRANSFER_PROGRESS_DONE);
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
}
public void setTransferState(long messageId, @NonNull PartId partId, int transferState) {
final ContentValues values = new ContentValues(1);
final SQLiteDatabase database = databaseHelper.getWritableDatabase();
values.put(TRANSFER_STATE, transferState);
database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings());
notifyConversationListeners(DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId));
ApplicationContext.getInstance(context).notifyMediaControlEvent();
}
public void updatePartData(MasterSecret masterSecret, PduPart part, InputStream data)
throws MmsException
{
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Pair<File, Long> partData = writePartData(masterSecret, part, data);
if (partData == null) throw new MmsException("couldn't update part data");
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[]{DATA}, PART_ID_WHERE,
part.getPartId().toStrings(), null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int dataColumn = cursor.getColumnIndexOrThrow(DATA);
if (!cursor.isNull(dataColumn) && !new File(cursor.getString(dataColumn)).delete()) {
Log.w(TAG, "Couldn't delete old part file");
}
}
} finally {
if (cursor != null) cursor.close();
}
ContentValues values = new ContentValues(2);
values.put(DATA, partData.first.getAbsolutePath());
values.put(SIZE, partData.second);
part.setDataSize(partData.second);
database.update(TABLE_NAME, values, PART_ID_WHERE, part.getPartId().toStrings());
Log.w(TAG, "updated data for part #" + part.getPartId());
}
public void updatePartThumbnail(MasterSecret masterSecret, PartId partId, PduPart part, InputStream in, float aspectRatio)
throws MmsException
{
Log.w(TAG, "updating part thumbnail for #" + partId);
Pair<File, Long> thumbnailFile = writePartData(masterSecret, part, in);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues(2);
values.put(THUMBNAIL, thumbnailFile.first.getAbsolutePath());
values.put(ASPECT_RATIO, aspectRatio);
database.update(TABLE_NAME, values, PART_ID_WHERE, partId.toStrings());
}
public static class ImageRecord {
private PartId partId;
private String contentType;
private String address;
private long date;
private ImageRecord(PartId partId, String contentType, String address, long date) {
this.partId = partId;
this.contentType = contentType;
this.address = address;
this.date = date;
}
public static ImageRecord from(Cursor cursor) {
PartId partId = new PartId(cursor.getLong(cursor.getColumnIndexOrThrow(ROW_ID)),
cursor.getLong(cursor.getColumnIndexOrThrow(UNIQUE_ID)));
return new ImageRecord(partId,
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_TYPE)),
cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS)),
cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.NORMALIZED_DATE_RECEIVED)));
}
public PartId getPartId() {
return partId;
}
public String getContentType() {
return contentType;
}
public String getAddress() {
return address;
}
public long getDate() {
return date;
}
public Uri getUri() {
return PartAuthority.getPartUri(partId);
}
}
@VisibleForTesting class ThumbnailFetchCallable implements Callable<InputStream> {
private final MasterSecret masterSecret;
private final PartId partId;
public ThumbnailFetchCallable(MasterSecret masterSecret, PartId partId) {
this.masterSecret = masterSecret;
this.partId = partId;
}
@Override
public @Nullable InputStream call() throws Exception {
final InputStream stream = getDataStream(masterSecret, partId, THUMBNAIL);
if (stream != null) {
return stream;
}
PduPart part = getPart(partId);
if (part.isInProgress()) {
return null;
}
ThumbnailData data = MediaUtil.generateThumbnail(context, masterSecret, part.getDataUri(), Util.toIsoString(part.getContentType()));
if (data == null) {
return null;
}
updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio());
return getDataStream(masterSecret, partId, THUMBNAIL);
}
}
public static class PartId {
private final long rowId;
private final long uniqueId;
public PartId(long rowId, long uniqueId) {
this.rowId = rowId;
this.uniqueId = uniqueId;
}
public long getRowId() {
return rowId;
}
public long getUniqueId() {
return uniqueId;
}
public String[] toStrings() {
return new String[] {String.valueOf(rowId), String.valueOf(uniqueId)};
}
public String toString() {
return "(row id: " + rowId + ", unique ID: " + uniqueId + ")";
}
public boolean isValid() {
return rowId >= 0 && uniqueId >= 0;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PartId partId = (PartId)o;
if (rowId != partId.rowId) return false;
return uniqueId == partId.uniqueId;
}
@Override public int hashCode() {
return Util.hashCode(rowId, uniqueId);
}
}
}

Wyświetl plik

@ -1,20 +1,21 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.MediaKey;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.jobs.requirements.MediaNetworkRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VisibleForTesting;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -33,7 +34,6 @@ import javax.inject.Inject;
import de.greenrobot.event.EventBus;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduPart;
public class AttachmentDownloadJob extends MasterSecretJob implements InjectableType {
private static final long serialVersionUID = 1L;
@ -45,18 +45,18 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
private final long partRowId;
private final long partUniqueId;
public AttachmentDownloadJob(Context context, long messageId, PartId partId) {
public AttachmentDownloadJob(Context context, long messageId, AttachmentId attachmentId) {
super(context, JobParameters.newBuilder()
.withGroupId(AttachmentDownloadJob.class.getCanonicalName())
.withRequirement(new MasterSecretRequirement(context))
.withRequirement(new NetworkRequirement(context))
.withRequirement(new MediaNetworkRequirement(context, messageId, partId))
.withRequirement(new MediaNetworkRequirement(context, messageId, attachmentId))
.withPersistence()
.create());
this.messageId = messageId;
this.partRowId = partId.getRowId();
this.partUniqueId = partId.getUniqueId();
this.partRowId = attachmentId.getRowId();
this.partUniqueId = attachmentId.getUniqueId();
}
@Override
@ -65,29 +65,29 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
@Override
public void onRun(MasterSecret masterSecret) throws IOException {
final PartId partId = new PartId(partRowId, partUniqueId);
final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId);
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final Attachment attachment = DatabaseFactory.getAttachmentDatabase(context).getAttachment(attachmentId);
if (part == null) {
Log.w(TAG, "part no longer exists.");
return;
}
if (part.getDataUri() != null) {
Log.w(TAG, "part was already downloaded.");
if (attachment == null) {
Log.w(TAG, "attachment no longer exists.");
return;
}
Log.w(TAG, "Downloading push part " + partId);
if (!attachment.isInProgress()) {
Log.w(TAG, "Attachment was already downloaded.");
return;
}
retrievePart(masterSecret, part, messageId);
Log.w(TAG, "Downloading push part " + attachmentId);
retrieveAttachment(masterSecret, messageId, attachmentId, attachment);
MessageNotifier.updateNotification(context, masterSecret);
}
@Override
public void onCanceled() {
final PartId partId = new PartId(partRowId, partUniqueId);
final PduPart part = DatabaseFactory.getPartDatabase(context).getPart(partId);
markFailed(messageId, part, part.getPartId());
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
markFailed(messageId, attachmentId);
}
@Override
@ -95,28 +95,31 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
return (exception instanceof PushNetworkException);
}
private void retrievePart(MasterSecret masterSecret, PduPart part, long messageId)
private void retrieveAttachment(MasterSecret masterSecret,
long messageId,
final AttachmentId attachmentId,
final Attachment attachment)
throws IOException
{
PartDatabase database = DatabaseFactory.getPartDatabase(context);
File attachmentFile = null;
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
File attachmentFile = null;
final PartId partId = part.getPartId();
try {
attachmentFile = createTempFile();
TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, part);
InputStream attachment = messageReceiver.retrieveAttachment(pointer, attachmentFile, new ProgressListener() {
@Override public void onAttachmentProgress(long total, long progress) {
EventBus.getDefault().postSticky(new PartProgressEvent(partId, total, progress));
TextSecureAttachmentPointer pointer = createAttachmentPointer(masterSecret, attachment);
InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, new ProgressListener() {
@Override
public void onAttachmentProgress(long total, long progress) {
EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress));
}
});
database.updateDownloadedPart(masterSecret, messageId, partId, part, attachment);
database.insertAttachmentsForPlaceholder(masterSecret, messageId, attachmentId, stream);
} catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) {
Log.w(TAG, e);
markFailed(messageId, part, partId);
markFailed(messageId, attachmentId);
} finally {
if (attachmentFile != null)
attachmentFile.delete();
@ -124,24 +127,25 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
}
@VisibleForTesting
TextSecureAttachmentPointer createAttachmentPointer(MasterSecret masterSecret, PduPart part)
TextSecureAttachmentPointer createAttachmentPointer(MasterSecret masterSecret, Attachment attachment)
throws InvalidPartException
{
if (part.getContentLocation() == null || part.getContentLocation().length == 0) {
if (TextUtils.isEmpty(attachment.getLocation())) {
throw new InvalidPartException("empty content id");
}
if (part.getContentDisposition() == null || part.getContentDisposition().length == 0) {
if (TextUtils.isEmpty(attachment.getKey())) {
throw new InvalidPartException("empty encrypted key");
}
try {
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
long id = Long.parseLong(Util.toIsoString(part.getContentLocation()));
byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, Util.toIsoString(part.getContentDisposition()));
long id = Long.parseLong(attachment.getLocation());
byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, attachment.getKey());
String relay = null;
if (part.getName() != null) {
relay = Util.toIsoString(part.getName());
if (TextUtils.isEmpty(attachment.getRelay())) {
relay = attachment.getRelay();
}
return new TextSecureAttachmentPointer(id, null, key, relay);
@ -162,10 +166,10 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
}
}
private void markFailed(long messageId, PduPart part, PartDatabase.PartId partId) {
private void markFailed(long messageId, AttachmentId attachmentId) {
try {
PartDatabase database = DatabaseFactory.getPartDatabase(context);
database.updateFailedDownloadedPart(messageId, partId, part);
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
database.setTransferProgressFailed(attachmentId, messageId);
} catch (MmsException e) {
Log.w(TAG, e);
}

Wyświetl plik

@ -5,17 +5,22 @@ import android.net.Uri;
import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsRadioException;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libaxolotl.DuplicateMessageException;
@ -25,10 +30,15 @@ import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.RetrieveConf;
public class MmsDownloadJob extends MasterSecretJob {
@ -63,8 +73,6 @@ public class MmsDownloadJob extends MasterSecretJob {
@Override
public void onRun(MasterSecret masterSecret) {
Log.w(TAG, "onRun()");
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Optional<NotificationInd> notification = database.getNotification(messageId);
@ -140,13 +148,52 @@ public class MmsDownloadJob extends MasterSecretJob {
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
LegacyMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
IncomingMediaMessage message = new IncomingMediaMessage(retrieved);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SingleUseBlobProvider provider = SingleUseBlobProvider.getInstance();
String from = null;
List<String> to = new LinkedList<>();
List<String> cc = new LinkedList<>();
String body = null;
List<Attachment> attachments = new LinkedList<>();
if (retrieved.getFrom() != null) {
from = Util.toIsoString(retrieved.getFrom().getTextString());
}
if (retrieved.getTo() != null) {
for (EncodedStringValue toValue : retrieved.getTo()) {
to.add(Util.toIsoString(toValue.getTextString()));
}
}
if (retrieved.getCc() != null) {
for (EncodedStringValue ccValue : retrieved.getCc()) {
cc.add(Util.toIsoString(ccValue.getTextString()));
}
}
if (retrieved.getBody() != null) {
for (int i=0;i<retrieved.getBody().getPartsNum();i++) {
PduPart part = retrieved.getBody().getPart(i);
if (Util.toIsoString(part.getContentType()).equals(ContentType.TEXT_PLAIN)) {
body = Util.toIsoString(part.getData());
} else if (part.getData() != null) {
Uri uri = provider.createUri(part.getData());
attachments.add(new UriAttachment(uri, Util.toIsoString(part.getContentType()),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
part.getData().length));
}
}
}
IncomingMediaMessage message = new IncomingMediaMessage(from, to, cc, body, retrieved.getDate() * 1000L, attachments);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
message, contentLocation, threadId);
database.delete(messageId);
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}

Wyświetl plik

@ -1,23 +1,23 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.ThreadDatabase.DistributionTypes;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingLegacyMmsConnection;
import org.thoughtcrime.securesms.mms.OutgoingLollipopMmsConnection;
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
@ -25,20 +25,29 @@ import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduComposer;
import ws.com.google.android.mms.pdu.PduHeaders;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.SendConf;
import ws.com.google.android.mms.pdu.SendReq;
public class MmsSendJob extends SendJob {
private static final long serialVersionUID = 0L;
private static final String TAG = MmsSendJob.class.getSimpleName();
private final long messageId;
@ -62,18 +71,20 @@ public class MmsSendJob extends SendJob {
@Override
public void onSend(MasterSecret masterSecret) throws MmsException, NoSuchMessageException, IOException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
try {
validateDestinations(message);
SendReq pdu = constructSendPdu(masterSecret, message);
final byte[] pduBytes = getPduBytes(masterSecret, message);
validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(pdu);
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes);
final MmsSendResult result = getSendResult(sendConf, message);
final MmsSendResult result = getSendResult(sendConf, pdu);
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
markPartsUploaded(messageId, message.getBody());
database.markAsSent(messageId);
markAttachmentsUploaded(messageId, message.getAttachments());
} catch (UndeliverableMessageException | IOException e) {
Log.w(TAG, e);
database.markAsSentFailed(messageId);
@ -96,21 +107,19 @@ public class MmsSendJob extends SendJob {
notifyMediaMessageDeliveryFailed(context, messageId);
}
private byte[] getPduBytes(MasterSecret masterSecret, SendReq message)
private byte[] getPduBytes(SendReq message)
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{
String number = TelephonyUtil.getManager(context).getLine1Number();
message = getResolvedMessage(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
message.setBody(SmilUtil.getSmilBody(message.getBody()));
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
}
if (number != null && number.trim().length() != 0) {
if (!TextUtils.isEmpty(number)) {
message.setFrom(new EncodedStringValue(number));
}
byte[] pduBytes = new PduComposer(context, message).make();
if (pduBytes == null) {
throw new UndeliverableMessageException("PDU composition failed, null payload");
}
@ -149,7 +158,7 @@ public class MmsSendJob extends SendJob {
}
}
private void validateDestinations(SendReq message) throws UndeliverableMessageException {
private void validateDestinations(OutgoingMediaMessage media, SendReq message) throws UndeliverableMessageException {
validateDestinations(message.getTo());
validateDestinations(message.getCc());
validateDestinations(message.getBcc());
@ -157,6 +166,59 @@ public class MmsSendJob extends SendJob {
if (message.getTo() == null && message.getCc() == null && message.getBcc() == null) {
throw new UndeliverableMessageException("No to, cc, or bcc specified!");
}
if (media.isSecure()) {
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
}
}
private SendReq constructSendPdu(MasterSecret masterSecret, OutgoingMediaMessage message)
throws UndeliverableMessageException
{
SendReq sendReq = new SendReq();
PduBody body = new PduBody();
for (Recipient recipient : message.getRecipients()) {
if (message.getDistributionType() == DistributionTypes.CONVERSATION) {
sendReq.addTo(new EncodedStringValue(Util.toIsoBytes(recipient.getNumber())));
} else {
sendReq.addBcc(new EncodedStringValue(Util.toIsoBytes(recipient.getNumber())));
}
}
sendReq.setDate(message.getSentTimeMillis() / 1000L);
if (!TextUtils.isEmpty(message.getBody())) {
PduPart part = new PduPart();
part.setData(Util.toUtf8Bytes(message.getBody()));
part.setCharset(CharacterSets.UTF_8);
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setName(("Text"+System.currentTimeMillis()).getBytes());
body.addPart(part);
}
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, MediaConstraints.MMS_CONSTRAINTS, message.getAttachments());
for (Attachment attachment : scaledAttachments) {
try {
if (attachment.getDataUri() == null) throw new IOException("Assertion failed, attachment for outgoing MMS has no data!");
PduPart part = new PduPart();
part.setData(Util.readFully(PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri())));
part.setContentType(Util.toIsoBytes(attachment.getContentType()));
part.setContentId((System.currentTimeMillis() + "").getBytes());
part.setName((System.currentTimeMillis() + "").getBytes());
body.addPart(part);
} catch (IOException e) {
Log.w(TAG, e);
}
}
sendReq.setBody(body);
return sendReq;
}
private void notifyMediaMessageDeliveryFailed(Context context, long messageId) {

Wyświetl plik

@ -1,15 +1,19 @@
package org.thoughtcrime.securesms.jobs;
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.attachments.Attachment;
public class PartProgressEvent {
public PartId partId;
public long total;
public long progress;
public PartProgressEvent(PartId partId, long total, long progress) {
this.partId = partId;
this.total = total;
this.progress = progress;
public final Attachment attachment;
public final long total;
public final long progress;
public PartProgressEvent(@NonNull Attachment attachment, long total, long progress) {
this.attachment = attachment;
this.total = total;
this.progress = progress;
}
}

Wyświetl plik

@ -6,6 +6,8 @@ import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.attachments.PointerAttachment;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
@ -18,6 +20,7 @@ import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
@ -62,7 +65,6 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduPart;
public class PushDecryptJob extends ContextJob {
@ -254,13 +256,14 @@ public class PushDecryptJob extends ContextJob {
message.getGroupInfo(),
message.getAttachments());
Pair<Long, Long> messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
Pair<Long, Long> messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, mediaMessage, -1);
List<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageAndThreadId.first);
List<PduPart> parts = DatabaseFactory.getPartDatabase(context).getParts(messageAndThreadId.first);
for (PduPart part : parts) {
for (DatabaseAttachment attachment : attachments) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, messageAndThreadId.first, part.getPartId()));
.add(new AttachmentDownloadJob(context, messageAndThreadId.first,
attachment.getAttachmentId()));
}
if (smsMessageId.isPresent()) {
@ -277,22 +280,22 @@ public class PushDecryptJob extends ContextJob {
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipients recipients = getSyncMessageDestination(message);
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(context, masterSecret, recipients,
message.getMessage().getAttachments().get(),
message.getMessage().getBody().orNull());
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()),
message.getTimestamp(), ThreadDatabase.DistributionTypes.DEFAULT);
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false, message.getTimestamp());
long messageId = database.insertMessageOutbox(masterSecret, mediaMessage, threadId, false);
database.markAsSent(messageId, "push".getBytes(), 0);
database.markAsSent(messageId);
database.markAsPush(messageId);
for (PduPart part : DatabaseFactory.getPartDatabase(context).getParts(messageId)) {
for (DatabaseAttachment attachment : DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId)) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, messageId, part.getPartId()));
.add(new AttachmentDownloadJob(context, messageId, attachment.getAttachmentId()));
}
if (smsMessageId.isPresent()) {

Wyświetl plik

@ -6,27 +6,25 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
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.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.textsecure.api.TextSecureMessageSender;
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
@ -40,7 +38,6 @@ import java.util.List;
import javax.inject.Inject;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.SendReq;
import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
@ -78,16 +75,16 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
public void onSend(MasterSecret masterSecret)
throws MmsException, IOException, NoSuchMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
try {
deliver(masterSecret, message, filterRecipientId);
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId, "push".getBytes(), 0);
markPartsUploaded(messageId, message.getBody());
database.markAsSent(messageId);
markAttachmentsUploaded(messageId, message.getAttachments());
} catch (InvalidNumberException | RecipientFormattingException | UndeliverableMessageException e) {
Log.w(TAG, e);
database.markAsSentFailed(messageId);
@ -101,11 +98,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
failures.add(new NetworkFailure(recipient.getRecipientId()));
}
// for (UnregisteredUserException uue : e.getUnregisteredUserExceptions()) {
// Recipient recipient = RecipientFactory.getRecipientsFromString(context, uue.getE164Number(), false).getPrimaryRecipient();
// failures.add(new NetworkFailure(recipient.getRecipientId(), NetworkFailure.UNREGISTERED_FAILURE));
// }
for (UntrustedIdentityException uie : e.getUntrustedIdentityExceptions()) {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false).getPrimaryRecipient();
database.addMismatchedIdentity(messageId, recipient.getRecipientId(), uie.getIdentityKey());
@ -130,39 +122,31 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
}
private void deliver(MasterSecret masterSecret, SendReq message, long filterRecipientId)
private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message, long filterRecipientId)
throws IOException, RecipientFormattingException, InvalidNumberException,
EncapsulatedExceptions, UndeliverableMessageException
{
message = getResolvedMessage(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
TextSecureMessageSender messageSender = messageSenderFactory.create();
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
byte[] groupId = GroupUtil.getDecodedId(message.getRecipients().getPrimaryRecipient().getNumber());
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
List<TextSecureAttachment> attachments = getAttachmentsFor(masterSecret, message.getAttachments());
List<TextSecureAddress> addresses;
if (filterRecipientId >= 0) addresses = getPushAddresses(filterRecipientId);
else addresses = getPushAddresses(recipients);
if (MmsSmsColumns.Types.isGroupUpdate(message.getDatabaseMessageBox()) ||
MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()))
{
String content = PartParser.getMessageText(message.getBody());
if (message.isGroup()) {
OutgoingGroupMediaMessage groupMessage = (OutgoingGroupMediaMessage) message;
GroupContext groupContext = groupMessage.getGroupContext();
TextSecureAttachment avatar = attachments.isEmpty() ? null : attachments.get(0);
TextSecureGroup.Type type = groupMessage.isGroupQuit() ? TextSecureGroup.Type.QUIT : TextSecureGroup.Type.UPDATE;
TextSecureGroup group = new TextSecureGroup(type, groupId, groupContext.getName(), groupContext.getMembersList(), avatar);
TextSecureDataMessage groupDataMessage = new TextSecureDataMessage(message.getSentTimeMillis(), group, null, null);
if (content != null && !content.trim().isEmpty()) {
GroupContext groupContext = GroupContext.parseFrom(Base64.decode(content));
TextSecureAttachment avatar = attachments.isEmpty() ? null : attachments.get(0);
TextSecureGroup.Type type = MmsSmsColumns.Types.isGroupQuit(message.getDatabaseMessageBox()) ? TextSecureGroup.Type.QUIT : TextSecureGroup.Type.UPDATE;
TextSecureGroup group = new TextSecureGroup(type, groupId, groupContext.getName(), groupContext.getMembersList(), avatar);
TextSecureDataMessage groupMessage = new TextSecureDataMessage(message.getSentTimestamp(), group, null, null);
messageSender.sendMessage(addresses, groupMessage);
}
messageSender.sendMessage(addresses, groupDataMessage);
} else {
String body = PartParser.getMessageText(message.getBody());
TextSecureGroup group = new TextSecureGroup(groupId);
TextSecureDataMessage groupMessage = new TextSecureDataMessage(message.getSentTimestamp(), group, attachments, body);
TextSecureDataMessage groupMessage = new TextSecureDataMessage(message.getSentTimeMillis(), group, attachments, message.getBody());
messageSender.sendMessage(addresses, groupMessage);
}

Wyświetl plik

@ -4,14 +4,14 @@ import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
@ -31,8 +31,6 @@ import java.util.List;
import javax.inject.Inject;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.SendReq;
import static org.thoughtcrime.securesms.dependencies.TextSecureCommunicationModule.TextSecureMessageSenderFactory;
@ -63,15 +61,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
throws RetryLaterException, MmsException, NoSuchMessageException,
UndeliverableMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
OutgoingMediaMessage message = database.getOutgoingMessage(masterSecret, messageId);
try {
deliver(masterSecret, message);
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId, "push".getBytes(), 0);
markPartsUploaded(messageId, message.getBody());
database.markAsSent(messageId);
markAttachmentsUploaded(messageId, message.getAttachments());
} catch (InsecureFallbackApprovalException ifae) {
Log.w(TAG, ifae);
database.markAsPendingInsecureSmsFallback(messageId);
@ -100,24 +98,21 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
notifyMediaMessageDeliveryFailed(context, messageId);
}
private void deliver(MasterSecret masterSecret, SendReq message)
private void deliver(MasterSecret masterSecret, OutgoingMediaMessage message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
UndeliverableMessageException
{
TextSecureMessageSender messageSender = messageSenderFactory.create();
String destination = message.getTo()[0].getString();
try {
message = getResolvedMessage(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
TextSecureAddress address = getPushAddress(destination);
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
String body = PartParser.getMessageText(message.getBody());
TextSecureDataMessage mediaMessage = TextSecureDataMessage.newBuilder()
.withBody(body)
.withAttachments(attachments)
.withTimestamp(message.getSentTimestamp())
.build();
TextSecureAddress address = getPushAddress(message.getRecipients().getPrimaryRecipient().getNumber());
List<Attachment> scaledAttachments = scaleAttachments(masterSecret, MediaConstraints.PUSH_CONSTRAINTS, message.getAttachments());
List<TextSecureAttachment> attachmentStreams = getAttachmentsFor(masterSecret, scaledAttachments);
TextSecureDataMessage mediaMessage = TextSecureDataMessage.newBuilder()
.withBody(message.getBody())
.withAttachments(attachmentStreams)
.withTimestamp(message.getSentTimeMillis())
.build();
messageSender.sendMessage(address, mediaMessage);
} catch (InvalidNumberException | UnregisteredUserException e) {

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
@ -16,7 +17,6 @@ import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment.ProgressListener;
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
@ -27,8 +27,6 @@ import java.util.List;
import de.greenrobot.event.EventBus;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.SendReq;
public abstract class PushSendJob extends SendJob {
@ -55,25 +53,25 @@ public abstract class PushSendJob extends SendJob {
return new TextSecureAddress(e164number, Optional.fromNullable(relay));
}
protected List<TextSecureAttachment> getAttachments(final MasterSecret masterSecret, final SendReq message) {
protected List<TextSecureAttachment> getAttachmentsFor(MasterSecret masterSecret, List<Attachment> parts) {
List<TextSecureAttachment> attachments = new LinkedList<>();
for (int i=0;i<message.getBody().getPartsNum();i++) {
final PduPart part = message.getBody().getPart(i);
final String contentType = Util.toIsoString(part.getContentType());
if (ContentType.isImageType(contentType) ||
ContentType.isAudioType(contentType) ||
ContentType.isVideoType(contentType))
for (final Attachment attachment : parts) {
if (ContentType.isImageType(attachment.getContentType()) ||
ContentType.isAudioType(attachment.getContentType()) ||
ContentType.isVideoType(attachment.getContentType()))
{
try {
InputStream is = PartAuthority.getPartStream(context, masterSecret, part.getDataUri());
if (attachment.getDataUri() == null) throw new IOException("Assertion failed, outgoing attachment has no data!");
InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, attachment.getDataUri());
attachments.add(TextSecureAttachment.newStreamBuilder()
.withStream(is)
.withContentType(contentType)
.withLength(part.getDataSize())
.withContentType(attachment.getContentType())
.withLength(attachment.getSize())
.withListener(new ProgressListener() {
@Override public void onAttachmentProgress(long total, long progress) {
EventBus.getDefault().postSticky(new PartProgressEvent(part.getPartId(), total, progress));
@Override
public void onAttachmentProgress(long total, long progress) {
EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress));
}
})
.build());

Wyświetl plik

@ -1,28 +1,28 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.util.Log;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.TextSecureExpiredException;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.SendReq;
public abstract class SendJob extends MasterSecretJob {
private final static String TAG = SendJob.class.getSimpleName();
public SendJob(Context context, JobParameters parameters) {
@ -42,68 +42,37 @@ public abstract class SendJob extends MasterSecretJob {
protected abstract void onSend(MasterSecret masterSecret) throws Exception;
protected SendReq getResolvedMessage(MasterSecret masterSecret, SendReq message,
MediaConstraints constraints, boolean toMemory)
throws IOException, UndeliverableMessageException
protected void markAttachmentsUploaded(long messageId, @NonNull List<Attachment> attachments) {
AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context);
for (Attachment attachment : attachments) {
database.markAttachmentUploaded(messageId, attachment);
}
}
protected List<Attachment> scaleAttachments(@NonNull MasterSecret masterSecret,
@NonNull MediaConstraints constraints,
@NonNull List<Attachment> attachments)
throws UndeliverableMessageException
{
PduBody body = new PduBody();
try {
for (int i = 0; i < message.getBody().getPartsNum(); i++) {
body.addPart(getResolvedPart(masterSecret, constraints, message.getBody().getPart(i), toMemory));
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
List<Attachment> results = new LinkedList<>();
for (Attachment attachment : attachments) {
try {
if (constraints.isSatisfied(context, masterSecret, attachment)) {
results.add(attachment);
} else if (constraints.canResize(attachment)) {
InputStream resized = constraints.getResizedMedia(context, masterSecret, attachment);
results.add(attachmentDatabase.updateAttachmentData(masterSecret, attachment, resized));
} else {
throw new UndeliverableMessageException("Size constraints could not be met!");
}
} catch (IOException | MmsException e) {
throw new UndeliverableMessageException(e);
}
} catch (MmsException me) {
throw new UndeliverableMessageException(me);
}
return new SendReq(message.getPduHeaders(),
body,
message.getDatabaseMessageId(),
message.getDatabaseMessageBox(),
message.getSentTimestamp());
}
private PduPart getResolvedPart(MasterSecret masterSecret, MediaConstraints constraints,
PduPart part, boolean toMemory)
throws IOException, MmsException, UndeliverableMessageException
{
byte[] resizedData = null;
if (!constraints.isSatisfied(context, masterSecret, part)) {
if (!constraints.canResize(part)) {
throw new UndeliverableMessageException("Size constraints could not be satisfied.");
}
resizedData = getResizedPartData(masterSecret, constraints, part);
}
if (toMemory && part.getDataUri() != null) {
part.setData(resizedData != null ? resizedData : MediaUtil.getPartData(context, masterSecret, part));
}
if (resizedData != null) {
part.setDataSize(resizedData.length);
}
return part;
}
protected void markPartsUploaded(long messageId, PduBody body) {
if (body == null) return;
PartDatabase database = DatabaseFactory.getPartDatabase(context);
for (int i = 0; i < body.getPartsNum(); i++) {
database.markPartUploaded(messageId, body.getPart(i));
}
}
private byte[] getResizedPartData(MasterSecret masterSecret, MediaConstraints constraints,
PduPart part)
throws IOException, MmsException
{
Log.w(TAG, "resizing part " + part.getPartId());
final long oldSize = part.getDataSize();
final byte[] data = constraints.getResizedMedia(context, masterSecret, part);
DatabaseFactory.getPartDatabase(context).updatePartData(masterSecret, part, new ByteArrayInputStream(data));
Log.w(TAG, String.format("Resized part %.1fkb => %.1fkb", oldSize / 1024.0, part.getDataSize() / 1024.0));
return data;
return results;
}
}

Wyświetl plik

@ -6,9 +6,10 @@ import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -18,8 +19,6 @@ import org.whispersystems.jobqueue.requirements.Requirement;
import java.util.Collections;
import java.util.Set;
import ws.com.google.android.mms.pdu.PduPart;
public class MediaNetworkRequirement implements Requirement, ContextDependent {
private static final long serialVersionUID = 0L;
private static final String TAG = MediaNetworkRequirement.class.getSimpleName();
@ -30,14 +29,15 @@ public class MediaNetworkRequirement implements Requirement, ContextDependent {
private final long partRowId;
private final long partUniqueId;
public MediaNetworkRequirement(Context context, long messageId, PartId partId) {
public MediaNetworkRequirement(Context context, long messageId, AttachmentId attachmentId) {
this.context = context;
this.messageId = messageId;
this.partRowId = partId.getRowId();
this.partUniqueId = partId.getUniqueId();
this.partRowId = attachmentId.getRowId();
this.partUniqueId = attachmentId.getUniqueId();
}
@Override public void setContext(Context context) {
@Override
public void setContext(Context context) {
this.context = context;
}
@ -74,23 +74,26 @@ public class MediaNetworkRequirement implements Requirement, ContextDependent {
@Override
public boolean isPresent() {
final PartId partId = new PartId(partRowId, partUniqueId);
final PartDatabase db = DatabaseFactory.getPartDatabase(context);
final PduPart part = db.getPart(partId);
if (part == null) {
Log.w(TAG, "part was null, returning vacuous true");
final AttachmentId attachmentId = new AttachmentId(partRowId, partUniqueId);
final AttachmentDatabase db = DatabaseFactory.getAttachmentDatabase(context);
final Attachment attachment = db.getAttachment(attachmentId);
if (attachment == null) {
Log.w(TAG, "attachment was null, returning vacuous true");
return true;
}
Log.w(TAG, "part transfer progress is " + part.getTransferProgress());
switch (part.getTransferProgress()) {
case PartDatabase.TRANSFER_PROGRESS_STARTED:
Log.w(TAG, "part transfer progress is " + attachment.getTransferState());
switch (attachment.getTransferState()) {
case AttachmentDatabase.TRANSFER_PROGRESS_STARTED:
return true;
case PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING:
case AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING:
final Set<String> allowedTypes = getAllowedAutoDownloadTypes();
final boolean isAllowed = allowedTypes.contains(MediaUtil.getDiscreteMimeType(part));
final boolean isAllowed = allowedTypes.contains(MediaUtil.getDiscreteMimeType(attachment.getContentType()));
if (isAllowed) db.setTransferState(messageId, partId, PartDatabase.TRANSFER_PROGRESS_STARTED);
/// XXX WTF -- This is *hella* gross. A requirement shouldn't have the side effect of
// *modifying the database* just by calling isPresent().
if (isAllowed) db.setTransferState(messageId, attachmentId, AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
return isAllowed;
default:
return false;

Wyświetl plik

@ -135,7 +135,7 @@ public class AttachmentManager {
} else {
slideDeck.addSlide(slide);
attachmentView.setVisibility(View.VISIBLE);
thumbnail.setImageResource(slide, masterSecret);
thumbnail.setImageResource(masterSecret, slide, false, true);
attachmentListener.onAttachmentChanged();
}
}
@ -213,9 +213,9 @@ public class AttachmentManager {
final @Nullable Slide slide,
final @NonNull MediaConstraints constraints)
{
return slide == null ||
constraints.isSatisfied(context, masterSecret, slide.getPart()) ||
constraints.canResize(slide.getPart());
return slide == null ||
constraints.isSatisfied(context, masterSecret, slide.asAttachment()) ||
constraints.canResize(slide.asAttachment());
}
private class RemoveButtonListener implements View.OnClickListener {

Wyświetl plik

@ -21,8 +21,10 @@ import android.content.res.Resources.Theme;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.util.ResUtil;
import java.io.IOException;
@ -33,11 +35,22 @@ import ws.com.google.android.mms.pdu.PduPart;
public class AudioSlide extends Slide {
public AudioSlide(Context context, Uri uri, long dataSize) throws IOException {
super(context, constructPartFromUri(context, uri, ContentType.AUDIO_UNSPECIFIED, dataSize));
super(context, constructAttachmentFromUri(context, uri, ContentType.AUDIO_UNSPECIFIED, dataSize));
}
public AudioSlide(Context context, PduPart part) {
super(context, part);
public AudioSlide(Context context, Attachment attachment) {
super(context, attachment);
}
@Override
@Nullable
public Uri getThumbnailUri() {
return null;
}
@Override
public boolean hasPlaceholder() {
return true;
}
@Override
@ -50,7 +63,9 @@ public class AudioSlide extends Slide {
return true;
}
@NonNull @Override public String getContentDescription() {
@NonNull
@Override
public String getContentDescription() {
return context.getString(R.string.Slide_audio);
}

Wyświetl plik

@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
@ -12,11 +10,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.RetrieveConf;
@ -31,7 +24,9 @@ public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsCo
this.context = context;
}
@Nullable @Override public SendConf send(@NonNull byte[] pduBytes)
@Nullable
@Override
public SendConf send(@NonNull byte[] pduBytes)
throws UndeliverableMessageException
{
try {
@ -47,8 +42,10 @@ public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsCo
}
}
@Nullable @Override public RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId)
@Nullable
@Override
public RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId)
throws MmsException, MmsRadioException, ApnUnavailableException, IOException
{
try {

Wyświetl plik

@ -14,20 +14,22 @@ import java.io.IOException;
import java.io.InputStream;
public class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher {
private static final String TAG = DecryptableStreamLocalUriFetcher.class.getSimpleName();
private Context context;
private Context context;
private MasterSecret masterSecret;
public DecryptableStreamLocalUriFetcher(Context context, MasterSecret masterSecret, Uri uri) {
super(context, uri);
this.context = context;
this.context = context;
this.masterSecret = masterSecret;
}
@Override
protected InputStream loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException {
try {
return PartAuthority.getPartStream(context, masterSecret, uri);
return PartAuthority.getAttachmentStream(context, masterSecret, uri);
} catch (IOException ioe) {
Log.w(TAG, ioe);
throw new FileNotFoundException("PartAuthority couldn't load Uri resource.");

Wyświetl plik

@ -2,21 +2,29 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.io.IOException;
import java.io.InputStream;
import ws.com.google.android.mms.pdu.PduPart;
public class GifSlide extends ImageSlide {
public GifSlide(Context context, PduPart part) {
super(context, part);
public GifSlide(Context context, Attachment attachment) {
super(context, attachment);
}
public GifSlide(Context context, Uri uri, long dataSize) throws IOException {
super(context, uri, dataSize);
}
@Override public Uri getThumbnailUri() {
return getPart().getDataUri();
@Override
@Nullable
public Uri getThumbnailUri() {
return getUri();
}
}

Wyświetl plik

@ -23,32 +23,22 @@ import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import java.io.IOException;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
public class ImageSlide extends Slide {
private static final String TAG = ImageSlide.class.getSimpleName();
public ImageSlide(Context context, PduPart part) {
super(context, part);
public ImageSlide(@NonNull Context context, @NonNull Attachment attachment) {
super(context, attachment);
}
public ImageSlide(Context context, Uri uri, long size) throws IOException {
super(context, constructPartFromUri(context, uri, ContentType.IMAGE_JPEG, size));
}
@Override
public Uri getThumbnailUri() {
if (getPart().getDataUri() != null) {
return isDraft()
? getPart().getDataUri()
: PartAuthority.getThumbnailUri(getPart().getPartId());
}
return null;
super(context, constructAttachmentFromUri(context, uri, ContentType.IMAGE_JPEG, size));
}
@Override

Wyświetl plik

@ -1,38 +1,42 @@
package org.thoughtcrime.securesms.mms;
import android.text.TextUtils;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.PointerAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MediaKey;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.MmsAddresses;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import java.util.LinkedList;
import java.util.List;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduHeaders;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.RetrieveConf;
public class IncomingMediaMessage {
private final PduHeaders headers;
private final PduBody body;
private final String groupId;
private final boolean push;
private final String from;
private final String body;
private final String groupId;
private final boolean push;
private final long sentTimeMillis;
public IncomingMediaMessage(RetrieveConf retrieved) {
this.headers = retrieved.getPduHeaders();
this.body = retrieved.getBody();
this.groupId = null;
this.push = false;
private final List<String> to = new LinkedList<>();
private final List<String> cc = new LinkedList<>();
private final List<Attachment> attachments = new LinkedList<>();
public IncomingMediaMessage(String from, List<String> to, List<String> cc,
String body, long sentTimeMillis,
List<Attachment> attachments)
{
this.from = from;
this.sentTimeMillis = sentTimeMillis;
this.body = body;
this.groupId = null;
this.push = false;
this.to.addAll(to);
this.cc.addAll(cc);
this.attachments.addAll(attachments);
}
public IncomingMediaMessage(MasterSecretUnion masterSecret,
@ -44,59 +48,30 @@ public class IncomingMediaMessage {
Optional<TextSecureGroup> group,
Optional<List<TextSecureAttachment>> attachments)
{
this.headers = new PduHeaders();
this.body = new PduBody();
this.push = true;
this.push = true;
this.from = from;
this.sentTimeMillis = sentTimeMillis;
this.body = body.orNull();
if (group.isPresent()) {
this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
} else {
this.groupId = null;
}
if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
else this.groupId = null;
this.headers.setEncodedStringValue(new EncodedStringValue(from), PduHeaders.FROM);
this.headers.appendEncodedStringValue(new EncodedStringValue(to), PduHeaders.TO);
this.headers.setLongInteger(sentTimeMillis / 1000, PduHeaders.DATE);
if (body.isPresent() && !TextUtils.isEmpty(body.get())) {
PduPart text = new PduPart();
text.setData(Util.toUtf8Bytes(body.get()));
text.setContentType(Util.toIsoBytes("text/plain"));
text.setCharset(CharacterSets.UTF_8);
this.body.addPart(text);
}
if (attachments.isPresent()) {
for (TextSecureAttachment attachment : attachments.get()) {
if (attachment.isPointer()) {
PduPart media = new PduPart();
String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey());
media.setContentType(Util.toIsoBytes(attachment.getContentType()));
media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId())));
media.setContentDisposition(Util.toIsoBytes(encryptedKey));
if (relay.isPresent()) {
media.setName(Util.toIsoBytes(relay.get()));
}
media.setTransferProgress(PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING);
this.body.addPart(media);
}
}
}
this.to.add(to);
this.attachments.addAll(PointerAttachment.forPointers(masterSecret, attachments));
}
public PduHeaders getPduHeaders() {
return headers;
}
public PduBody getBody() {
public String getBody() {
return body;
}
public MmsAddresses getAddresses() {
return new MmsAddresses(from, to, cc, new LinkedList<String>());
}
public List<Attachment> getAttachments() {
return attachments;
}
public String getGroupId() {
return groupId;
}
@ -105,10 +80,11 @@ public class IncomingMediaMessage {
return push;
}
public long getSentTimeMillis() {
return sentTimeMillis;
}
public boolean isGroupMessage() {
return groupId != null ||
!Util.isEmpty(headers.getEncodedStringValues(PduHeaders.CC)) ||
(headers.getEncodedStringValues(PduHeaders.TO) != null &&
headers.getEncodedStringValues(PduHeaders.TO).length > 1);
return groupId != null || to.size() > 1 || cc.size() > 0;
}
}

Wyświetl plik

@ -199,7 +199,6 @@ public abstract class LegacyMmsConnection {
protected List<Header> getBaseHeaders() {
final String number = TelephonyUtil.getManager(context).getLine1Number(); ;
final Optional<BasicHeader> mdnHeader = getVerizonMdnHeader(number);
return new LinkedList<Header>() {{
add(new BasicHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"));
@ -209,28 +208,10 @@ public abstract class LegacyMmsConnection {
if (!TextUtils.isEmpty(number)) {
add(new BasicHeader("x-up-calling-line-id", number));
add(new BasicHeader("X-MDN", number));
if (mdnHeader.isPresent()) add(mdnHeader.get());
}
}};
}
private Optional<BasicHeader> getVerizonMdnHeader(@Nullable String number) {
if (TextUtils.isEmpty(number)) return Optional.absent();
try {
PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
PhoneNumber phoneNumber = phoneNumberUtil.parse(number, null);
String mdnNumber = phoneNumberUtil.getNationalSignificantNumber(phoneNumber);
return Optional.of(new BasicHeader("x-vzw-mdn", mdnNumber));
} catch (NumberParseException e) {
Log.w(TAG, e);
return Optional.absent();
}
}
public static class Apn {
public static Apn EMPTY = new Apn("", "", "", "", "");

Wyświetl plik

@ -1,25 +1,23 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import ws.com.google.android.mms.pdu.PduPart;
public abstract class MediaConstraints {
private static final String TAG = MediaConstraints.class.getSimpleName();
@ -36,13 +34,13 @@ public abstract class MediaConstraints {
public abstract int getAudioMaxSize();
public boolean isSatisfied(Context context, MasterSecret masterSecret, PduPart part) {
public boolean isSatisfied(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Attachment attachment) {
try {
return (MediaUtil.isGif(part) && part.getDataSize() <= getGifMaxSize() && isWithinBounds(context, masterSecret, part.getDataUri())) ||
(MediaUtil.isImage(part) && part.getDataSize() <= getImageMaxSize() && isWithinBounds(context, masterSecret, part.getDataUri())) ||
(MediaUtil.isAudio(part) && part.getDataSize() <= getAudioMaxSize()) ||
(MediaUtil.isVideo(part) && part.getDataSize() <= getVideoMaxSize()) ||
(!MediaUtil.isImage(part) && !MediaUtil.isAudio(part) && !MediaUtil.isVideo(part));
return (MediaUtil.isGif(attachment) && attachment.getSize() <= getGifMaxSize() && isWithinBounds(context, masterSecret, attachment.getDataUri())) ||
(MediaUtil.isImage(attachment) && attachment.getSize() <= getImageMaxSize() && isWithinBounds(context, masterSecret, attachment.getDataUri())) ||
(MediaUtil.isAudio(attachment) && attachment.getSize() <= getAudioMaxSize()) ||
(MediaUtil.isVideo(attachment) && attachment.getSize() <= getVideoMaxSize()) ||
(!MediaUtil.isImage(attachment) && !MediaUtil.isAudio(attachment) && !MediaUtil.isVideo(attachment));
} catch (IOException ioe) {
Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe);
return false;
@ -50,24 +48,28 @@ public abstract class MediaConstraints {
}
public boolean isWithinBounds(Context context, MasterSecret masterSecret, Uri uri) throws IOException {
InputStream is = PartAuthority.getPartStream(context, masterSecret, uri);
InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, uri);
Pair<Integer, Integer> dimensions = BitmapUtil.getDimensions(is);
return dimensions.first > 0 && dimensions.first <= getImageMaxWidth(context) &&
dimensions.second > 0 && dimensions.second <= getImageMaxHeight(context);
}
public boolean canResize(PduPart part) {
return part != null && MediaUtil.isImage(part) && !MediaUtil.isGif(part);
public boolean canResize(@Nullable Attachment attachment) {
return attachment != null && MediaUtil.isImage(attachment) && !MediaUtil.isGif(attachment);
}
public byte[] getResizedMedia(Context context, MasterSecret masterSecret, PduPart part)
public InputStream getResizedMedia(@NonNull Context context,
@NonNull MasterSecret masterSecret,
@NonNull Attachment attachment)
throws IOException
{
if (!canResize(part) || part.getDataUri() == null) {
if (!canResize(attachment)) {
throw new UnsupportedOperationException("Cannot resize this content type");
}
try {
return BitmapUtil.createScaledBytes(context, new DecryptableUri(masterSecret, part.getDataUri()), this);
// XXX - This is loading everything into memory! We want the send path to be stream-like.
return new ByteArrayInputStream(BitmapUtil.createScaledBytes(context, new DecryptableUri(masterSecret, attachment.getDataUri()), this));
} catch (ExecutionException ee) {
throw new IOException(ee);
}

Wyświetl plik

@ -1,36 +1,44 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.redphone.util.Base64;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.textsecure.internal.push.TextSecureProtos.GroupContext;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
private final GroupContext group;
public OutgoingGroupMediaMessage(Context context, Recipients recipients,
GroupContext group, byte[] avatar)
public OutgoingGroupMediaMessage(@NonNull Recipients recipients,
@NonNull String encodedGroupContext,
@NonNull List<Attachment> avatar,
long sentTimeMillis)
throws IOException
{
super(context, recipients, new PduBody(), Base64.encodeBytes(group.toByteArray()),
super(recipients, encodedGroupContext, avatar, sentTimeMillis,
ThreadDatabase.DistributionTypes.CONVERSATION);
this.group = GroupContext.parseFrom(Base64.decode(encodedGroupContext));
}
public OutgoingGroupMediaMessage(@NonNull Recipients recipients,
@NonNull GroupContext group,
@Nullable final Attachment avatar)
{
super(recipients, Base64.encodeBytes(group.toByteArray()),
new LinkedList<Attachment>() {{if (avatar != null) add(avatar);}},
System.currentTimeMillis(),
ThreadDatabase.DistributionTypes.CONVERSATION);
this.group = group;
if (avatar != null) {
PduPart part = new PduPart();
part.setData(avatar);
part.setContentType(ContentType.IMAGE_PNG.getBytes());
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setName(("Image" + System.currentTimeMillis()).getBytes());
body.addPart(part);
}
}
@Override
@ -45,4 +53,8 @@ public class OutgoingGroupMediaMessage extends OutgoingSecureMediaMessage {
public boolean isGroupQuit() {
return group.getType().getNumber() == GroupContext.Type.QUIT_VALUE;
}
public GroupContext getGroupContext() {
return group;
}
}

Wyświetl plik

@ -1,66 +1,54 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.text.TextUtils;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MediaKey;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
import java.util.List;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
public class OutgoingMediaMessage {
private final Recipients recipients;
protected final PduBody body;
private final int distributionType;
private final Recipients recipients;
protected final String body;
protected final List<Attachment> attachments;
private final long sentTimeMillis;
private final int distributionType;
public OutgoingMediaMessage(Context context, Recipients recipients, PduBody body,
String message, int distributionType)
public OutgoingMediaMessage(Recipients recipients, String message,
List<Attachment> attachments, long sentTimeMillis,
int distributionType)
{
this.recipients = recipients;
this.body = body;
this.body = message;
this.sentTimeMillis = sentTimeMillis;
this.distributionType = distributionType;
if (!TextUtils.isEmpty(message)) {
this.body.addPart(new TextSlide(context, message).getPart());
}
this.attachments = attachments;
}
public OutgoingMediaMessage(Context context, Recipients recipients, SlideDeck slideDeck,
String message, int distributionType)
public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int distributionType)
{
this(context, recipients, slideDeck.toPduBody(), message, distributionType);
}
public OutgoingMediaMessage(Context context, MasterSecretUnion masterSecret,
Recipients recipients, List<TextSecureAttachment> attachments,
String message)
{
this(context, recipients, pduBodyFor(masterSecret, attachments), message,
ThreadDatabase.DistributionTypes.CONVERSATION);
this(recipients, message, slideDeck.asAttachments(), sentTimeMillis, distributionType);
}
public OutgoingMediaMessage(OutgoingMediaMessage that) {
this.recipients = that.getRecipients();
this.body = that.body;
this.distributionType = that.distributionType;
this.attachments = that.attachments;
this.sentTimeMillis = that.sentTimeMillis;
}
public Recipients getRecipients() {
return recipients;
}
public PduBody getPduBody() {
public String getBody() {
return body;
}
public List<Attachment> getAttachments() {
return attachments;
}
public int getDistributionType() {
return distributionType;
}
@ -73,23 +61,8 @@ public class OutgoingMediaMessage {
return false;
}
private static PduBody pduBodyFor(MasterSecretUnion masterSecret, List<TextSecureAttachment> attachments) {
PduBody body = new PduBody();
for (TextSecureAttachment attachment : attachments) {
if (attachment.isPointer()) {
PduPart media = new PduPart();
String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey());
media.setContentType(Util.toIsoBytes(attachment.getContentType()));
media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId())));
media.setContentDisposition(Util.toIsoBytes(encryptedKey));
body.addPart(media);
}
}
return body;
public long getSentTimeMillis() {
return sentTimeMillis;
}
}

Wyświetl plik

@ -2,16 +2,21 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.List;
import ws.com.google.android.mms.pdu.PduBody;
public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
public OutgoingSecureMediaMessage(Context context, Recipients recipients, PduBody body,
String message, int distributionType)
public OutgoingSecureMediaMessage(Recipients recipients, String body,
List<Attachment> attachments,
long sentTimeMillis,
int distributionType)
{
super(context, recipients, body, message, distributionType);
super(recipients, body, attachments, sentTimeMillis, distributionType);
}
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {

Wyświetl plik

@ -4,12 +4,14 @@ import android.content.ContentUris;
import android.content.Context;
import android.content.UriMatcher;
import android.net.Uri;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.providers.CaptureProvider;
import org.thoughtcrime.securesms.providers.PartProvider;
import org.thoughtcrime.securesms.providers.SingleUseBlobProvider;
import java.io.IOException;
import java.io.InputStream;
@ -21,9 +23,10 @@ public class PartAuthority {
private static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
private static final Uri THUMB_CONTENT_URI = Uri.parse(THUMB_URI_STRING);
private static final int PART_ROW = 1;
private static final int THUMB_ROW = 2;
private static final int CAPTURE_ROW = 3;
private static final int PART_ROW = 1;
private static final int THUMB_ROW = 2;
private static final int CAPTURE_ROW = 3;
private static final int SINGLE_USE_ROW = 4;
private static final UriMatcher uriMatcher;
@ -32,9 +35,10 @@ public class PartAuthority {
uriMatcher.addURI("org.thoughtcrime.securesms", "part/*/#", PART_ROW);
uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/*/#", THUMB_ROW);
uriMatcher.addURI(CaptureProvider.AUTHORITY, CaptureProvider.EXPECTED_PATH, CAPTURE_ROW);
uriMatcher.addURI(SingleUseBlobProvider.AUTHORITY, SingleUseBlobProvider.PATH, SINGLE_USE_ROW);
}
public static InputStream getPartStream(Context context, MasterSecret masterSecret, Uri uri)
public static InputStream getAttachmentStream(@NonNull Context context, @NonNull MasterSecret masterSecret, @NonNull Uri uri)
throws IOException
{
int match = uriMatcher.match(uri);
@ -42,12 +46,14 @@ public class PartAuthority {
switch (match) {
case PART_ROW:
PartUriParser partUri = new PartUriParser(uri);
return DatabaseFactory.getPartDatabase(context).getPartStream(masterSecret, partUri.getPartId());
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentStream(masterSecret, partUri.getPartId());
case THUMB_ROW:
partUri = new PartUriParser(uri);
return DatabaseFactory.getPartDatabase(context).getThumbnailStream(masterSecret, partUri.getPartId());
return DatabaseFactory.getAttachmentDatabase(context).getThumbnailStream(masterSecret, partUri.getPartId());
case CAPTURE_ROW:
return CaptureProvider.getInstance(context).getStream(masterSecret, ContentUris.parseId(uri));
case SINGLE_USE_ROW:
return SingleUseBlobProvider.getInstance().getStream(ContentUris.parseId(uri));
default:
return context.getContentResolver().openInputStream(uri);
}
@ -56,18 +62,18 @@ public class PartAuthority {
}
}
public static Uri getPublicPartUri(Uri uri) {
public static Uri getAttachmentPublicUri(Uri uri) {
PartUriParser partUri = new PartUriParser(uri);
return PartProvider.getContentUri(partUri.getPartId());
}
public static Uri getPartUri(PartDatabase.PartId partId) {
Uri uri = Uri.withAppendedPath(PART_CONTENT_URI, String.valueOf(partId.getUniqueId()));
return ContentUris.withAppendedId(uri, partId.getRowId());
public static Uri getAttachmentDataUri(AttachmentId attachmentId) {
Uri uri = Uri.withAppendedPath(PART_CONTENT_URI, String.valueOf(attachmentId.getUniqueId()));
return ContentUris.withAppendedId(uri, attachmentId.getRowId());
}
public static Uri getThumbnailUri(PartDatabase.PartId partId) {
Uri uri = Uri.withAppendedPath(THUMB_CONTENT_URI, String.valueOf(partId.getUniqueId()));
return ContentUris.withAppendedId(uri, partId.getRowId());
public static Uri getAttachmentThumbnailUri(AttachmentId attachmentId) {
Uri uri = Uri.withAppendedPath(THUMB_CONTENT_URI, String.valueOf(attachmentId.getUniqueId()));
return ContentUris.withAppendedId(uri, attachmentId.getRowId());
}
}

Wyświetl plik

@ -3,10 +3,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.ContentUris;
import android.net.Uri;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.util.Hex;
import java.io.IOException;
import org.thoughtcrime.securesms.attachments.AttachmentId;
public class PartUriParser {
@ -16,8 +13,8 @@ public class PartUriParser {
this.uri = uri;
}
public PartDatabase.PartId getPartId() {
return new PartDatabase.PartId(getId(), getUniqueId());
public AttachmentId getPartId() {
return new AttachmentId(getId(), getUniqueId());
}
private long getId() {

Wyświetl plik

@ -23,32 +23,38 @@ import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.io.IOException;
import java.io.InputStream;
import ws.com.google.android.mms.pdu.PduPart;
public abstract class Slide {
protected final PduPart part;
protected final Context context;
protected final Attachment attachment;
protected final Context context;
public Slide(@NonNull Context context, @NonNull Attachment attachment) {
this.context = context;
this.attachment = attachment;
public Slide(Context context, @NonNull PduPart part) {
this.part = part;
this.context = context;
}
public String getContentType() {
return new String(part.getContentType());
return attachment.getContentType();
}
@Nullable
public Uri getUri() {
return part.getDataUri();
return attachment.getDataUri();
}
@Nullable
public Uri getThumbnailUri() {
return attachment.getThumbnailUri();
}
public boolean hasImage() {
@ -65,53 +71,39 @@ public abstract class Slide {
public @NonNull String getContentDescription() { return ""; }
public PduPart getPart() {
return part;
}
public Uri getThumbnailUri() {
return null;
public Attachment asAttachment() {
return attachment;
}
public boolean isInProgress() {
return part.isInProgress();
return attachment.isInProgress();
}
public boolean isPendingDownload() {
return getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_FAILED ||
getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_AUTO_PENDING;
return getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_FAILED ||
getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_AUTO_PENDING;
}
public long getTransferProgress() {
return part.getTransferProgress();
public long getTransferState() {
return attachment.getTransferState();
}
public @DrawableRes int getPlaceholderRes(Theme theme) {
throw new AssertionError("getPlaceholderRes() called for non-drawable slide");
}
public boolean isDraft() {
return !getPart().getPartId().isValid();
public boolean hasPlaceholder() {
return false;
}
protected static PduPart constructPartFromUri(@NonNull Context context,
@NonNull Uri uri,
@NonNull String defaultMime,
long dataSize)
throws IOException
protected static Attachment constructAttachmentFromUri(@NonNull Context context,
@NonNull Uri uri,
@NonNull String defaultMime,
long size)
throws IOException
{
final PduPart part = new PduPart();
final String mimeType = MediaUtil.getMimeType(context, uri);
final String derivedMimeType = mimeType != null ? mimeType : defaultMime;
part.setDataSize(dataSize);
part.setDataUri(uri);
part.setContentType(derivedMimeType.getBytes());
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setName((MediaUtil.getDiscreteMimeType(derivedMimeType) + System.currentTimeMillis()).getBytes());
return part;
Optional<String> resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri));
return new UriAttachment(uri, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size);
}
@Override
@ -124,8 +116,7 @@ public abstract class Slide {
this.hasAudio() == that.hasAudio() &&
this.hasImage() == that.hasImage() &&
this.hasVideo() == that.hasVideo() &&
this.isDraft() == that.isDraft() &&
this.getTransferProgress() == that.getTransferProgress() &&
this.getTransferState() == that.getTransferState() &&
Util.equals(this.getUri(), that.getUri()) &&
Util.equals(this.getThumbnailUri(), that.getThumbnailUri());
}
@ -133,6 +124,6 @@ public abstract class Slide {
@Override
public int hashCode() {
return Util.hashCode(getContentType(), hasAudio(), hasImage(),
hasVideo(), isDraft(), getUri(), getThumbnailUri(), getTransferProgress());
hasVideo(), getUri(), getThumbnailUri(), getTransferState());
}
}

Wyświetl plik

@ -22,6 +22,7 @@ import android.support.annotation.Nullable;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
@ -43,14 +44,9 @@ public class SlideDeck {
private final List<Slide> slides = new LinkedList<>();
public SlideDeck(SlideDeck copy) {
this.slides.addAll(copy.getSlides());
}
public SlideDeck(Context context, PduBody body) {
for (int i=0;i<body.getPartsNum();i++) {
String contentType = Util.toIsoString(body.getPart(i).getContentType());
Slide slide = MediaUtil.getSlideForPart(context, body.getPart(i), contentType);
public SlideDeck(Context context, List<Attachment> attachments) {
for (Attachment attachment : attachments) {
Slide slide = MediaUtil.getSlideForAttachment(context, attachment);
if (slide != null) slides.add(slide);
}
}
@ -62,15 +58,14 @@ public class SlideDeck {
slides.clear();
}
public PduBody toPduBody() {
PduBody body = new PduBody();
public List<Attachment> asAttachments() {
List<Attachment> attachments = new LinkedList<>();
for (Slide slide : slides) {
PduPart part = slide.getPart();
body.addPart(part);
attachments.add(slide.asAttachment());
}
return body;
return attachments;
}
public void addSlide(Slide slide) {

Wyświetl plik

@ -1,55 +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 java.io.UnsupportedEncodingException;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.PduPart;
public class TextSlide extends Slide {
public TextSlide(Context context, String message) {
super(context, getPartForMessage(message));
}
private static PduPart getPartForMessage(String message) {
PduPart part = new PduPart();
try {
part.setData(message.getBytes(CharacterSets.MIMENAME_UTF_8));
if (part.getData().length == 0)
throw new AssertionError("Part data should not be zero!");
} catch (UnsupportedEncodingException e) {
Log.w("TextSlide", "ISO_8859_1 must be supported!", e);
part.setData("Unsupported character set!".getBytes());
}
part.setCharset(CharacterSets.UTF_8);
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setName(("Text"+System.currentTimeMillis()).getBytes());
return part;
}
}

Wyświetl plik

@ -21,8 +21,10 @@ import android.content.res.Resources.Theme;
import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.util.ResUtil;
import java.io.IOException;
@ -33,11 +35,22 @@ import ws.com.google.android.mms.pdu.PduPart;
public class VideoSlide extends Slide {
public VideoSlide(Context context, Uri uri, long dataSize) throws IOException {
super(context, constructPartFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize));
super(context, constructAttachmentFromUri(context, uri, ContentType.VIDEO_UNSPECIFIED, dataSize));
}
public VideoSlide(Context context, PduPart part) {
super(context, part);
public VideoSlide(Context context, Attachment attachment) {
super(context, attachment);
}
@Override
@Nullable
public Uri getThumbnailUri() {
return null;
}
@Override
public boolean hasPlaceholder() {
return true;
}
@Override

Wyświetl plik

@ -24,6 +24,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.RemoteInput;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
@ -32,6 +33,8 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import java.util.LinkedList;
import ws.com.google.android.mms.pdu.PduBody;
/**
@ -64,7 +67,7 @@ public class WearReplyReceiver extends MasterSecretBroadcastReceiver {
long threadId;
if (recipients.isGroupRecipient()) {
OutgoingMediaMessage reply = new OutgoingMediaMessage(context, recipients, new PduBody(), responseText.toString(), 0);
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), 0);
threadId = MessageSender.send(context, masterSecret, reply, -1, false);
} else {
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString());

Wyświetl plik

@ -98,7 +98,7 @@ public class CaptureProvider {
}
}
public InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
public @NonNull InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
final byte[] cached = cache.get((int)id);
return cached != null ? new ByteArrayInputStream(cached)
: new DecryptingPartInputStream(getFile(id), masterSecret);

Wyświetl plik

@ -23,11 +23,12 @@ import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.mms.PartUriParser;
import org.thoughtcrime.securesms.service.KeyCachingService;
@ -57,13 +58,14 @@ public class PartProvider extends ContentProvider {
return true;
}
public static Uri getContentUri(PartDatabase.PartId partId) {
Uri uri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(partId.getUniqueId()));
return ContentUris.withAppendedId(uri, partId.getRowId());
public static Uri getContentUri(AttachmentId attachmentId) {
Uri uri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(attachmentId.getUniqueId()));
return ContentUris.withAppendedId(uri, attachmentId.getRowId());
}
private File copyPartToTemporaryFile(MasterSecret masterSecret, PartDatabase.PartId partId) throws IOException {
InputStream in = DatabaseFactory.getPartDatabase(getContext()).getPartStream(masterSecret, partId);
@SuppressWarnings("ConstantConditions")
private File copyPartToTemporaryFile(MasterSecret masterSecret, AttachmentId attachmentId) throws IOException {
InputStream in = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachmentStream(masterSecret, attachmentId);
File tmpDir = getContext().getDir("tmp", 0);
File tmpFile = File.createTempFile("test", ".jpg", tmpDir);
FileOutputStream fout = new FileOutputStream(tmpFile);
@ -80,7 +82,7 @@ public class PartProvider extends ContentProvider {
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
MasterSecret masterSecret = KeyCachingService.getMasterSecret(getContext());
Log.w(TAG, "openFile() called!");
@ -112,27 +114,27 @@ public class PartProvider extends ContentProvider {
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
public int delete(@NonNull Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
public String getType(@NonNull Uri arg0) {
return null;
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
public Uri insert(@NonNull Uri arg0, ContentValues arg1) {
return null;
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
public Cursor query(@NonNull Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
public int update(@NonNull Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
}

Wyświetl plik

@ -0,0 +1,67 @@
package org.thoughtcrime.securesms.providers;
import android.content.ContentUris;
import android.content.Context;
import android.content.UriMatcher;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Util;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class SingleUseBlobProvider {
private static final String TAG = CaptureProvider.class.getSimpleName();
public static final String AUTHORITY = "org.thoughtcrime.securesms";
public static final String PATH = "memory/*/#";
private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/memory");
private final Map<Long, byte[]> cache = new HashMap<>();
private static final SingleUseBlobProvider instance = new SingleUseBlobProvider();
public static SingleUseBlobProvider getInstance() {
return instance;
}
private SingleUseBlobProvider() {}
public synchronized Uri createUri(@NonNull byte[] blob) {
try {
long id = Math.abs(SecureRandom.getInstance("SHA1PRNG").nextLong());
cache.put(id, blob);
Uri uniqueUri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(System.currentTimeMillis()));
return ContentUris.withAppendedId(uniqueUri, id);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public synchronized @NonNull InputStream getStream(long id) throws IOException {
byte[] cached = cache.get(id);
cache.remove(id);
if (cached != null) return new ByteArrayInputStream(cached);
else throw new IOException("ID not found: " + id);
}
}

Wyświetl plik

@ -81,6 +81,20 @@ public class RecipientFactory {
return getRecipientsForIds(context, ids, asynchronous);
}
public static Recipients getRecipientsFromStrings(@NonNull Context context, @NonNull List<String> numbers, boolean asynchronous) {
List<String> ids = new LinkedList<>();
for (String number : numbers) {
Optional<Long> id = getRecipientIdFromNumber(context, number);
if (id.isPresent()) {
ids.add(String.valueOf(id.get()));
}
}
return getRecipientsForIds(context, ids, asynchronous);
}
private static Recipients getRecipientsForIds(Context context, List<String> idStrings, boolean asynchronous) {
long[] ids = new long[idStrings.size()];
int i = 0;

Wyświetl plik

@ -257,7 +257,7 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
return Util.join(recipientArray, " ");
}
public String[] toNumberStringArray(boolean scrub) {
public @NonNull String[] toNumberStringArray(boolean scrub) {
String[] recipientsArray = new String[recipients.size()];
Iterator<Recipient> iterator = recipients.iterator();
int i = 0;
@ -278,6 +278,13 @@ public class Recipients implements Iterable<Recipient>, RecipientModifiedListene
return recipientsArray;
}
public @NonNull List<String> toNumberStringList(boolean scrub) {
List<String> results = new LinkedList<>();
Collections.addAll(results, toNumberStringArray(scrub));
return results;
}
public String toShortString() {
String fromString = "";

Wyświetl plik

@ -55,7 +55,7 @@ public class QuickResponseService extends MasterSecretIntentService {
if (recipients.isSingleRecipient()) {
MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content), -1, false);
} else {
MessageSender.send(this, masterSecret, new OutgoingMediaMessage(this, recipients, new SlideDeck(), content,
MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(),
ThreadDatabase.DistributionTypes.DEFAULT), -1, false);
}
}

Wyświetl plik

@ -100,7 +100,7 @@ public class MessageSender {
}
Recipients recipients = message.getRecipients();
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms, System.currentTimeMillis());
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms);
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
@ -177,7 +177,7 @@ public class MessageSender {
throws MmsException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.markAsSent(messageId, "self-send".getBytes(), 0);
database.markAsSent(messageId);
database.markAsPush(messageId);
long newMessageId = database.copyMessageInbox(masterSecret, messageId);

Wyświetl plik

@ -30,7 +30,7 @@ public class GroupUtil {
return Hex.fromStringCondensed(groupId.split("!", 2)[1]);
}
public static boolean isEncodedGroup(String groupId) {
public static boolean isEncodedGroup(@NonNull String groupId) {
return groupId.startsWith(ENCODED_GROUP_PREFIX);
}

Wyświetl plik

@ -16,6 +16,8 @@
*/
package org.thoughtcrime.securesms.util;
import android.support.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
@ -26,17 +28,30 @@ public class ListenableFutureTask<V> extends FutureTask<V> {
private final List<FutureTaskListener<V>> listeners = new LinkedList<>();
@Nullable
private final Object identifier;
public ListenableFutureTask(Callable<V> callable) {
this(callable, null);
}
public ListenableFutureTask(Callable<V> callable, @Nullable Object identifier) {
super(callable);
this.identifier = identifier;
}
public ListenableFutureTask(final V result) {
this(result, null);
}
public ListenableFutureTask(final V result, @Nullable Object identifier) {
super(new Callable<V>() {
@Override
public V call() throws Exception {
return result;
}
});
this.identifier = identifier;
this.run();
}
@ -74,4 +89,19 @@ public class ListenableFutureTask<V> extends FutureTask<V> {
}
}
}
@Override
public boolean equals(Object other) {
if (other != null && other instanceof ListenableFutureTask && this.identifier != null) {
return identifier.equals(other);
} else {
return super.equals(other);
}
}
@Override
public int hashCode() {
if (identifier != null) return identifier.hashCode();
else return super.hashCode();
}
}

Wyświetl plik

@ -9,9 +9,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
@ -21,24 +20,24 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
public class MediaUtil {
private static final String TAG = MediaUtil.class.getSimpleName();
public static ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, Uri uri, String type)
public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri)
throws ExecutionException
{
long startMillis = System.currentTimeMillis();
ThumbnailData data;
if (ContentType.isImageType(type)) data = new ThumbnailData(generateImageThumbnail(context, masterSecret, uri));
else data = null;
ThumbnailData data = null;
if (ContentType.isImageType(contentType)) {
data = new ThumbnailData(generateImageThumbnail(context, masterSecret, uri));
}
if (data != null) {
Log.w(TAG, String.format("generated thumbnail for part, %dx%d (%.3f:1) in %dms",
@ -49,16 +48,6 @@ public class MediaUtil {
return data;
}
public static byte[] getPartData(Context context, MasterSecret masterSecret, PduPart part)
throws IOException
{
ByteArrayOutputStream os = part.getDataSize() > 0 && part.getDataSize() < Integer.MAX_VALUE
? new ByteArrayOutputStream((int) part.getDataSize())
: new ByteArrayOutputStream();
Util.copy(PartAuthority.getPartStream(context, masterSecret, part.getDataUri()), os);
return os.toByteArray();
}
private static Bitmap generateImageThumbnail(Context context, MasterSecret masterSecret, Uri uri)
throws ExecutionException
{
@ -66,22 +55,22 @@ public class MediaUtil {
return BitmapUtil.createScaledBitmap(context, new DecryptableUri(masterSecret, uri), maxSize, maxSize);
}
public static Slide getSlideForPart(Context context, PduPart part, String contentType) {
public static Slide getSlideForAttachment(Context context, Attachment attachment) {
Slide slide = null;
if (isGif(contentType)) {
slide = new GifSlide(context, part);
} else if (ContentType.isImageType(contentType)) {
slide = new ImageSlide(context, part);
} else if (ContentType.isVideoType(contentType)) {
slide = new VideoSlide(context, part);
} else if (ContentType.isAudioType(contentType)) {
slide = new AudioSlide(context, part);
if (isGif(attachment.getContentType())) {
slide = new GifSlide(context, attachment);
} else if (ContentType.isImageType(attachment.getContentType())) {
slide = new ImageSlide(context, attachment);
} else if (ContentType.isVideoType(attachment.getContentType())) {
slide = new VideoSlide(context, attachment);
} else if (ContentType.isAudioType(attachment.getContentType())) {
slide = new AudioSlide(context, attachment);
}
return slide;
}
public static String getMimeType(Context context, Uri uri) {
public static @Nullable String getMimeType(Context context, Uri uri) {
String type = context.getContentResolver().getType(uri);
if (type == null) {
final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
@ -91,7 +80,7 @@ public class MediaUtil {
}
public static long getMediaSize(Context context, MasterSecret masterSecret, Uri uri) throws IOException {
InputStream in = PartAuthority.getPartStream(context, masterSecret, uri);
InputStream in = PartAuthority.getAttachmentStream(context, masterSecret, uri);
if (in == null) throw new IOException("Couldn't obtain input stream.");
long size = 0;
@ -110,24 +99,20 @@ public class MediaUtil {
return !TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif");
}
public static boolean isGif(PduPart part) {
return isGif(Util.toIsoString(part.getContentType()));
public static boolean isGif(Attachment attachment) {
return isGif(attachment.getContentType());
}
public static boolean isImage(PduPart part) {
return ContentType.isImageType(Util.toIsoString(part.getContentType()));
public static boolean isImage(Attachment attachment) {
return ContentType.isImageType(attachment.getContentType());
}
public static boolean isAudio(PduPart part) {
return ContentType.isAudioType(Util.toIsoString(part.getContentType()));
public static boolean isAudio(Attachment attachment) {
return ContentType.isAudioType(attachment.getContentType());
}
public static boolean isVideo(PduPart part) {
return ContentType.isVideoType(Util.toIsoString(part.getContentType()));
}
public static @Nullable String getDiscreteMimeType(@NonNull PduPart part) {
return getDiscreteMimeType(Util.toIsoString(part.getContentType()));
public static boolean isVideo(Attachment attachment) {
return ContentType.isVideoType(attachment.getContentType());
}
public static @Nullable String getDiscreteMimeType(@NonNull String mimeType) {

Wyświetl plik

@ -35,8 +35,8 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
public SaveAttachmentTask(Context context, MasterSecret masterSecret) {
super(context, R.string.ConversationFragment_saving_attachment, R.string.ConversationFragment_saving_attachment_to_sd_card);
this.contextReference = new WeakReference<Context>(context);
this.masterSecretReference = new WeakReference<MasterSecret>(masterSecret);
this.contextReference = new WeakReference<>(context);
this.masterSecretReference = new WeakReference<>(masterSecret);
}
@Override
@ -59,7 +59,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
}
File mediaFile = constructOutputFile(attachment.contentType, attachment.date);
InputStream inputStream = PartAuthority.getPartStream(context, masterSecret, attachment.uri);
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, attachment.uri);
if (inputStream == null) {
return FAILURE;

Wyświetl plik

@ -159,7 +159,7 @@ public class Util {
return PhoneNumberFormatter.formatNumber(number, localNumber);
}
public static String canonicalizeNumberOrGroup(Context context, String number)
public static String canonicalizeNumberOrGroup(@NonNull Context context, @NonNull String number)
throws InvalidNumberException
{
if (GroupUtil.isEncodedGroup(number)) return number;

Wyświetl plik

@ -41,16 +41,6 @@ public class PduBody {
mPartMapByFileName = new HashMap<String, PduPart>();
}
public boolean containsPushInProgress() {
for (int i=0;i<getPartsNum();i++) {
if (getPart(i).isInProgress()) {
return true;
}
}
return false;
}
private void putPartToMaps(PduPart part) {
// Put part to mPartMapByContentId.
byte[] contentId = part.getContentId();

Wyświetl plik

@ -17,22 +17,11 @@
package ws.com.google.android.mms.pdu;
import android.graphics.Bitmap;
import android.net.Uri;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import ws.com.google.android.mms.ContentType;
/**
* The pdu part.
*/
@ -132,49 +121,11 @@ public class PduPart {
private static final String TAG = "PduPart";
private long rowId = -1;
private long uniqueId = -1;
private long mmsId = -1;
private boolean isEncrypted;
private int transferProgress;
private long dataSize;
private Bitmap thumbnail;
/**
* Empty Constructor.
*/
public PduPart() {
mPartHeader = new HashMap<Integer, Object>();
setUniqueId(System.currentTimeMillis());
}
public void setEncrypted(boolean isEncrypted) {
this.isEncrypted = isEncrypted;
}
public boolean getEncrypted() {
return isEncrypted;
}
public void setDataSize(long dataSize) {
this.dataSize = dataSize;
}
public long getDataSize() {
return this.dataSize;
}
public boolean isInProgress() {
return transferProgress != PartDatabase.TRANSFER_PROGRESS_DONE &&
transferProgress != PartDatabase.TRANSFER_PROGRESS_FAILED;
}
public void setTransferProgress(int transferProgress) {
this.transferProgress = transferProgress;
}
public int getTransferProgress() {
return transferProgress;
}
/**
@ -447,46 +398,5 @@ public class PduPart {
return new String(location);
}
}
public PartDatabase.PartId getPartId() {
return new PartDatabase.PartId(rowId, uniqueId);
}
public void setPartId(PartDatabase.PartId partId) {
this.rowId = partId.getRowId();
this.uniqueId = partId.getUniqueId();
}
public long getRowId() {
return rowId;
}
public void setRowId(long rowId) {
this.rowId = rowId;
}
public Bitmap getThumbnail() {
return thumbnail;
}
public void setThumbnail(Bitmap thumbnail) {
this.thumbnail = thumbnail;
}
public long getUniqueId() {
return uniqueId;
}
public void setUniqueId(long uniqueId) {
this.uniqueId = uniqueId;
}
public long getMmsId() {
return mmsId;
}
public void setMmsId(long mmsId) {
this.mmsId = mmsId;
}
}

Wyświetl plik

@ -23,9 +23,6 @@ import ws.com.google.android.mms.InvalidHeaderValueException;
public class SendReq extends MultimediaMessagePdu {
private static final String TAG = "SendReq";
private long databaseMessageId;
private long messageBox;
private long timestamp;
public SendReq() {
super();
@ -91,26 +88,6 @@ public class SendReq extends MultimediaMessagePdu {
super(headers, body);
}
public SendReq(PduHeaders headers, PduBody body, long messageId, long messageBox, long timestamp)
{
super(headers, body);
this.databaseMessageId = messageId;
this.messageBox = messageBox;
this.timestamp = timestamp;
}
public long getDatabaseMessageBox() {
return this.messageBox;
}
public long getDatabaseMessageId() {
return databaseMessageId;
}
public long getSentTimestamp() {
return timestamp;
}
/**
* Get Bcc value.
*

Wyświetl plik

@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.database;
import android.net.Uri;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.io.FileNotFoundException;
import java.io.InputStream;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class AttachmentDatabaseTest extends TextSecureTestCase {
private static final long ROW_ID = 1L;
private static final long UNIQUE_ID = 2L;
private AttachmentDatabase database;
@Override
public void setUp() {
database = spy(DatabaseFactory.getAttachmentDatabase(getInstrumentation().getTargetContext()));
}
public void testTaskNotRunWhenThumbnailExists() throws Exception {
final AttachmentId attachmentId = new AttachmentId(ROW_ID, UNIQUE_ID);
when(database.getAttachment(attachmentId)).thenReturn(getMockAttachment("x/x"));
doReturn(mock(InputStream.class)).when(database).getDataStream(any(MasterSecret.class), any(AttachmentId.class), eq("thumbnail"));
database.getThumbnailStream(mock(MasterSecret.class), attachmentId);
// XXX - I don't think this is testing anything? The thumbnail would be updated asynchronously.
verify(database, never()).updateAttachmentThumbnail(any(MasterSecret.class), any(AttachmentId.class), any(InputStream.class), anyFloat());
}
public void testTaskRunWhenThumbnailMissing() throws Exception {
final AttachmentId attachmentId = new AttachmentId(ROW_ID, UNIQUE_ID);
when(database.getAttachment(attachmentId)).thenReturn(getMockAttachment("image/png"));
doReturn(null).when(database).getDataStream(any(MasterSecret.class), any(AttachmentId.class), eq("thumbnail"));
doNothing().when(database).updateAttachmentThumbnail(any(MasterSecret.class), any(AttachmentId.class), any(InputStream.class), anyFloat());
try {
database.new ThumbnailFetchCallable(mock(MasterSecret.class), attachmentId).call();
throw new AssertionError("didn't try to generate thumbnail");
} catch (FileNotFoundException fnfe) {
// success
}
}
private DatabaseAttachment getMockAttachment(String contentType) {
DatabaseAttachment attachment = mock(DatabaseAttachment.class);
when(attachment.getContentType()).thenReturn(contentType);
when(attachment.getDataUri()).thenReturn(Uri.EMPTY);
return attachment;
}
}

Wyświetl plik

@ -1,67 +0,0 @@
package org.thoughtcrime.securesms.database;
import android.net.Uri;
import org.thoughtcrime.securesms.TextSecureTestCase;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import java.io.FileNotFoundException;
import java.io.InputStream;
import ws.com.google.android.mms.pdu.PduPart;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyFloat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class PartDatabaseTest extends TextSecureTestCase {
private static final long ROW_ID = 1L;
private static final long UNIQUE_ID = 2L;
private PartDatabase database;
@Override
public void setUp() {
database = spy(DatabaseFactory.getPartDatabase(getInstrumentation().getTargetContext()));
}
public void testTaskNotRunWhenThumbnailExists() throws Exception {
final PartId partId = new PartId(ROW_ID, UNIQUE_ID);
when(database.getPart(partId)).thenReturn(getPduPartSkeleton("x/x"));
doReturn(mock(InputStream.class)).when(database).getDataStream(any(MasterSecret.class), any(PartId.class), eq("thumbnail"));
database.getThumbnailStream(null, partId);
verify(database, never()).updatePartThumbnail(any(MasterSecret.class), any(PartId.class), any(PduPart.class), any(InputStream.class), anyFloat());
}
public void testTaskRunWhenThumbnailMissing() throws Exception {
final PartId partId = new PartId(ROW_ID, UNIQUE_ID);
when(database.getPart(partId)).thenReturn(getPduPartSkeleton("image/png"));
doReturn(null).when(database).getDataStream(any(MasterSecret.class), any(PartId.class), eq("thumbnail"));
doNothing().when(database).updatePartThumbnail(any(MasterSecret.class), any(PartId.class), any(PduPart.class), any(InputStream.class), anyFloat());
try {
database.new ThumbnailFetchCallable(mock(MasterSecret.class), partId).call();
throw new AssertionError("didn't try to generate thumbnail");
} catch (FileNotFoundException fnfe) {
// success
}
}
private PduPart getPduPartSkeleton(String contentType) {
PduPart part = new PduPart();
part.setContentType(contentType.getBytes());
part.setDataUri(Uri.EMPTY);
return part;
}
}

Wyświetl plik

@ -5,7 +5,7 @@ import android.content.Context;
import org.junit.Before;
import org.junit.Test;
import org.thoughtcrime.securesms.BaseUnitTest;
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import org.thoughtcrime.securesms.database.AttachmentDatabase.AttachmentId;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob.InvalidPartException;
import org.thoughtcrime.securesms.util.Util;
@ -20,7 +20,7 @@ public class AttachmentDownloadJobTest extends BaseUnitTest {
@Override
public void setUp() throws Exception {
super.setUp();
job = new AttachmentDownloadJob(mock(Context.class), 1L, new PartId(1L, 1L));
job = new AttachmentDownloadJob(mock(Context.class), 1L, new AttachmentId(1L, 1L));
}
@Test(expected = InvalidPartException.class)