From f13ad54ba11868115fb1630a7ac31d0a306a41c1 Mon Sep 17 00:00:00 2001 From: Jake McGinty Date: Wed, 15 Jul 2015 13:42:59 -0700 Subject: [PATCH] ditch RoundedImageView, make animated gifs work // FREEBIE --- build.gradle | 4 +- res/layout/conversation_activity.xml | 4 +- res/layout/conversation_item_received.xml | 2 - res/layout/conversation_item_sent.xml | 2 - res/layout/group_create_activity.xml | 1 - res/layout/thumbnail_view.xml | 16 ++-- res/values/attrs.xml | 3 + .../securesms/ConversationItem.java | 7 +- .../securesms/components/ImageDivet.java | 4 +- .../securesms/components/ThumbnailView.java | 56 ++++++++---- .../components/ZoomingImageView.java | 7 +- .../securesms/mms/AttachmentManager.java | 22 ++--- .../thoughtcrime/securesms/mms/GifSlide.java | 27 ++++++ .../securesms/mms/ImageSlide.java | 9 +- .../securesms/mms/MediaConstraints.java | 11 ++- .../securesms/mms/MmsMediaConstraints.java | 5 ++ .../securesms/mms/PushMediaConstraints.java | 5 ++ .../securesms/mms/RoundedCorners.java | 88 +++++++++++++++++++ .../securesms/util/MediaUtil.java | 24 ++++- 19 files changed, 231 insertions(+), 66 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/mms/GifSlide.java create mode 100644 src/org/thoughtcrime/securesms/mms/RoundedCorners.java diff --git a/build.gradle b/build.gradle index 1800064c47..c43968bb74 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,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.6.0' + compile 'com.github.bumptech.glide:glide:3.6.1' compile 'com.makeramen:roundedimageview:2.1.0' compile 'com.pnikosis:materialish-progress:1.5' compile 'de.greenrobot:eventbus:2.4.0' @@ -108,7 +108,7 @@ dependencyVerification { 'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b', 'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1', 'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc', - 'com.github.bumptech.glide:glide:adf657e6bddccb168a29e18ab0954043af46a9b5c736d8c3193c9783fd83d69e', + 'com.github.bumptech.glide:glide:4718ac4c57ebabe56e673dc3265950b9dbf940d1c43c0adc363e8b95c0abdf75', 'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1', 'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54', 'de.greenrobot:eventbus:61d743a748156a372024d083de763b9e91ac2dcb3f6a1cbc74995c7ddab6e968', diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml index 6912cfa44d..fde6002d97 100644 --- a/res/layout/conversation_activity.xml +++ b/res/layout/conversation_activity.xml @@ -55,8 +55,8 @@ android:id="@+id/attachment_thumbnail" android:layout_width="230dp" android:layout_height="150dp" - app:riv_corner_radius="3dp" - android:contentDescription="@string/conversation_activity__attachment_thumbnail"/> + android:contentDescription="@string/conversation_activity__attachment_thumbnail" + app:backgroundColorHint="?conversation_background" /> diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml index 9d7fb0ad52..027283cc4d 100644 --- a/res/layout/conversation_item_sent.xml +++ b/res/layout/conversation_item_sent.xml @@ -65,8 +65,6 @@ android:adjustViewBounds="true" android:contentDescription="@string/conversation_item__mms_image_description" android:visibility="gone" - app:riv_corner_radius="@dimen/message_bubble_corner_radius" - app:riv_border_width="0dp" tools:src="@drawable/ic_video_light" tools:visibility="visible" /> diff --git a/res/layout/group_create_activity.xml b/res/layout/group_create_activity.xml index 18133523b8..cb57ad4db4 100644 --- a/res/layout/group_create_activity.xml +++ b/res/layout/group_create_activity.xml @@ -25,7 +25,6 @@ android:layout_width="70dp" android:layout_height="70dp" position="bottom_right" - app:riv_oval="true" android:layout_marginRight="10dp" android:src="@drawable/ic_group_photo" android:contentDescription="@string/GroupCreateActivity_avatar_content_description" /> diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index 739fae50f7..36116145d2 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -2,15 +2,13 @@ - + + + + diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 5f931b0471..f7cad6b2f8 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -218,11 +218,12 @@ public class ConversationItem extends LinearLayout implements Recipient.Recipien private void setBubbleState(MessageRecord messageRecord, Recipient recipient) { if (messageRecord.isOutgoing()) { bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY); + mediaThumbnail.setBackgroundColorHint(defaultBubbleColor); } else { - bodyBubble.getBackground().setColorFilter(recipient.getColor().toConversationColor(context), - PorterDuff.Mode.MULTIPLY); + int color = recipient.getColor().toConversationColor(context); + bodyBubble.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY); + mediaThumbnail.setBackgroundColorHint(color); } - } private void setSelectionBackgroundDrawables(MessageRecord messageRecord) { diff --git a/src/org/thoughtcrime/securesms/components/ImageDivet.java b/src/org/thoughtcrime/securesms/components/ImageDivet.java index 987cf3a1fa..770226b835 100644 --- a/src/org/thoughtcrime/securesms/components/ImageDivet.java +++ b/src/org/thoughtcrime/securesms/components/ImageDivet.java @@ -7,11 +7,9 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; -import com.makeramen.roundedimageview.RoundedImageView; - import org.thoughtcrime.securesms.R; -public class ImageDivet extends RoundedImageView { +public class ImageDivet extends ImageView { private static final float CORNER_OFFSET = 12F; private static final String[] POSITIONS = new String[] {"bottom_right"}; diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index b5c6c5d3ca..9191d3ddf4 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -3,7 +3,8 @@ package org.thoughtcrime.securesms.components; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; -import android.graphics.Bitmap; +import android.content.res.TypedArray; +import android.graphics.Color; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.support.annotation.NonNull; @@ -15,13 +16,16 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.widget.FrameLayout; +import android.widget.ImageView; import com.bumptech.glide.DrawableTypeRequest; import com.bumptech.glide.GenericRequestBuilder; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.bitmap.CenterCrop; +import com.bumptech.glide.load.resource.bitmap.GlideBitmapDrawable; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.Target; -import com.makeramen.roundedimageview.RoundedImageView; import com.pnikosis.materialishprogress.ProgressWheel; import org.thoughtcrime.securesms.R; @@ -32,6 +36,7 @@ import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; +import org.thoughtcrime.securesms.mms.RoundedCorners; import org.thoughtcrime.securesms.util.Util; import de.greenrobot.event.EventBus; @@ -40,9 +45,11 @@ import ws.com.google.android.mms.pdu.PduPart; public class ThumbnailView extends FrameLayout { private static final String TAG = ThumbnailView.class.getSimpleName(); - private boolean showProgress = true; - private RoundedImageView image; - private ProgressWheel progress; + private boolean showProgress = true; + private ImageView image; + private ProgressWheel progress; + private int backgroundColorHint; + private int radius; private ListenableFutureTask slideDeckFuture = null; private SlideDeckListener slideDeckListener = null; @@ -61,8 +68,15 @@ public class ThumbnailView extends FrameLayout { public ThumbnailView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); inflate(context, R.layout.thumbnail_view, this); - image = (RoundedImageView) findViewById(R.id.thumbnail_image); - progress = (ProgressWheel) findViewById(R.id.progress_wheel); + radius = getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius); + image = (ImageView) findViewById(R.id.thumbnail_image); + progress = (ProgressWheel) findViewById(R.id.progress_wheel); + + if (attrs != null) { + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); + backgroundColorHint = typedArray.getColor(0, Color.BLACK); + typedArray.recycle(); + } } @Override protected void onAttachedToWindow() { @@ -80,13 +94,17 @@ public class ThumbnailView extends FrameLayout { if (this.slide != null && event.partId.equals(this.slide.getPart().getPartId())) { Util.runOnMain(new Runnable() { @Override public void run() { - progress.setInstantProgress(((float) event.progress) / event.total); + progress.setInstantProgress(((float)event.progress) / event.total); if (event.progress >= event.total) animateOutProgress(); } }); } } + public void setBackgroundColorHint(int color) { + this.backgroundColorHint = color; + } + public void setImageResource(@Nullable MasterSecret masterSecret, long id, long timestamp, @NonNull ListenableFutureTask slideDeckFuture) @@ -155,6 +173,7 @@ public class ThumbnailView extends FrameLayout { private GenericRequestBuilder buildGlideRequest(@NonNull Slide slide, @Nullable MasterSecret masterSecret) { + Log.w(TAG, "slide type " + slide.getContentType()); final GenericRequestBuilder builder; if (slide.getThumbnailUri() != null) { builder = buildThumbnailGlideRequest(slide, masterSecret); @@ -182,8 +201,7 @@ public class ThumbnailView extends FrameLayout { if (masterSecret == null) request = Glide.with(getContext()).load(slide.getThumbnailUri()); else request = Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri())); - return request.asBitmap() - .fitCenter() + return request.transform(new RoundedCorners(getContext(), false, radius, backgroundColorHint)) .listener(new PduThumbnailSetListener(slide.getPart())); } @@ -192,9 +210,9 @@ public class ThumbnailView extends FrameLayout { throw new IllegalStateException("null MasterSecret when loading non-draft thumbnail"); } - return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri())) - .asBitmap() - .centerCrop(); + return Glide.with(getContext()).load(new DecryptableUri(masterSecret, slide.getThumbnailUri())) + .crossFade() + .transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint)); } private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) { @@ -282,7 +300,7 @@ public class ThumbnailView extends FrameLayout { } } - private static class PduThumbnailSetListener implements RequestListener { + private static class PduThumbnailSetListener implements RequestListener { private PduPart part; public PduThumbnailSetListener(@NonNull PduPart part) { @@ -290,15 +308,17 @@ public class ThumbnailView extends FrameLayout { } @Override - public boolean onException(Exception e, Object model, Target target, boolean isFirstResource) { + public boolean onException(Exception e, Object model, Target target, boolean isFirstResource) { return false; } @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { - part.setThumbnail(resource); + public boolean onResourceReady(GlideDrawable resource, Object model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + if (resource instanceof GlideBitmapDrawable) { + Log.w(TAG, "onResourceReady() for a Bitmap. Saving."); + part.setThumbnail(((GlideBitmapDrawable)resource).getBitmap()); + } return false; } } - } diff --git a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java index bc175ce329..1caf6d4bf3 100644 --- a/src/org/thoughtcrime/securesms/components/ZoomingImageView.java +++ b/src/org/thoughtcrime/securesms/components/ZoomingImageView.java @@ -7,7 +7,9 @@ import android.util.AttributeSet; import android.widget.ImageView; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.target.BitmapImageViewTarget; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; @@ -32,11 +34,10 @@ public class ZoomingImageView extends ImageView { public void setImageUri(MasterSecret masterSecret, Uri uri) { Glide.with(getContext()) .load(new DecryptableUri(masterSecret, uri)) - .asBitmap() .dontTransform() .dontAnimate() - .into(new BitmapImageViewTarget(this) { - @Override protected void setResource(Bitmap resource) { + .into(new GlideDrawableImageViewTarget(this) { + @Override protected void setResource(GlideDrawable resource) { super.setResource(resource); attacher.update(); } diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index cfe8477515..6ecd7da2e2 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.providers.CaptureProvider; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.MediaUtil; import java.io.IOException; @@ -68,22 +69,14 @@ public class AttachmentManager { AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f); animation.setDuration(200); animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { + @Override public void onAnimationStart(Animation animation) {} + @Override public void onAnimationRepeat(Animation animation) {} + @Override public void onAnimationEnd(Animation animation) { slideDeck.clear(); attachmentView.setVisibility(View.GONE); attachmentListener.onAttachmentChanged(); } - @Override - public void onAnimationRepeat(Animation animation) { - - } }); attachmentView.startAnimation(animation); @@ -95,7 +88,11 @@ public class AttachmentManager { } public void setImage(MasterSecret masterSecret, Uri image) throws IOException, BitmapDecodingException { - setMedia(new ImageSlide(context, masterSecret, image), masterSecret); + if (MediaUtil.getMimeType(context, image).startsWith("image/gif")) { + setMedia(new GifSlide(context, masterSecret, image), masterSecret); + } else { + setMedia(new ImageSlide(context, masterSecret, image), masterSecret); + } } public void setVideo(Uri video) throws IOException, MediaTooLargeException { @@ -148,7 +145,6 @@ public class AttachmentManager { return captureUri; } - public void setCaptureUri(Uri captureUri) { this.captureUri = captureUri; } diff --git a/src/org/thoughtcrime/securesms/mms/GifSlide.java b/src/org/thoughtcrime/securesms/mms/GifSlide.java new file mode 100644 index 0000000000..e858373d89 --- /dev/null +++ b/src/org/thoughtcrime/securesms/mms/GifSlide.java @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms.mms; + +import android.content.Context; +import android.net.Uri; + +import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.util.BitmapDecodingException; + +import java.io.IOException; + +import ws.com.google.android.mms.pdu.PduPart; + +public class GifSlide extends ImageSlide { + public GifSlide(Context context, MasterSecret masterSecret, PduPart part) { + super(context, masterSecret, part); + } + + public GifSlide(Context context, MasterSecret masterSecret, Uri uri) + throws IOException, BitmapDecodingException + { + super(context, masterSecret, uri); + } + + @Override public Uri getThumbnailUri() { + return getPart().getDataUri(); + } +} diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index a90eccd9ad..87131f2f3b 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -24,6 +24,7 @@ import android.support.annotation.DrawableRes; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.MediaUtil; import java.io.IOException; @@ -38,7 +39,7 @@ public class ImageSlide extends Slide { } public ImageSlide(Context context, MasterSecret masterSecret, Uri uri) throws IOException, BitmapDecodingException { - super(context, masterSecret, constructPartFromUri(uri)); + super(context, masterSecret, constructPartFromUri(context, uri)); } @Override @@ -62,13 +63,15 @@ public class ImageSlide extends Slide { return true; } - private static PduPart constructPartFromUri(Uri uri) + private static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, BitmapDecodingException { PduPart part = new PduPart(); + final String mimeType = MediaUtil.getMimeType(context, uri); + part.setDataUri(uri); - part.setContentType(ContentType.IMAGE_JPEG.getBytes()); + part.setContentType((mimeType != null ? mimeType : ContentType.IMAGE_JPEG).getBytes()); part.setContentId((System.currentTimeMillis()+"").getBytes()); part.setName(("Image" + System.currentTimeMillis()).getBytes()); diff --git a/src/org/thoughtcrime/securesms/mms/MediaConstraints.java b/src/org/thoughtcrime/securesms/mms/MediaConstraints.java index b87fe43864..ddf7b89537 100644 --- a/src/org/thoughtcrime/securesms/mms/MediaConstraints.java +++ b/src/org/thoughtcrime/securesms/mms/MediaConstraints.java @@ -25,15 +25,18 @@ public abstract class MediaConstraints { public abstract int getImageMaxHeight(Context context); public abstract int getImageMaxSize(); + public abstract int getGifMaxSize(); + public abstract int getVideoMaxSize(); public abstract int getAudioMaxSize(); public boolean isSatisfied(Context context, MasterSecret masterSecret, PduPart part) { try { - return (MediaUtil.isImage(part) && part.getDataSize() <= getImageMaxSize() && isWithinBounds(context, masterSecret, part.getDataUri())) || - (MediaUtil.isAudio(part) && part.getDataSize() <= getAudioMaxSize()) || - (MediaUtil.isVideo(part) && part.getDataSize() <= getVideoMaxSize()) || + return (MediaUtil.isGif(part) && part.getDataSize() <= getGifMaxSize()) || + (MediaUtil.isImage(part) && part.getDataSize() <= getImageMaxSize() && isWithinBounds(context, masterSecret, part.getDataUri())) || + (MediaUtil.isAudio(part) && part.getDataSize() <= getAudioMaxSize()) || + (MediaUtil.isVideo(part) && part.getDataSize() <= getVideoMaxSize()) || (!MediaUtil.isImage(part) && !MediaUtil.isAudio(part) && !MediaUtil.isVideo(part)); } catch (IOException ioe) { Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe); @@ -49,7 +52,7 @@ public abstract class MediaConstraints { } public boolean canResize(PduPart part) { - return part != null && MediaUtil.isImage(part); + return part != null && MediaUtil.isImage(part) && !MediaUtil.isGif(part); } public byte[] getResizedMedia(Context context, MasterSecret masterSecret, PduPart part) diff --git a/src/org/thoughtcrime/securesms/mms/MmsMediaConstraints.java b/src/org/thoughtcrime/securesms/mms/MmsMediaConstraints.java index f92a55979b..f6db1eea1b 100644 --- a/src/org/thoughtcrime/securesms/mms/MmsMediaConstraints.java +++ b/src/org/thoughtcrime/securesms/mms/MmsMediaConstraints.java @@ -24,6 +24,11 @@ public class MmsMediaConstraints extends MediaConstraints { return MAX_MESSAGE_SIZE; } + @Override + public int getGifMaxSize() { + return MAX_MESSAGE_SIZE; + } + @Override public int getVideoMaxSize() { return MAX_MESSAGE_SIZE; diff --git a/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java index 12e00a6ab4..f59ece7865 100644 --- a/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java +++ b/src/org/thoughtcrime/securesms/mms/PushMediaConstraints.java @@ -25,6 +25,11 @@ public class PushMediaConstraints extends MediaConstraints { return 420 * KB; } + @Override + public int getGifMaxSize() { + return 1 * MB; + } + @Override public int getVideoMaxSize() { return MmsMediaConstraints.MAX_MESSAGE_SIZE; diff --git a/src/org/thoughtcrime/securesms/mms/RoundedCorners.java b/src/org/thoughtcrime/securesms/mms/RoundedCorners.java new file mode 100644 index 0000000000..3a3485baad --- /dev/null +++ b/src/org/thoughtcrime/securesms/mms/RoundedCorners.java @@ -0,0 +1,88 @@ +package org.thoughtcrime.securesms.mms; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader.TileMode; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +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 RoundedCorners extends BitmapTransformation { + private final boolean crop; + private final int radius; + private final int colorHint; + + public RoundedCorners(@NonNull Context context, boolean crop, int radius, int colorHint) { + super(context); + this.crop = crop; + this.radius = radius; + this.colorHint = colorHint; + } + + @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, + int outHeight) + { + final Bitmap toRound = crop ? centerCrop(pool, toTransform, outWidth, outHeight) + : fitCenter(pool, toTransform, outWidth, outHeight); + return round(pool, toRound); + } + + private Bitmap centerCrop(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + final Bitmap toReuse = pool.get(outWidth, outHeight, getSafeConfig(toTransform)); + final Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight); + if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { + toReuse.recycle(); + } + return transformed; + } + + private Bitmap fitCenter(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { + return TransformationUtils.fitCenter(toTransform, pool, outWidth, outHeight); + } + + private Bitmap round(@NonNull BitmapPool pool, @Nullable Bitmap toRound) { + if (toRound == null) { + return null; + } + + final Bitmap result; + final Bitmap toReuse = pool.get(toRound.getWidth(), toRound.getHeight(), getSafeConfig(toRound)); + if (toReuse != null) { + result = toReuse; + } else { + result = Bitmap.createBitmap(toRound.getWidth(), toRound.getHeight(), getSafeConfig(toRound)); + } + + final Canvas canvas = new Canvas(result); + final Paint cornerPaint = new Paint(); + final Paint shaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + shaderPaint.setShader(new BitmapShader(toRound, TileMode.CLAMP, TileMode.CLAMP)); + cornerPaint.setColor(colorHint); + if (Config.RGB_565.equals(result.getConfig())) { + canvas.drawRect(0, 0, radius, radius, cornerPaint); + canvas.drawRect(0, toRound.getHeight() - radius, radius, toRound.getHeight(), cornerPaint); + canvas.drawRect(toRound.getWidth() - radius, 0, toRound.getWidth(), radius, cornerPaint); + canvas.drawRect(toRound.getWidth() - radius, toRound.getHeight() - radius, toRound.getWidth(), toRound.getHeight(), cornerPaint); + } + canvas.drawRoundRect(new RectF(0, 0, toRound.getWidth(), toRound.getHeight()), radius, radius, shaderPaint); +// Log.w("RoundedCorners", "in was " + toRound.getWidth() + "x" + toRound.getHeight() + ", out to " + result.getWidth() + "x" + result.getHeight()); + return result; + } + + private static Bitmap.Config getSafeConfig(Bitmap bitmap) { + return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; + } + + @Override public String getId() { + return RoundedCorners.class.getCanonicalName(); + } +} diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java index 65f7ef4ae4..e5230fc15a 100644 --- a/src/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java @@ -3,11 +3,14 @@ package org.thoughtcrime.securesms.util; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; +import android.webkit.MimeTypeMap; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.mms.AudioSlide; +import org.thoughtcrime.securesms.mms.GifSlide; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.Slide; @@ -59,7 +62,9 @@ public class MediaUtil { public static Slide getSlideForPart(Context context, MasterSecret masterSecret, PduPart part, String contentType) { Slide slide = null; - if (ContentType.isImageType(contentType)) { + if (isGif(contentType)) { + slide = new GifSlide(context, masterSecret, part); + } else if (ContentType.isImageType(contentType)) { slide = new ImageSlide(context, masterSecret, part); } else if (ContentType.isVideoType(contentType)) { slide = new VideoSlide(context, masterSecret, part); @@ -70,6 +75,23 @@ public class MediaUtil { return slide; } + public static String getMimeType(Context context, Uri uri) { + String type = context.getContentResolver().getType(uri); + if (type == null) { + final String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + return type; + } + + private static boolean isGif(String contentType) { + return !TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif"); + } + + public static boolean isGif(PduPart part) { + return isGif(Util.toIsoString(part.getContentType())); + } + public static boolean isImage(PduPart part) { return ContentType.isImageType(Util.toIsoString(part.getContentType())); }