diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 16103cbaaf..b9c2a5736f 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -45,6 +45,9 @@
+
+
diff --git a/build.gradle b/build.gradle
index 35230b7629..765f1a4222 100644
--- a/build.gradle
+++ b/build.gradle
@@ -38,6 +38,7 @@ dependencies {
compile 'org.w3c:smil:1.0.0'
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
+ compile 'com.github.bumptech.glide:glide:3.5.2'
compile 'com.makeramen:roundedimageview:1.5.0'
compile ('com.afollestad:material-dialogs:0.6.4.7') {
exclude module: 'appcompat-v7'
@@ -88,6 +89,7 @@ dependencyVerification {
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
+ 'com.github.bumptech.glide:glide:16936352b96aa604b030f2d2263fb0cc656933e6cdda0bf6ac681282d1f7f196',
'com.makeramen:roundedimageview:7dda2e78c406760e5c356ccce59b0df46b5b171cf18abb891998594405021548',
'com.afollestad:material-dialogs:6ed57c1c479219f8ae929c310fc171dbfcbcee8326a6dcd50a91959d69eccdf0',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
@@ -167,6 +169,7 @@ android {
'proguard-square-okio.pro',
'proguard-spongycastle.pro',
'proguard-rounded-image-view.pro',
+ 'proguard-glide.pro',
'proguard.cfg'
}
release {
@@ -181,6 +184,7 @@ android {
'proguard-square-okio.pro',
'proguard-spongycastle.pro',
'proguard-rounded-image-view.pro',
+ 'proguard-glide.pro',
'proguard.cfg'
signingConfig signingConfigs.release
}
diff --git a/proguard-glide.pro b/proguard-glide.pro
new file mode 100644
index 0000000000..0588677f33
--- /dev/null
+++ b/proguard-glide.pro
@@ -0,0 +1,4 @@
+-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index 9fe067a22e..4f17ef2bf0 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -36,7 +36,7 @@
android:orientation="horizontal"
android:visibility="gone">
-
-
-
-
#ff2090ea
- #ff145c95
+ #ff1c7ac5
#ffffffff
#ff000000
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 3b5136a2db..0d89130e4f 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -22,16 +22,12 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
import android.provider.ContactsContract;
import android.provider.ContactsContract.QuickContact;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Pair;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
@@ -43,7 +39,7 @@ import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
import org.thoughtcrime.securesms.components.BubbleContainer;
-import org.thoughtcrime.securesms.components.ForegroundImageView;
+import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -58,12 +54,9 @@ 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.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Emoji;
-import org.thoughtcrime.securesms.util.FutureTaskListener;
-import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.Set;
@@ -97,16 +90,10 @@ public class ConversationItem extends LinearLayout {
private Set batchSelected;
private SelectionClickListener selectionClickListener;
- private ForegroundImageView mediaThumbnail;
+ private ThumbnailView mediaThumbnail;
private Button mmsDownloadButton;
private TextView mmsDownloadingLabel;
- private ListenableFutureTask slideDeckFuture;
- private ListenableFutureTask> thumbnailFuture;
- private SlideDeckListener slideDeckListener;
- private ThumbnailListener thumbnailListener;
- private Handler handler;
-
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
private final ClickListener clickListener = new ClickListener();
@@ -139,14 +126,14 @@ public class ConversationItem extends LinearLayout {
this.bodyBubble = findViewById(R.id.body_bubble);
this.pendingIndicator = (ImageView)findViewById(R.id.pending_approval_indicator);
this.bubbleContainer = (BubbleContainer)findViewById(R.id.bubble);
- this.mediaThumbnail = (ForegroundImageView)findViewById(R.id.image_view);
-
- slideDeckListener = new SlideDeckListener();
- handler = new Handler(Looper.getMainLooper());
+ this.mediaThumbnail = (ThumbnailView)findViewById(R.id.image_view);
setOnClickListener(clickListener);
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
- if (mediaThumbnail != null) mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
+ if (mediaThumbnail != null) {
+ mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
+ mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
+ }
}
public void set(@NonNull MasterSecret masterSecret,
@@ -177,13 +164,6 @@ public class ConversationItem extends LinearLayout {
}
public void unbind() {
- if (slideDeckFuture != null && slideDeckListener != null) {
- slideDeckFuture.removeListener(slideDeckListener);
- }
-
- if (thumbnailFuture != null && thumbnailListener != null) {
- thumbnailFuture.removeListener(thumbnailListener);
- }
}
public MessageRecord getMessageRecord() {
@@ -376,8 +356,7 @@ public class ConversationItem extends LinearLayout {
private void resolveMedia(MediaMmsMessageRecord messageRecord) {
if (hasMedia(messageRecord)) {
- slideDeckFuture = messageRecord.getSlideDeckFuture();
- slideDeckFuture.addListener(slideDeckListener);
+ mediaThumbnail.setImageResource(messageRecord.getSlideDeckFuture(), masterSecret);
}
}
@@ -429,14 +408,8 @@ public class ConversationItem extends LinearLayout {
context.startActivity(intent);
}
- private class ThumbnailClickListener implements View.OnClickListener {
- private final Slide slide;
-
- public ThumbnailClickListener(Slide slide) {
- this.slide = slide;
- }
-
- private void fireIntent() {
+ private class ThumbnailClickListener implements ThumbnailView.ThumbnailClickListener {
+ private void fireIntent(Slide slide) {
Log.w(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -449,7 +422,7 @@ public class ConversationItem extends LinearLayout {
}
}
- public void onClick(View v) {
+ public void onClick(final View v, final Slide slide) {
if (!batchSelected.isEmpty()) {
selectionClickListener.onItemClick(null, ConversationItem.this, -1, -1);
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType())) {
@@ -468,7 +441,7 @@ public class ConversationItem extends LinearLayout {
builder.setMessage(R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
- fireIntent();
+ fireIntent(slide);
}
});
builder.setNegativeButton(R.string.no, null);
@@ -587,73 +560,4 @@ public class ConversationItem extends LinearLayout {
});
builder.show();
}
-
- private class ThumbnailListener implements FutureTaskListener> {
- private final Object tag;
-
- public ThumbnailListener(Object tag) {
- this.tag = tag;
- }
-
- @Override
- public void onSuccess(final Pair result) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if (mediaThumbnail.getTag() == tag) {
- Log.w(TAG, "displaying media thumbnail");
- mediaThumbnail.show(result.first, result.second);
- }
- }
- });
- }
-
- @Override
- public void onFailure(Throwable error) {
- Log.w(TAG, error);
- handler.post(new Runnable() {
- @Override
- public void run() {
- mediaThumbnail.hide();
- }
- });
- }
- }
-
- private class SlideDeckListener implements FutureTaskListener {
- @Override
- public void onSuccess(final SlideDeck slideDeck) {
- if (slideDeck == null) return;
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- Slide slide = slideDeck.getThumbnailSlide(context);
- if (slide != null) {
- thumbnailFuture = slide.getThumbnail(context);
- if (thumbnailFuture != null) {
- Object tag = new Object();
- mediaThumbnail.setTag(tag);
- thumbnailListener = new ThumbnailListener(tag);
- thumbnailFuture.addListener(thumbnailListener);
- mediaThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
- return;
- }
- }
- mediaThumbnail.hide();
- }
- });
- }
-
- @Override
- public void onFailure(Throwable error) {
- Log.w(TAG, error);
- handler.post(new Runnable() {
- @Override
- public void run() {
- mediaThumbnail.hide();
- }
- });
- }
- }
}
diff --git a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
index 2670172e4d..bd037ff8bb 100644
--- a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
+++ b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
@@ -19,25 +19,21 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
-import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.ImageMediaAdapter.ViewHolder;
-import org.thoughtcrime.securesms.components.ForegroundImageView;
+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.mms.Slide;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.MediaUtil;
import ws.com.google.android.mms.pdu.PduPart;
@@ -46,21 +42,19 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
private static final String TAG = ImageMediaAdapter.class.getSimpleName();
private final MasterSecret masterSecret;
- private final int gridSize;
public static class ViewHolder extends RecyclerView.ViewHolder {
- public ForegroundImageView imageView;
+ public ThumbnailView imageView;
public ViewHolder(View v) {
super(v);
- imageView = (ForegroundImageView) v.findViewById(R.id.image);
+ imageView = (ThumbnailView) v.findViewById(R.id.image);
}
}
public ImageMediaAdapter(Context context, MasterSecret masterSecret, Cursor c) {
super(context, c);
this.masterSecret = masterSecret;
- this.gridSize = context.getResources().getDimensionPixelSize(R.dimen.thumbnail_max_size);
}
@Override
@@ -71,8 +65,8 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final Cursor cursor) {
- final ForegroundImageView imageView = viewHolder.imageView;
- final ImageRecord imageRecord = ImageRecord.from(cursor);
+ final ThumbnailView imageView = viewHolder.imageView;
+ final ImageRecord imageRecord = ImageRecord.from(cursor);
PduPart part = new PduPart();
@@ -80,25 +74,9 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
part.setContentType(imageRecord.getContentType().getBytes());
part.setId(imageRecord.getPartId());
- imageView.setVisibility(View.INVISIBLE);
Slide slide = MediaUtil.getSlideForPart(getContext(), masterSecret, part, imageRecord.getContentType());
if (slide != null) {
- slide.getThumbnail(getContext()).addListener(new FutureTaskListener>() {
- @Override
- public void onSuccess(final Pair result) {
- imageView.post(new Runnable() {
- @Override
- public void run() {
- imageView.show(result.first, false);
- }
- });
- }
-
- @Override
- public void onFailure(Throwable error) {
- Log.w(TAG, error);
- }
- });
+ imageView.setImageResource(slide, masterSecret);
}
imageView.setOnClickListener(new OnMediaClickListener(imageRecord));
diff --git a/src/org/thoughtcrime/securesms/components/BubbleContainer.java b/src/org/thoughtcrime/securesms/components/BubbleContainer.java
index da352ad9e5..9ba12b65c5 100644
--- a/src/org/thoughtcrime/securesms/components/BubbleContainer.java
+++ b/src/org/thoughtcrime/securesms/components/BubbleContainer.java
@@ -22,7 +22,6 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
-import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.util.AttributeSet;
import android.view.View;
@@ -51,11 +50,11 @@ public abstract class BubbleContainer extends RelativeLayout {
@IntDef({MEDIA_STATE_NO_MEDIA, MEDIA_STATE_CAPTIONLESS, MEDIA_STATE_CAPTIONED})
public @interface MediaState {}
- private View bodyBubble;
- private View triangleTick;
- private ForegroundImageView media;
- private int shadowColor;
- private int mmsPendingOverlayColor;
+ private View bodyBubble;
+ private View triangleTick;
+ private ThumbnailView media;
+ private int shadowColor;
+ private int mmsPendingOverlayColor;
public BubbleContainer(Context context) {
super(context);
@@ -88,7 +87,7 @@ public abstract class BubbleContainer extends RelativeLayout {
onCreateView();
this.bodyBubble = findViewById(R.id.body_bubble );
this.triangleTick = findViewById(R.id.triangle_tick);
- this.media = (ForegroundImageView) findViewById(R.id.image_view);
+ this.media = (ThumbnailView) findViewById(R.id.image_view);
this.shadowColor = ResUtil.getColor(getContext(), R.attr.conversation_item_shadow);
this.mmsPendingOverlayColor = ResUtil.getColor(getContext(), R.attr.conversation_item_mms_pending_mask);
diff --git a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java b/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
index f1cf42a462..83837a183d 100644
--- a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
+++ b/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
@@ -23,12 +23,11 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
-import android.view.animation.AlphaAnimation;
import com.makeramen.RoundedImageView;
@@ -46,9 +45,9 @@ public class ForegroundImageView extends RoundedImageView {
private int mForegroundGravity = Gravity.FILL;
- protected boolean mForegroundInPadding = true;
+ private boolean mForegroundInPadding = true;
- boolean mForegroundBoundsChanged = false;
+ private boolean mForegroundBoundsChanged = false;
public ForegroundImageView(Context context) {
super(context);
@@ -123,54 +122,15 @@ public class ForegroundImageView extends RoundedImageView {
return ActivityOptions.makeScaleUpAnimation(this, 0, 0, getWidth(), getHeight());
}
- public void show(Drawable drawable, boolean instantaneous) {
- setImageDrawable(drawable);
- if (drawable.getIntrinsicHeight() < (getHeight() * 0.75f) &&
- drawable.getIntrinsicWidth() < (getHeight() * 0.75f))
- {
- setScaleType(ScaleType.CENTER_INSIDE);
- } else {
- setScaleType(ScaleType.CENTER_CROP);
- }
- fadeIn(instantaneous ? 0 : 200);
- }
-
public void reset() {
- cancelAnimations();
setImageDrawable(null);
- setVisibility(View.INVISIBLE);
+ setVisibility(View.VISIBLE);
}
public void hide() {
setVisibility(View.GONE);
}
- private void fadeIn(final long millis) {
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) fadeInModern(millis);
- else fadeInLegacy(millis);
- setVisibility(View.VISIBLE);
- }
-
- private void fadeInLegacy(final long millis) {
- final AlphaAnimation alpha = new AlphaAnimation(0f, 1f);
- alpha.setDuration(millis);
- alpha.setFillAfter(true);
- startAnimation(alpha);
- }
-
- @TargetApi(VERSION_CODES.JELLY_BEAN)
- private void fadeInModern(final long millis) {
- setAlpha(0f);
- animate().alpha(1f).setDuration(millis);
- }
-
- private void cancelAnimations() {
- if (getAnimation() != null) {
- getAnimation().cancel();
- clearAnimation();
- }
- }
-
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
@@ -249,7 +209,7 @@ public class ForegroundImageView extends RoundedImageView {
}
@Override
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
@@ -278,5 +238,4 @@ public class ForegroundImageView extends RoundedImageView {
foreground.draw(canvas);
}
}
-
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
new file mode 100644
index 0000000000..5a98fe2415
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
@@ -0,0 +1,202 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.bumptech.glide.GenericRequestBuilder;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
+import org.thoughtcrime.securesms.mms.Slide;
+import org.thoughtcrime.securesms.mms.SlideDeck;
+import org.thoughtcrime.securesms.mms.ThumbnailTransform;
+import org.thoughtcrime.securesms.util.FutureTaskListener;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
+
+import ws.com.google.android.mms.pdu.PduPart;
+
+public class ThumbnailView extends ForegroundImageView {
+ private ListenableFutureTask slideDeckFuture = null;
+ private SlideDeckListener slideDeckListener = null;
+ private ThumbnailClickListener thumbnailClickListener = null;
+ private Handler handler = new Handler();
+
+ public ThumbnailView(Context context) {
+ super(context);
+ }
+
+ public ThumbnailView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ThumbnailView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setImageResource(@NonNull ListenableFutureTask slideDeckFuture,
+ @Nullable MasterSecret masterSecret)
+ {
+ if (this.slideDeckFuture != null && this.slideDeckListener != null) {
+ this.slideDeckFuture.removeListener(this.slideDeckListener);
+ }
+
+ this.slideDeckListener = new SlideDeckListener(masterSecret);
+ this.slideDeckFuture = slideDeckFuture;
+ this.slideDeckFuture.addListener(this.slideDeckListener);
+ }
+
+ public void setImageResource(@NonNull Slide slide, @Nullable MasterSecret masterSecret) {
+ buildGlideRequest(slide, masterSecret).into(ThumbnailView.this);
+ setOnClickListener(new ThumbnailClickDispatcher(thumbnailClickListener, slide));
+ }
+
+ public void setImageResource(@NonNull Slide slide)
+ {
+ setImageResource(slide, null);
+ }
+
+ public void setThumbnailClickListener(ThumbnailClickListener listener) {
+ this.thumbnailClickListener = listener;
+ }
+
+ private GenericRequestBuilder buildGlideRequest(@NonNull Slide slide,
+ @Nullable MasterSecret masterSecret)
+ {
+ final GenericRequestBuilder builder;
+ if (slide.getPart().isPendingPush()) {
+ builder = buildPendingGlideRequest(slide);
+ } else if (slide.getThumbnailUri() != null) {
+ builder = buildThumbnailGlideRequest(slide, masterSecret);
+ } else {
+ builder = buildPlaceholderGlideRequest(slide);
+ }
+
+ return builder.error(R.drawable.ic_missing_thumbnail_picture);
+ }
+
+ private GenericRequestBuilder buildPendingGlideRequest(Slide slide) {
+ return Glide.with(getContext()).load(R.drawable.stat_sys_download_anim0)
+ .dontTransform()
+ .skipMemoryCache(true)
+ .crossFade();
+ }
+
+ private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) {
+
+ final GenericRequestBuilder builder;
+ if (slide.isDraft()) builder = buildDraftGlideRequest(slide);
+ else builder = buildEncryptedPartGlideRequest(slide, masterSecret);
+ return builder;
+ }
+
+ private GenericRequestBuilder buildDraftGlideRequest(Slide slide) {
+ return Glide.with(getContext()).load(slide.getThumbnailUri()).asBitmap()
+ .fitCenter()
+ .listener(new PduThumbnailSetListener(slide.getPart()));
+ }
+
+ private GenericRequestBuilder buildEncryptedPartGlideRequest(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 ThumbnailTransform(getContext()));
+ }
+
+ private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) {
+ return Glide.with(getContext()).load(slide.getPlaceholderRes(getContext().getTheme()))
+ .fitCenter()
+ .crossFade();
+ }
+
+ private class SlideDeckListener implements FutureTaskListener {
+ private final MasterSecret masterSecret;
+
+ public SlideDeckListener(MasterSecret masterSecret) {
+ this.masterSecret = masterSecret;
+ }
+
+ @Override
+ public void onSuccess(final SlideDeck slideDeck) {
+ if (slideDeck == null) return;
+
+ final Slide slide = slideDeck.getThumbnailSlide(getContext());
+ if (slide != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ setImageResource(slide, masterSecret);
+ }
+ });
+ } else {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ hide();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ Log.w(TAG, error);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ hide();
+ }
+ });
+ }
+ }
+
+ public interface ThumbnailClickListener {
+ void onClick(View v, Slide slide);
+ }
+
+ private class ThumbnailClickDispatcher implements View.OnClickListener {
+ private ThumbnailClickListener listener;
+ private Slide slide;
+
+ public ThumbnailClickDispatcher(ThumbnailClickListener listener, Slide slide) {
+ this.listener = listener;
+ this.slide = slide;
+ }
+
+ @Override
+ public void onClick(View view) {
+ listener.onClick(view, slide);
+ }
+ }
+
+ private static class PduThumbnailSetListener implements RequestListener {
+ private PduPart part;
+
+ public PduThumbnailSetListener(@NonNull PduPart part) {
+ this.part = part;
+ }
+
+ @Override
+ public boolean onException(Exception e, Uri model, Target target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Bitmap resource, Uri model, Target target, boolean isFromMemoryCache, boolean isFirstResource) {
+ part.setThumbnail(resource);
+ return false;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 38e70072b0..ecd3c19fa1 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -20,21 +20,17 @@ import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Build;
-import android.util.Log;
import android.provider.ContactsContract;
-import android.util.Pair;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
-import org.thoughtcrime.securesms.util.FutureTaskListener;
import java.io.IOException;
@@ -43,14 +39,14 @@ public class AttachmentManager {
private final Context context;
private final View attachmentView;
- private final ImageView thumbnail;
+ private final ThumbnailView thumbnail;
private final Button removeButton;
private final SlideDeck slideDeck;
private final AttachmentListener attachmentListener;
public AttachmentManager(Activity view, AttachmentListener listener) {
this.attachmentView = (View)view.findViewById(R.id.attachment_editor);
- this.thumbnail = (ImageView)view.findViewById(R.id.attachment_thumbnail);
+ this.thumbnail = (ThumbnailView)view.findViewById(R.id.attachment_thumbnail);
this.removeButton = (Button)view.findViewById(R.id.remove_image_button);
this.slideDeck = new SlideDeck();
this.context = view;
@@ -66,7 +62,7 @@ public class AttachmentManager {
}
public void setImage(Uri image) throws IOException, BitmapDecodingException {
- setMedia(new ImageSlide(context, image), 345, 261);
+ setMedia(new ImageSlide(context, image));
}
public void setVideo(Uri video) throws IOException, MediaTooLargeException {
@@ -77,32 +73,11 @@ public class AttachmentManager {
setMedia(new AudioSlide(context, audio));
}
- public void setMedia(final Slide slide, final int thumbnailWidth, final int thumbnailHeight) {
+ public void setMedia(final Slide slide) {
slideDeck.clear();
slideDeck.addSlide(slide);
- slide.getThumbnail(context).addListener(new FutureTaskListener>() {
- @Override
- public void onSuccess(final Pair result) {
- thumbnail.post(new Runnable() {
- @Override
- public void run() {
- thumbnail.setImageDrawable(result.first);
- attachmentView.setVisibility(View.VISIBLE);
- attachmentListener.onAttachmentChanged();
- }
- });
- }
-
- @Override
- public void onFailure(Throwable error) {
- Log.w(TAG, error);
- slideDeck.clear();
- }
- });
- }
-
- public void setMedia(Slide slide) {
- setMedia(slide, thumbnail.getWidth(), thumbnail.getHeight());
+ attachmentView.setVisibility(View.VISIBLE);
+ thumbnail.setImageResource(slide);
}
public boolean isAttachmentPresent() {
diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
index 94ae28127b..f51ce570d9 100644
--- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
@@ -16,20 +16,20 @@
*/
package org.thoughtcrime.securesms.mms;
-import java.io.IOException;
+import android.content.Context;
+import android.content.res.Resources.Theme;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio;
+import android.support.annotation.DrawableRes;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.ResUtil;
+import java.io.IOException;
+
import ws.com.google.android.mms.pdu.PduPart;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.MediaStore.Audio;
-import android.util.Pair;
public class AudioSlide extends Slide {
@@ -52,8 +52,8 @@ public class AudioSlide extends Slide {
}
@Override
- public ListenableFutureTask> getThumbnail(Context context) {
- return new ListenableFutureTask<>(new Pair<>(ResUtil.getDrawable(context, R.attr.conversation_icon_attach_audio), true));
+ public @DrawableRes int getPlaceholderRes(Theme theme) {
+ return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_audio);
}
public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException {
diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java
new file mode 100644
index 0000000000..e258fdf9a6
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamLocalUriFetcher.java
@@ -0,0 +1,36 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.bumptech.glide.load.data.StreamLocalUriFetcher;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+
+import java.io.FileNotFoundException;
+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 MasterSecret masterSecret;
+
+ public DecryptableStreamLocalUriFetcher(Context context, MasterSecret masterSecret, Uri uri) {
+ super(context, uri);
+ this.context = context;
+ this.masterSecret = masterSecret;
+ }
+
+ @Override
+ protected InputStream loadResource(Uri uri, ContentResolver contentResolver) throws FileNotFoundException {
+ try {
+ return PartAuthority.getPartStream(context, masterSecret, uri);
+ } catch (IOException ioe) {
+ Log.w(TAG, ioe);
+ throw new FileNotFoundException("PartAuthority couldn't load Uri resource.");
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java
new file mode 100644
index 0000000000..bfb3a0a09e
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/DecryptableStreamUriLoader.java
@@ -0,0 +1,60 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.GenericLoaderFactory;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.stream.StreamModelLoader;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
+
+import java.io.InputStream;
+
+/**
+ * A {@link ModelLoader} for translating uri models into {@link InputStream} data. Capable of handling 'http',
+ * 'https', 'android.resource', 'content', and 'file' schemes. Unsupported schemes will throw an exception in
+ * {@link #getResourceFetcher(Uri, int, int)}.
+ */
+public class DecryptableStreamUriLoader implements StreamModelLoader {
+ private final Context context;
+
+ /**
+ * THe default factory for {@link com.bumptech.glide.load.model.stream.StreamUriLoader}s.
+ */
+ public static class Factory implements ModelLoaderFactory {
+
+ @Override
+ public StreamModelLoader build(Context context, GenericLoaderFactory factories) {
+ return new DecryptableStreamUriLoader(context);
+ }
+
+ @Override
+ public void teardown() {
+ // Do nothing.
+ }
+ }
+
+ public DecryptableStreamUriLoader(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public DataFetcher getResourceFetcher(DecryptableUri model, int width, int height) {
+ return new DecryptableStreamLocalUriFetcher(context, model.masterSecret, model.uri);
+ }
+
+ public static class DecryptableUri {
+ public MasterSecret masterSecret;
+ public Uri uri;
+
+ public DecryptableUri(MasterSecret masterSecret, Uri uri) {
+ this.masterSecret = masterSecret;
+ this.uri = uri;
+ }
+ }
+}
+
diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
index f57c5884f5..63e3a652c7 100644
--- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
@@ -17,27 +17,15 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
+import android.content.res.Resources.Theme;
import android.net.Uri;
-import android.util.Log;
-import android.util.Pair;
+import android.support.annotation.DrawableRes;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.database.MmsDatabase;
-import org.thoughtcrime.securesms.util.BitmapDecodingException;
-import org.thoughtcrime.securesms.util.LRUCache;
-import org.thoughtcrime.securesms.util.ListenableFutureTask;
-import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.util.BitmapDecodingException;
import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.Callable;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
@@ -45,10 +33,6 @@ import ws.com.google.android.mms.pdu.PduPart;
public class ImageSlide extends Slide {
private static final String TAG = ImageSlide.class.getSimpleName();
- private static final int MAX_CACHE_SIZE = 10;
- private static final Map> thumbnailCache =
- Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE));
-
public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) {
super(context, masterSecret, part);
}
@@ -58,68 +42,21 @@ public class ImageSlide extends Slide {
}
@Override
- public ListenableFutureTask> getThumbnail(Context context) {
- if (getPart().isPendingPush()) {
- return new ListenableFutureTask<>(new Pair<>(context.getResources().getDrawable(R.drawable.stat_sys_download), true));
- }
-
- Drawable thumbnail = getCachedThumbnail();
- if (thumbnail != null) {
- Log.w(TAG, "getThumbnail() returning cached thumbnail");
- return new ListenableFutureTask<>(new Pair<>(thumbnail, true));
- }
-
- Log.w(TAG, "getThumbnail() resolving thumbnail, as it wasn't cached");
- return resolveThumbnail(context);
- }
-
- private ListenableFutureTask> resolveThumbnail(Context context) {
- final WeakReference weakContext = new WeakReference<>(context);
-
- Callable> slideCallable = new Callable>() {
- @Override
- public Pair call() throws Exception {
- final Context context = weakContext.get();
- if (context == null) {
- Log.w(TAG, "context SoftReference was null, leaving");
- return null;
- }
-
- try {
- final long startDecode = System.currentTimeMillis();
- final Bitmap thumbnailBitmap = MediaUtil.getOrGenerateThumbnail(context, masterSecret, part);
- final Drawable thumbnail = new BitmapDrawable(context.getResources(), thumbnailBitmap);
- Log.w(TAG, "thumbnail decode/generate time: " + (System.currentTimeMillis() - startDecode) + "ms");
-
- thumbnailCache.put(part.getDataUri(), new SoftReference<>(thumbnail));
- return new Pair<>(thumbnail, false);
- } catch (IOException | BitmapDecodingException e) {
- Log.w(TAG, e);
- return new Pair<>(context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture), false);
- }
- }
- };
- ListenableFutureTask> futureTask = new ListenableFutureTask<>(slideCallable);
- MmsDatabase.slideResolver.execute(futureTask);
- return futureTask;
- }
-
- private Drawable getCachedThumbnail() {
- synchronized (thumbnailCache) {
- SoftReference bitmapReference = thumbnailCache.get(part.getDataUri());
- Log.w("ImageSlide", "Got soft reference: " + bitmapReference);
-
- if (bitmapReference != null) {
- Drawable bitmap = bitmapReference.get();
- Log.w("ImageSlide", "Got cached bitmap: " + bitmap);
- if (bitmap != null) return bitmap;
- else thumbnailCache.remove(part.getDataUri());
- }
+ public Uri getThumbnailUri() {
+ if (!getPart().isPendingPush() && getPart().getDataUri() != null) {
+ return isDraft()
+ ? getPart().getDataUri()
+ : PartAuthority.getThumbnailUri(getPart().getId());
}
return null;
}
+ @Override
+ public @DrawableRes int getPlaceholderRes(Theme theme) {
+ return R.drawable.ic_missing_thumbnail_picture;
+ }
+
@Override
public boolean hasImage() {
return true;
@@ -137,4 +74,5 @@ public class ImageSlide extends Slide {
return part;
}
+
}
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index e6b5105d7a..db237bafed 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -15,16 +15,20 @@ import java.io.InputStream;
public class PartAuthority {
- private static final String PART_URI_STRING = "content://org.thoughtcrime.securesms/part";
- public static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
+ private static final String PART_URI_STRING = "content://org.thoughtcrime.securesms/part";
+ private static final String THUMB_URI_STRING = "content://org.thoughtcrime.securesms/thumb";
+ public static final Uri PART_CONTENT_URI = Uri.parse(PART_URI_STRING);
+ public 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 UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("org.thoughtcrime.securesms", "part/#", PART_ROW);
+ uriMatcher.addURI("org.thoughtcrime.securesms", "thumb/#", THUMB_ROW);
}
public static InputStream getPartStream(Context context, MasterSecret masterSecret, Uri uri)
@@ -36,6 +40,7 @@ public class PartAuthority {
try {
switch (match) {
case PART_ROW: return partDatabase.getPartStream(masterSecret, ContentUris.parseId(uri));
+ case THUMB_ROW: return partDatabase.getThumbnailStream(masterSecret, ContentUris.parseId(uri));
default: return context.getContentResolver().openInputStream(uri);
}
} catch (SecurityException se) {
@@ -43,19 +48,11 @@ public class PartAuthority {
}
}
- public static InputStream getThumbnail(Context context, MasterSecret masterSecret, Uri uri)
- throws IOException
- {
- PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
- int match = uriMatcher.match(uri);
-
- switch (match) {
- case PART_ROW: return partDatabase.getThumbnailStream(masterSecret, ContentUris.parseId(uri));
- default: return null;
- }
- }
-
public static Uri getPublicPartUri(Uri uri) {
return ContentUris.withAppendedId(PartProvider.CONTENT_URI, ContentUris.parseId(uri));
}
+
+ public static Uri getThumbnailUri(long partId) {
+ return ContentUris.withAppendedId(THUMB_CONTENT_URI, partId);
+ }
}
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index f5b7c7d680..4e6da8cf92 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -16,20 +16,18 @@
*/
package org.thoughtcrime.securesms.mms;
+import android.content.Context;
+import android.content.res.Resources.Theme;
+import android.net.Uri;
+import android.support.annotation.DrawableRes;
+import android.util.Log;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.util.Util;
+
import java.io.IOException;
import java.io.InputStream;
-import org.thoughtcrime.securesms.util.ListenableFutureTask;
-import org.thoughtcrime.securesms.util.Util;
-import org.thoughtcrime.securesms.crypto.MasterSecret;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.util.Log;
-import android.util.Pair;
-
import ws.com.google.android.mms.pdu.PduPart;
public abstract class Slide {
@@ -68,10 +66,6 @@ public abstract class Slide {
return part.getDataUri();
}
- public ListenableFutureTask> getThumbnail(Context context) {
- throw new AssertionError("getThumbnail() called on non-thumbnail producing slide!");
- }
-
public boolean hasImage() {
return false;
}
@@ -84,22 +78,22 @@ public abstract class Slide {
return false;
}
- public Bitmap getImage() {
- throw new AssertionError("getImage() called on non-image slide!");
- }
-
- public boolean hasText() {
- return false;
- }
-
- public String getText() {
- throw new AssertionError("getText() called on non-text slide!");
- }
-
public PduPart getPart() {
return part;
}
+ public Uri getThumbnailUri() {
+ return null;
+ }
+
+ public @DrawableRes int getPlaceholderRes(Theme theme) {
+ throw new AssertionError("getPlaceholderRes() called for non-drawable slide");
+ }
+
+ public boolean isDraft() {
+ return getPart().getId() < 0;
+ }
+
protected static void assertMediaSize(Context context, Uri uri)
throws MediaTooLargeException, IOException
{
diff --git a/src/org/thoughtcrime/securesms/mms/TextSecureGlideModule.java b/src/org/thoughtcrime/securesms/mms/TextSecureGlideModule.java
new file mode 100644
index 0000000000..8e27baab3c
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/TextSecureGlideModule.java
@@ -0,0 +1,32 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.GlideBuilder;
+import com.bumptech.glide.load.engine.cache.DiskCache;
+import com.bumptech.glide.load.engine.cache.DiskCacheAdapter;
+import com.bumptech.glide.module.GlideModule;
+
+import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
+
+import java.io.InputStream;
+
+public class TextSecureGlideModule implements GlideModule {
+ @Override
+ public void applyOptions(Context context, GlideBuilder builder) {
+ builder.setDiskCache(new NoopDiskCacheFactory());
+ }
+
+ @Override
+ public void registerComponents(Context context, Glide glide) {
+ glide.register(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory());
+ }
+
+ public static class NoopDiskCacheFactory implements DiskCache.Factory {
+ @Override
+ public DiskCache build() {
+ return new DiskCacheAdapter();
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/TextSlide.java b/src/org/thoughtcrime/securesms/mms/TextSlide.java
index 1c9dbd1e98..ee1930dba4 100644
--- a/src/org/thoughtcrime/securesms/mms/TextSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/TextSlide.java
@@ -19,12 +19,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
-import android.widget.ImageView;
-import org.thoughtcrime.securesms.util.SmilUtil;
-import org.w3c.dom.smil.SMILDocument;
-import org.w3c.dom.smil.SMILMediaElement;
-import org.w3c.dom.smil.SMILRegionElement;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.LRUCache;
@@ -51,35 +46,6 @@ public class TextSlide extends Slide {
super(context, getPartForMessage(message));
}
- @Override
- public boolean hasText() {
- return true;
- }
-
- @Override
- public String getText() {
- try {
- SoftReference reference = textCache.get(part.getDataUri());
-
- if (reference != null) {
- String cachedText = reference.get();
-
- if (cachedText != null) {
- return cachedText;
- }
- }
-
-
- String text = new String(getPartData(), CharacterSets.getMimeName(part.getCharset()));
- textCache.put(part.getDataUri(), new SoftReference(text));
-
- return text;
- } catch (UnsupportedEncodingException uee) {
- Log.w("TextSlide", uee);
- return new String(getPartData());
- }
- }
-
private static PduPart getPartForMessage(String message) {
PduPart part = new PduPart();
diff --git a/src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java b/src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java
new file mode 100644
index 0000000000..45bed74347
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java
@@ -0,0 +1,48 @@
+package org.thoughtcrime.securesms.mms;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
+
+public class ThumbnailTransform extends BitmapTransformation {
+ private static final String TAG = ThumbnailTransform.class.getSimpleName();
+
+ public ThumbnailTransform(Context context) {
+ super(context);
+ }
+
+ @SuppressWarnings("unused")
+ public ThumbnailTransform(BitmapPool bitmapPool) {
+ super(bitmapPool);
+ }
+
+ @Override
+ protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
+ if (toTransform.getWidth() < (outWidth / 2) && toTransform.getHeight() < (outHeight / 2)) {
+ return toTransform;
+ }
+
+ final float inAspectRatio = (float) toTransform.getWidth() / toTransform.getHeight();
+ final float outAspectRatio = (float) outWidth / outHeight;
+ if (inAspectRatio < outAspectRatio) {
+ outWidth = (int)(outHeight * inAspectRatio);
+ }
+
+ final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null
+ ? toTransform.getConfig()
+ : Bitmap.Config.ARGB_8888);
+ Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
+ if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
+ toReuse.recycle();
+ }
+ return transformed;
+ }
+
+ @Override
+ public String getId() {
+ return ThumbnailTransform.class.getCanonicalName();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
index 4222ef61ff..fb3e3f33a4 100644
--- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
@@ -16,22 +16,22 @@
*/
package org.thoughtcrime.securesms.mms;
-import java.io.IOException;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources.Theme;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.support.annotation.DrawableRes;
+import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.ResUtil;
+import java.io.IOException;
+
import ws.com.google.android.mms.pdu.PduPart;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.util.Pair;
public class VideoSlide extends Slide {
@@ -44,8 +44,8 @@ public class VideoSlide extends Slide {
}
@Override
- public ListenableFutureTask> getThumbnail(Context context) {
- return new ListenableFutureTask<>(new Pair<>(ResUtil.getDrawable(context, R.attr.conversation_icon_attach_video), true));
+ public @DrawableRes int getPlaceholderRes(Theme theme) {
+ return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_video);
}
@Override
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index 0ac915788e..65f7ef4ae4 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -2,34 +2,23 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.ImageSlide;
-import org.thoughtcrime.securesms.mms.MediaConstraints;
-import org.thoughtcrime.securesms.mms.MediaTooLargeException;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.VideoSlide;
-import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.util.concurrent.Callable;
import ws.com.google.android.mms.ContentType;
-import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.PduPart;
-import ws.com.google.android.mms.pdu.SendReq;
public class MediaUtil {
private static final String TAG = MediaUtil.class.getSimpleName();
@@ -51,22 +40,6 @@ public class MediaUtil {
return data;
}
- public static Bitmap getOrGenerateThumbnail(Context context, MasterSecret masterSecret, PduPart part)
- throws IOException, BitmapDecodingException
- {
- if (part.getDataUri() != null && part.getId() > -1) {
- return BitmapFactory.decodeStream(DatabaseFactory.getPartDatabase(context)
- .getThumbnailStream(masterSecret, part.getId()));
- } else if (part.getDataUri() != null) {
- Log.w(TAG, "generating thumbnail for new part");
- Bitmap bitmap = MediaUtil.generateThumbnail(context, masterSecret, part.getDataUri(), Util.toIsoString(part.getContentType())).getBitmap();
- part.setThumbnail(bitmap);
- return bitmap;
- } else {
- throw new FileNotFoundException("no data location specified");
- }
- }
-
public static byte[] getPartData(Context context, MasterSecret masterSecret, PduPart part)
throws IOException
{
diff --git a/src/org/thoughtcrime/securesms/util/ResUtil.java b/src/org/thoughtcrime/securesms/util/ResUtil.java
index a63dace4e9..86910a5c6f 100644
--- a/src/org/thoughtcrime/securesms/util/ResUtil.java
+++ b/src/org/thoughtcrime/securesms/util/ResUtil.java
@@ -18,6 +18,7 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
@@ -33,8 +34,12 @@ public class ResUtil {
}
public static int getDrawableRes(Context c, @AttrRes int attr) {
+ return getDrawableRes(c.getTheme(), attr);
+ }
+
+ public static int getDrawableRes(Theme theme, @AttrRes int attr) {
final TypedValue out = new TypedValue();
- c.getTheme().resolveAttribute(attr, out, true);
+ theme.resolveAttribute(attr, out, true);
return out.resourceId;
}