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; }