diff --git a/res/drawable-hdpi/ic_download_circle_fill_white_48dp.png b/res/drawable-hdpi/ic_download_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..47d4fc7cd6
Binary files /dev/null and b/res/drawable-hdpi/ic_download_circle_fill_white_48dp.png differ
diff --git a/res/drawable-hdpi/ic_pause_circle_fill_white_48dp.png b/res/drawable-hdpi/ic_pause_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..340c65b4f4
Binary files /dev/null and b/res/drawable-hdpi/ic_pause_circle_fill_white_48dp.png differ
diff --git a/res/drawable-hdpi/ic_play_circle_fill_white_48dp.png b/res/drawable-hdpi/ic_play_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..7c0c0bc2db
Binary files /dev/null and b/res/drawable-hdpi/ic_play_circle_fill_white_48dp.png differ
diff --git a/res/drawable-mdpi/ic_download_circle_fill_white_48dp.png b/res/drawable-mdpi/ic_download_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..fbea4f468f
Binary files /dev/null and b/res/drawable-mdpi/ic_download_circle_fill_white_48dp.png differ
diff --git a/res/drawable-mdpi/ic_pause_circle_fill_white_48dp.png b/res/drawable-mdpi/ic_pause_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..55b14334b2
Binary files /dev/null and b/res/drawable-mdpi/ic_pause_circle_fill_white_48dp.png differ
diff --git a/res/drawable-mdpi/ic_play_circle_fill_white_48dp.png b/res/drawable-mdpi/ic_play_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..64c1f79ab8
Binary files /dev/null and b/res/drawable-mdpi/ic_play_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xhdpi/ic_download_circle_fill_white_48dp.png b/res/drawable-xhdpi/ic_download_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..4fcc4d38a4
Binary files /dev/null and b/res/drawable-xhdpi/ic_download_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xhdpi/ic_pause_circle_fill_white_48dp.png b/res/drawable-xhdpi/ic_pause_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..bd2c7793dd
Binary files /dev/null and b/res/drawable-xhdpi/ic_pause_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xhdpi/ic_play_circle_fill_white_48dp.png b/res/drawable-xhdpi/ic_play_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..12ed3bfcb8
Binary files /dev/null and b/res/drawable-xhdpi/ic_play_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxhdpi/ic_download_circle_fill_white_48dp.png b/res/drawable-xxhdpi/ic_download_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..6ac6077150
Binary files /dev/null and b/res/drawable-xxhdpi/ic_download_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxhdpi/ic_pause_circle_fill_white_48dp.png b/res/drawable-xxhdpi/ic_pause_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..9c5c1abd02
Binary files /dev/null and b/res/drawable-xxhdpi/ic_pause_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxhdpi/ic_play_circle_fill_white_48dp.png b/res/drawable-xxhdpi/ic_play_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..23e189c8fc
Binary files /dev/null and b/res/drawable-xxhdpi/ic_play_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_download_circle_fill_white_48dp.png b/res/drawable-xxxhdpi/ic_download_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..db6ce0ff60
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_download_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_pause_circle_fill_white_48dp.png b/res/drawable-xxxhdpi/ic_pause_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..236c1c5071
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_pause_circle_fill_white_48dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_play_circle_fill_white_48dp.png b/res/drawable-xxxhdpi/ic_play_circle_fill_white_48dp.png
new file mode 100644
index 0000000000..d08a2ed51b
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_play_circle_fill_white_48dp.png differ
diff --git a/res/layout/audio_view.xml b/res/layout/audio_view.xml
new file mode 100644
index 0000000000..a83df65ad0
--- /dev/null
+++ b/res/layout/audio_view.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index 48a4716e46..0a5ba8ed5d 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -37,13 +37,32 @@
android:background="?android:windowBackground"
android:visibility="gone">
-
+
+
+
+
+
+
+
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 4f38aaf108..3cadef9b63 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -1,12 +1,14 @@
-
+
+ tools:visibility="gone" />
+
+
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/conversation_item"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="@drawable/conversation_item_background">
+
+
+ android:layout_gravity="top|right"
+ android:src="@drawable/conversation_attachment_close_circle"
+ android:visibility="gone"/>
diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml
index fc420fd56c..1e446b0552 100644
--- a/res/layout/thumbnail_view.xml
+++ b/res/layout/thumbnail_view.xml
@@ -16,9 +16,4 @@
android:layout_gravity="center"
android:layout="@layout/transfer_controls_stub" />
-
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index cbada251db..d89c892452 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -134,4 +134,9 @@
+
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/BindableConversationItem.java b/src/org/thoughtcrime/securesms/BindableConversationItem.java
index 553b9f5ac9..c6fd33cfd9 100644
--- a/src/org/thoughtcrime/securesms/BindableConversationItem.java
+++ b/src/org/thoughtcrime/securesms/BindableConversationItem.java
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Locale;
import java.util.Set;
@@ -13,5 +14,5 @@ public interface BindableConversationItem extends Unbindable {
@NonNull MessageRecord messageRecord,
@NonNull Locale locale,
@NonNull Set batchSelected,
- boolean groupThread);
+ @NonNull Recipients recipients);
}
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index 94d310385e..9a2b599ed9 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.LRUCache;
import java.lang.ref.SoftReference;
@@ -68,12 +69,12 @@ public class ConversationAdapter
private final Set batchSelected = Collections.synchronizedSet(new HashSet());
- private final ItemClickListener clickListener;
- private final MasterSecret masterSecret;
- private final Locale locale;
- private final boolean groupThread;
- private final MmsSmsDatabase db;
- private final LayoutInflater inflater;
+ private final ItemClickListener clickListener;
+ private final MasterSecret masterSecret;
+ private final Locale locale;
+ private final Recipients recipients;
+ private final MmsSmsDatabase db;
+ private final LayoutInflater inflater;
protected static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(final @NonNull V itemView) {
@@ -96,15 +97,15 @@ public class ConversationAdapter
@NonNull Locale locale,
@Nullable ItemClickListener clickListener,
@Nullable Cursor cursor,
- boolean groupThread)
+ @NonNull Recipients recipients)
{
super(context, cursor);
- this.masterSecret = masterSecret;
- this.locale = locale;
- this.clickListener = clickListener;
- this.groupThread = groupThread;
- this.inflater = LayoutInflater.from(context);
- this.db = DatabaseFactory.getMmsSmsDatabase(context);
+ this.masterSecret = masterSecret;
+ this.locale = locale;
+ this.clickListener = clickListener;
+ this.recipients = recipients;
+ this.inflater = LayoutInflater.from(context);
+ this.db = DatabaseFactory.getMmsSmsDatabase(context);
}
@Override
@@ -118,7 +119,7 @@ public class ConversationAdapter
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
- viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, groupThread);
+ viewHolder.getView().bind(masterSecret, messageRecord, locale, batchSelected, recipients);
}
@Override public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 16cb98a5ab..81fb1c8062 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -142,8 +142,7 @@ public class ConversationFragment extends Fragment
private void initializeListAdapter() {
if (this.recipients != null && this.threadId != -1) {
- list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null,
- (!this.recipients.isSingleRecipient()) || this.recipients.isGroupRecipient()));
+ list.setAdapter(new ConversationAdapter(getActivity(), masterSecret, locale, selectionClickListener, null, this.recipients));
getLoaderManager().restartLoader(0, Bundle.EMPTY, this);
}
}
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 2b5b8c1332..a16cff52de 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -41,13 +41,14 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
+import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
-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;
@@ -58,10 +59,13 @@ import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
+import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Util;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -75,7 +79,7 @@ import java.util.Set;
*/
public class ConversationItem extends LinearLayout
- implements Recipient.RecipientModifiedListener, BindableConversationItem
+ implements Recipient.RecipientModifiedListener, Recipients.RecipientsModifiedListener, BindableConversationItem
{
private final static String TAG = ConversationItem.class.getSimpleName();
@@ -98,11 +102,13 @@ public class ConversationItem extends LinearLayout
private View pendingIndicator;
private ImageView pendingApprovalIndicator;
- private StatusManager statusManager;
- private Set batchSelected;
- private ThumbnailView mediaThumbnail;
- private Button mmsDownloadButton;
- private TextView mmsDownloadingLabel;
+ private @NonNull Set batchSelected = new HashSet<>();
+ private @Nullable Recipients conversationRecipients;
+ private @NonNull StatusManager statusManager;
+ private @NonNull ThumbnailView mediaThumbnail;
+ private @NonNull AudioView audioView;
+ private @NonNull Button mmsDownloadButton;
+ private @NonNull TextView mmsDownloadingLabel;
private int defaultBubbleColor;
@@ -152,15 +158,20 @@ public class ConversationItem extends LinearLayout
this.pendingApprovalIndicator = (ImageView) findViewById(R.id.pending_approval_indicator);
this.pendingIndicator = findViewById(R.id.pending_indicator);
this.mediaThumbnail = (ThumbnailView) findViewById(R.id.image_view);
+ this.audioView = (AudioView) findViewById(R.id.audio_view);
this.statusManager = new StatusManager(pendingIndicator, sentIndicator, deliveredIndicator, failedIndicator, pendingApprovalIndicator);
setOnClickListener(new ClickListener(null));
- PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
- if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
+ PassthroughClickListener passthroughClickListener = new PassthroughClickListener();
+ AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener();
+
+ mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
- mediaThumbnail.setDownloadClickListener(new ThumbnailDownloadClickListener());
+ mediaThumbnail.setDownloadClickListener(downloadClickListener);
mediaThumbnail.setOnLongClickListener(passthroughClickListener);
mediaThumbnail.setOnClickListener(passthroughClickListener);
+ audioView.setDownloadClickListener(downloadClickListener);
+ audioView.setOnLongClickListener(passthroughClickListener);
bodyText.setOnLongClickListener(passthroughClickListener);
bodyText.setOnClickListener(passthroughClickListener);
}
@@ -170,16 +181,18 @@ public class ConversationItem extends LinearLayout
@NonNull MessageRecord messageRecord,
@NonNull Locale locale,
@NonNull Set batchSelected,
- boolean groupThread)
+ @NonNull Recipients conversationRecipients)
{
this.masterSecret = masterSecret;
this.messageRecord = messageRecord;
this.locale = locale;
this.batchSelected = batchSelected;
- this.groupThread = groupThread;
+ this.conversationRecipients = conversationRecipients;
+ this.groupThread = !conversationRecipients.isSingleRecipient() || conversationRecipients.isGroupRecipient();
this.recipient = messageRecord.getIndividualRecipient();
this.recipient.addListener(this);
+ this.conversationRecipients.addListener(this);
setInteractionState(messageRecord);
setBodyText(messageRecord);
@@ -218,6 +231,7 @@ public class ConversationItem extends LinearLayout
if (messageRecord.isOutgoing()) {
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
mediaThumbnail.setBackgroundColorHint(defaultBubbleColor);
+ audioView.setTint(conversationRecipients.getColor().toConversationColor(context));
} else {
int color = recipient.getColor().toConversationColor(context);
bodyBubble.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
@@ -237,7 +251,13 @@ public class ConversationItem extends LinearLayout
return TextUtils.isEmpty(messageRecord.getDisplayBody()) && messageRecord.isMms();
}
- private boolean hasMedia(MessageRecord messageRecord) {
+ private boolean hasAudio(MessageRecord messageRecord) {
+ return messageRecord.isMms() &&
+ !messageRecord.isMmsNotification() &&
+ ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null;
+ }
+
+ private boolean hasThumbnail(MessageRecord messageRecord) {
return messageRecord.isMms() &&
!messageRecord.isMmsNotification() &&
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null;
@@ -256,20 +276,33 @@ public class ConversationItem extends LinearLayout
}
private void setMediaAttributes(MessageRecord messageRecord) {
+ boolean showControls = !messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending());
+
if (messageRecord.isMmsNotification()) {
mediaThumbnail.setVisibility(View.GONE);
+ audioView.setVisibility(View.GONE);
+
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
setNotificationMmsAttributes((NotificationMmsMessageRecord) messageRecord);
- } else if (hasMedia(messageRecord)) {
+ } else if (hasAudio(messageRecord)) {
+ audioView.setVisibility(View.VISIBLE);
+ mediaThumbnail.setVisibility(View.GONE);
+
+ //noinspection ConstantConditions
+ audioView.setAudio(masterSecret, ((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
+ bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ } else if (hasThumbnail(messageRecord)) {
mediaThumbnail.setVisibility(View.VISIBLE);
+ audioView.setVisibility(View.GONE);
+
//noinspection ConstantConditions
mediaThumbnail.setImageResource(masterSecret,
((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
- !messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending()),
- false);
+ showControls);
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
mediaThumbnail.setVisibility(View.GONE);
+ audioView.setVisibility(View.GONE);
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
@@ -400,7 +433,12 @@ public class ConversationItem extends LinearLayout
});
}
- private class ThumbnailDownloadClickListener implements ThumbnailView.ThumbnailClickListener {
+ @Override
+ public void onModified(Recipients recipient) {
+ onModified(recipient.getPrimaryRecipient());
+ }
+
+ private class AttachmentDownloadClickListener implements SlideClickListener {
@Override public void onClick(View v, final Slide slide) {
DatabaseFactory.getAttachmentDatabase(context).setTransferState(messageRecord.getId(),
slide.asAttachment(),
@@ -408,7 +446,7 @@ public class ConversationItem extends LinearLayout
}
}
- private class ThumbnailClickListener implements ThumbnailView.ThumbnailClickListener {
+ private class ThumbnailClickListener implements SlideClickListener {
private void fireIntent(Slide slide) {
Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
Intent intent = new Intent(Intent.ACTION_VIEW);
diff --git a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java
index b42def811d..c31b8b68a3 100644
--- a/src/org/thoughtcrime/securesms/ConversationUpdateItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationUpdateItem.java
@@ -56,7 +56,7 @@ public class ConversationUpdateItem extends LinearLayout
@NonNull MessageRecord messageRecord,
@NonNull Locale locale,
@NonNull Set batchSelected,
- boolean groupThread)
+ @NonNull Recipients conversationRecipients)
{
bind(messageRecord, locale);
}
diff --git a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
index cd27ca41fe..3d51d6bf51 100644
--- a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
+++ b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
@@ -70,7 +70,7 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
Slide slide = MediaUtil.getSlideForAttachment(getContext(), imageRecord.getAttachment());
if (slide != null) {
- imageView.setImageResource(masterSecret, slide, false, false);
+ imageView.setImageResource(masterSecret, slide, false);
}
imageView.setOnClickListener(new OnMediaClickListener(imageRecord));
diff --git a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
index bd59046fec..45ffce71fd 100644
--- a/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
+++ b/src/org/thoughtcrime/securesms/MessageDetailsActivity.java
@@ -172,8 +172,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
}
toFrom.setText(toFromRes);
conversationItem.bind(masterSecret, messageRecord, dynamicLanguage.getCurrentLocale(),
- new HashSet(),
- recipients != messageRecord.getRecipients());
+ new HashSet(), recipients);
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, masterSecret, messageRecord,
recipients, isPushGroup));
}
diff --git a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
index 8c2749878b..c7b73bd528 100644
--- a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
@@ -13,15 +13,15 @@ import java.io.InputStream;
public class UriAttachment extends Attachment {
- private final Uri dataUri;
- private final Uri thumbnailUri;
+ private final @NonNull Uri dataUri;
+ private final @NonNull Uri thumbnailUri;
- public UriAttachment(Uri uri, String contentType, int transferState, long size) {
+ public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size) {
this(uri, uri, contentType, transferState, size);
}
- public UriAttachment(Uri dataUri, Uri thumbnailUri,
- String contentType, int transferState, long size)
+ public UriAttachment(@NonNull Uri dataUri, @NonNull Uri thumbnailUri,
+ @NonNull String contentType, int transferState, long size)
{
super(contentType, transferState, size, null, null, null);
this.dataUri = dataUri;
@@ -39,4 +39,14 @@ public class UriAttachment extends Attachment {
public Uri getThumbnailUri() {
return thumbnailUri;
}
+
+ @Override
+ public boolean equals(Object other) {
+ return other != null && other instanceof UriAttachment && ((UriAttachment) other).dataUri.equals(this.dataUri);
+ }
+
+ @Override
+ public int hashCode() {
+ return dataUri.hashCode();
+ }
}
diff --git a/src/org/thoughtcrime/securesms/audio/AudioAttachmentServer.java b/src/org/thoughtcrime/securesms/audio/AudioAttachmentServer.java
new file mode 100644
index 0000000000..31e67a767e
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/audio/AudioAttachmentServer.java
@@ -0,0 +1,376 @@
+package org.thoughtcrime.securesms.audio;
+
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import org.spongycastle.util.encoders.Hex;
+import org.thoughtcrime.securesms.attachments.Attachment;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.mms.PartAuthority;
+import org.thoughtcrime.securesms.util.Util;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.MessageDigest;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+/**
+ * @author Stefan "frostymarvelous" Froelich
+ */
+public class AudioAttachmentServer implements Runnable {
+
+ private static final String TAG = AudioAttachmentServer.class.getSimpleName();
+
+ private final Context context;
+ private final MasterSecret masterSecret;
+ private final Attachment attachment;
+ private final ServerSocket socket;
+ private final int port;
+ private final String auth;
+
+ private volatile boolean isRunning;
+
+ public AudioAttachmentServer(Context context, MasterSecret masterSecret, Attachment attachment)
+ throws IOException
+ {
+ try {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.attachment = attachment;
+ this.socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1}));
+ this.port = socket.getLocalPort();
+ this.auth = new String(Hex.encode(Util.getSecretBytes(16)));
+
+ this.socket.setSoTimeout(5000);
+ } catch (UnknownHostException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public Uri getUri() {
+ return Uri.parse(String.format("http://127.0.0.1:%d/%s", port, auth));
+ }
+
+ public void start() {
+ isRunning = true;
+ new Thread(this).start();
+ }
+
+ public void stop() {
+ isRunning = false;
+ }
+
+ @Override
+ public void run() {
+ while (isRunning) {
+ Socket client = null;
+
+ try {
+ client = socket.accept();
+
+ if (client != null) {
+ StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client, "/" + auth);
+
+ if (task.processRequest()) {
+ task.execute();
+ }
+ }
+
+ } catch (SocketTimeoutException e) {
+ Log.w(TAG, e);
+ } catch (IOException e) {
+ Log.e(TAG, "Error connecting to client", e);
+ } finally {
+ try {if (client != null) client.close();} catch (IOException e) {}
+ }
+ }
+
+ Log.d(TAG, "Proxy interrupted. Shutting down.");
+ }
+
+
+ private class StreamToMediaPlayerTask {
+
+ private final @NonNull Socket client;
+ private final @NonNull String auth;
+
+ private long cbSkip;
+ private Properties parameters;
+ private Properties request;
+ private Properties requestHeaders;
+// private String filePath;
+
+ public StreamToMediaPlayerTask(@NonNull Socket client, @NonNull String auth) {
+ this.client = client;
+ this.auth = auth;
+ }
+
+ public boolean processRequest() throws IOException {
+ InputStream is = client.getInputStream();
+ final int bufferSize = 8192;
+ byte[] buffer = new byte[bufferSize];
+ int splitByte = 0;
+ int readLength = 0;
+
+ {
+ int read = is.read(buffer, 0, bufferSize);
+ while (read > 0) {
+ readLength += read;
+ splitByte = findHeaderEnd(buffer, readLength);
+ if (splitByte > 0)
+ break;
+ read = is.read(buffer, readLength, bufferSize - readLength);
+ }
+ }
+
+ // Create a BufferedReader for parsing the header.
+ ByteArrayInputStream hbis = new ByteArrayInputStream(buffer, 0, readLength);
+ BufferedReader hin = new BufferedReader(new InputStreamReader(hbis));
+
+ request = new Properties();
+ parameters = new Properties();
+ requestHeaders = new Properties();
+
+ try {
+ decodeHeader(hin, request, parameters, requestHeaders);
+ } catch (InterruptedException e1) {
+ Log.e(TAG, "Exception: " + e1.getMessage());
+ e1.printStackTrace();
+ }
+
+ for (Map.Entry