diff --git a/build.gradle b/build.gradle
index a3d682549a..bb8b0f295e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,6 +63,7 @@ dependencies {
compile 'com.android.support:preference-v14:27.0.2'
compile 'com.android.support:gridlayout-v7:27.0.2'
compile 'com.android.support:multidex:1.0.2'
+ compile "com.android.support:exifinterface:27.0.2"
compile 'com.google.android.gms:play-services-gcm:9.6.1'
compile 'com.google.android.gms:play-services-maps:9.6.1'
@@ -156,6 +157,7 @@ dependencyVerification {
'com.android.support:cardview-v7:57f867a3c8f33e2d4dc0a03e2dfa03cad6267a908179f04a725a68ea9f0b8ccf',
'com.android.support:gridlayout-v7:227b5fdffa20f53bd562503aab6d2293d52cf64b5a6ab1116d2150f87bff9e88',
'com.android.support:multidex:7cd48755c7cfdb6dd2d21cbb02236ec390f6ac91cde87eb62f475b259ab5301d',
+ 'com.android.support:exifinterface:0e7cd526c4468895cd8549def46b3d33c8bcfb1ae4830569898d8c7326b15bb2',
'com.google.android.gms:play-services-gcm:312e61253a236f2d9b750b9c04fc92fd190d23b0b2755c99de6ce4a28b259dae',
'com.google.android.gms:play-services-places:abf3a4a3b146ec7e6e753be62775e512868cf37d6f88ffe2d81167b33b57132b',
'com.google.android.gms:play-services-maps:45e8021e7ddac4a44a82a0e9698991389ded3023d35c58f38dbd86d54211ec0e',
diff --git a/res/layout/conversation_activity_attachment_editor_stub.xml b/res/layout/conversation_activity_attachment_editor_stub.xml
index c7825b9205..a47de48b99 100644
--- a/res/layout/conversation_activity_attachment_editor_stub.xml
+++ b/res/layout/conversation_activity_attachment_editor_stub.xml
@@ -27,7 +27,11 @@
android:layout_gravity="center_horizontal"
android:visibility="gone"
android:contentDescription="@string/conversation_activity__attachment_thumbnail"
- app:backgroundColorHint="?conversation_background" />
+ app:backgroundColorHint="?conversation_background"
+ app:minWidth="100dp"
+ app:maxWidth="300dp"
+ app:minHeight="100dp"
+ app:maxHeight="300dp" />
+ android:layout_width="@dimen/media_bubble_default_dimens"
+ android:layout_height="@dimen/media_bubble_default_dimens"/>
diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml
index 2686e3ec8a..7e4ed9421b 100644
--- a/res/layout/conversation_item_sent.xml
+++ b/res/layout/conversation_item_sent.xml
@@ -40,9 +40,9 @@
+ android:layout_width="@dimen/media_bubble_default_dimens"
+ android:layout_height="@dimen/media_bubble_default_dimens"
+ android:layout="@layout/conversation_item_sent_thumbnail" />
diff --git a/res/values-sw320dp/dimens.xml b/res/values-sw320dp/dimens.xml
new file mode 100644
index 0000000000..e10ac512b8
--- /dev/null
+++ b/res/values-sw320dp/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 220dp
+ 300dp
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 50c793a27a..bbea8b9264 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -147,6 +147,10 @@
+
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index cc0c00d123..9a66bf4c7c 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,9 +18,13 @@
4dp
1.5dp
- 210dp
24dp
24dp
+ 210dp
+ 150dp
+ 250dp
+ 100dp
+ 320dp
3
10dp
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 0451c06519..525575675d 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -112,6 +112,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
+import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.mms.AttachmentManager;
@@ -406,6 +407,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
else mediaType = MediaType.IMAGE;
setMedia(data.getData(), mediaType);
+
break;
case PICK_DOCUMENT:
setMedia(data.getData(), MediaType.DOCUMENT);
@@ -438,7 +440,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
attachmentManager.setLocation(place, getCurrentMediaConstraints());
break;
case PICK_GIF:
- setMedia(data.getData(), MediaType.GIF);
+ setMedia(data.getData(),
+ MediaType.GIF,
+ data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0),
+ data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0));
break;
case ScribbleActivity.SCRIBBLE_REQUEST_CODE:
setMedia(data.getData(), MediaType.IMAGE);
@@ -1377,8 +1382,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) {
+ setMedia(uri, mediaType, 0, 0);
+ }
+
+ private void setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) {
if (uri == null) return;
- attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints());
+ attachmentManager.setMedia(glideRequests, uri, mediaType, getCurrentMediaConstraints(), width, height);
}
private void addAttachmentContactInfo(Uri contactUri) {
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index ecfd17feba..f0c64daace 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -45,6 +45,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AudioView;
@@ -385,9 +386,14 @@ public class ConversationItem extends LinearLayout
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
+ Slide thumbnailSlide = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlide();
+ Attachment attachment = thumbnailSlide.asAttachment();
mediaThumbnailStub.get().setImageResource(glideRequests,
- ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
- showControls, false);
+ thumbnailSlide,
+ showControls,
+ false,
+ attachment.getWidth(),
+ attachment.getHeight());
mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener());
mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener);
mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
diff --git a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
index 4511563303..d1cd989c40 100644
--- a/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
+++ b/src/org/thoughtcrime/securesms/attachments/UriAttachment.java
@@ -12,15 +12,15 @@ public class UriAttachment extends Attachment {
public UriAttachment(@NonNull Uri uri, @NonNull String contentType, int transferState, long size,
@Nullable String fileName, boolean voiceNote)
{
- this(uri, uri, contentType, transferState, size, fileName, null, voiceNote);
+ this(uri, uri, contentType, transferState, size, 0, 0, fileName, null, voiceNote);
}
public UriAttachment(@NonNull Uri dataUri, @Nullable Uri thumbnailUri,
- @NonNull String contentType, int transferState, long size,
+ @NonNull String contentType, int transferState, long size, int width, int height,
@Nullable String fileName, @Nullable String fastPreflightId,
boolean voiceNote)
{
- super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, 0, 0);
+ super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height);
this.dataUri = dataUri;
this.thumbnailUri = thumbnailUri;
}
diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
index bc35c1ae81..3c25aab8b7 100644
--- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java
+++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java
@@ -5,20 +5,26 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
+import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
@@ -26,11 +32,19 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
+import java.util.Locale;
+
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
public class ThumbnailView extends FrameLayout {
private static final String TAG = ThumbnailView.class.getSimpleName();
+ private static final int WIDTH = 0;
+ private static final int HEIGHT = 1;
+ private static final int MIN_WIDTH = 0;
+ private static final int MAX_WIDTH = 1;
+ private static final int MIN_HEIGHT = 2;
+ private static final int MAX_HEIGHT = 3;
private ImageView image;
private ImageView playOverlay;
@@ -38,6 +52,10 @@ public class ThumbnailView extends FrameLayout {
private int radius;
private OnClickListener parentClickListener;
+ private final int[] dimens = new int[2];
+ private final int[] bounds = new int[4];
+ private final int[] measureDimens = new int[2];
+
private Optional transferControls = Optional.absent();
private SlideClickListener thumbnailClickListener = null;
private SlideClickListener downloadClickListener = null;
@@ -63,11 +81,110 @@ public class ThumbnailView extends FrameLayout {
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0);
- backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK);
+ backgroundColorHint = typedArray.getColor(R.styleable.ThumbnailView_backgroundColorHint, Color.BLACK);
+ bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0);
+ bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
+ bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
+ bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
typedArray.recycle();
}
}
+ @Override
+ protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) {
+ fillTargetDimensions(measureDimens, dimens, bounds);
+ if (measureDimens[WIDTH] == 0 && measureDimens[HEIGHT] == 0) {
+ super.onMeasure(originalWidthMeasureSpec, originalHeightMeasureSpec);
+ return;
+ }
+
+ int finalWidth = measureDimens[WIDTH] + getPaddingLeft() + getPaddingRight();
+ int finalHeight = measureDimens[HEIGHT] + getPaddingTop() + getPaddingBottom();
+
+ super.onMeasure(MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY));
+ }
+
+ @SuppressWarnings("SuspiciousNameCombination")
+ private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) {
+ int dimensFilledCount = getNonZeroCount(dimens);
+ int boundsFilledCount = getNonZeroCount(bounds);
+
+ if (dimensFilledCount == 0 || boundsFilledCount == 0) {
+ targetDimens[WIDTH] = 0;
+ targetDimens[HEIGHT] = 0;
+ return;
+ }
+
+ double naturalWidth = dimens[WIDTH];
+ double naturalHeight = dimens[HEIGHT];
+
+ int minWidth = bounds[MIN_WIDTH];
+ int maxWidth = bounds[MAX_WIDTH];
+ int minHeight = bounds[MIN_HEIGHT];
+ int maxHeight = bounds[MAX_HEIGHT];
+
+ if (dimensFilledCount > 0 && dimensFilledCount < dimens.length) {
+ throw new IllegalStateException(String.format(Locale.ENGLISH, "Width or height has been specified, but not both. Dimens: %f x %f",
+ naturalWidth, naturalHeight));
+ }
+ if (boundsFilledCount > 0 && boundsFilledCount < bounds.length) {
+ throw new IllegalStateException(String.format(Locale.ENGLISH, "One or more min/max dimensions have been specified, but not all. Bounds: [%d, %d, %d, %d]",
+ minWidth, maxWidth, minHeight, maxHeight));
+ }
+
+ double measuredWidth = naturalWidth;
+ double measuredHeight = naturalHeight;
+
+ boolean widthInBounds = measuredWidth >= minWidth && measuredWidth <= maxWidth;
+ boolean heightInBounds = measuredHeight >= minHeight && measuredHeight <= maxHeight;
+
+ if (!widthInBounds || !heightInBounds) {
+ double minWidthRatio = naturalWidth / minWidth;
+ double maxWidthRatio = naturalWidth / maxWidth;
+ double minHeightRatio = naturalHeight / minHeight;
+ double maxHeightRatio = naturalHeight / maxHeight;
+
+ if (maxWidthRatio > 1 || maxHeightRatio > 1) {
+ if (maxWidthRatio >= maxHeightRatio) {
+ measuredWidth /= maxWidthRatio;
+ measuredHeight /= maxWidthRatio;
+ } else {
+ measuredWidth /= maxHeightRatio;
+ measuredHeight /= maxHeightRatio;
+ }
+
+ measuredWidth = Math.max(measuredWidth, minWidth);
+ measuredHeight = Math.max(measuredHeight, minHeight);
+
+ } else if (minWidthRatio < 1 || minHeightRatio < 1) {
+ if (minWidthRatio <= minHeightRatio) {
+ measuredWidth /= minWidthRatio;
+ measuredHeight /= minWidthRatio;
+ } else {
+ measuredWidth /= minHeightRatio;
+ measuredHeight /= minHeightRatio;
+ }
+
+ measuredWidth = Math.min(measuredWidth, maxWidth);
+ measuredHeight = Math.min(measuredHeight, maxHeight);
+ }
+ }
+
+ targetDimens[WIDTH] = (int) measuredWidth;
+ targetDimens[HEIGHT] = (int) measuredHeight;
+ }
+
+ private int getNonZeroCount(int[] vals) {
+ int count = 0;
+ for (int val : vals) {
+ if (val > 0) {
+ count++;
+ }
+ }
+ return count;
+ }
+
@Override
public void setOnClickListener(OnClickListener l) {
parentClickListener = l;
@@ -96,9 +213,22 @@ public class ThumbnailView extends FrameLayout {
this.backgroundColorHint = color;
}
+ @UiThread
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
boolean showControls, boolean isPreview)
{
+ setImageResource(glideRequests, slide, showControls, isPreview, 0, 0);
+ }
+
+ @UiThread
+ public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide,
+ boolean showControls, boolean isPreview, int naturalWidth,
+ int naturalHeight)
+ {
+ dimens[WIDTH] = naturalWidth;
+ dimens[HEIGHT] = naturalHeight;
+ invalidate();
+
if (showControls) {
getTransferControls().setSlide(slide);
getTransferControls().setDownloadClickListener(new DownloadClickDispatcher());
@@ -136,11 +266,11 @@ public class ThumbnailView extends FrameLayout {
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(glideRequests, slide).into(image);
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(glideRequests, slide).into(image);
else glideRequests.clear(image);
+
}
public void setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
-
glideRequests.load(new DecryptableUri(uri))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.transform(new RoundedCorners(radius))
@@ -171,22 +301,29 @@ public class ThumbnailView extends FrameLayout {
getTransferControls().showProgressSpinner();
}
- private RequestBuilder buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
- RequestBuilder builder = glideRequests.load(new DecryptableUri(slide.getThumbnailUri()))
+ private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
+ GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getThumbnailUri()))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
- .transform(new RoundedCorners(radius))
- .centerCrop()
- .transition(withCrossFade());
+ .transition(withCrossFade()), new CenterCrop());
- if (slide.isInProgress()) return builder;
- else return builder.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
+ if (slide.isInProgress()) return request;
+ else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
}
private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) {
- return glideRequests.asBitmap()
+ return applySizing(glideRequests.asBitmap()
.load(slide.getPlaceholderRes(getContext().getTheme()))
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .fitCenter();
+ .diskCacheStrategy(DiskCacheStrategy.NONE), new FitCenter());
+ }
+
+ private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation unavailableDimensSizing) {
+ int[] size = new int[2];
+ fillTargetDimensions(size, dimens, bounds);
+ if (size[WIDTH] == 0 && size[HEIGHT] == 0) {
+ return request.transforms(unavailableDimensSizing, new RoundedCorners(radius));
+ }
+ return request.override(size[WIDTH], size[HEIGHT])
+ .transforms(new CenterCrop(), new RoundedCorners(radius));
}
private class ThumbnailClickDispatcher implements View.OnClickListener {
diff --git a/src/org/thoughtcrime/securesms/giph/model/GiphyImage.java b/src/org/thoughtcrime/securesms/giph/model/GiphyImage.java
index ace6e16053..9a03884c36 100644
--- a/src/org/thoughtcrime/securesms/giph/model/GiphyImage.java
+++ b/src/org/thoughtcrime/securesms/giph/model/GiphyImage.java
@@ -28,6 +28,14 @@ public class GiphyImage {
return (float)images.downsized.width / (float)images.downsized.height;
}
+ public int getGifWidth() {
+ return images.downsized.width;
+ }
+
+ public int getGifHeight() {
+ return images.downsized.height;
+ }
+
public String getStillUrl() {
return images.downsized_still.url;
}
diff --git a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
index ded8206104..f056fbdddc 100644
--- a/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
+++ b/src/org/thoughtcrime/securesms/giph/ui/GiphyActivity.java
@@ -37,6 +37,8 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
private static final String TAG = GiphyActivity.class.getSimpleName();
public static final String EXTRA_IS_MMS = "extra_is_mms";
+ public static final String EXTRA_WIDTH = "extra_width";
+ public static final String EXTRA_HEIGHT = "extra_height";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -124,7 +126,11 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
if (uri == null) {
Toast.makeText(GiphyActivity.this, R.string.GiphyActivity_error_while_retrieving_full_resolution_gif, Toast.LENGTH_LONG).show();
} else if (viewHolder == finishingImage) {
- setResult(RESULT_OK, new Intent().setData(uri));
+ Intent intent = new Intent();
+ intent.setData(uri);
+ intent.putExtra(EXTRA_WIDTH, viewHolder.image.getGifWidth());
+ intent.putExtra(EXTRA_HEIGHT, viewHolder.image.getGifHeight());
+ setResult(RESULT_OK, intent);
finish();
} else {
Log.w(TAG, "Resolved Uri is no longer the selected element...");
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 4810fffb13..addc7c7386 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -34,6 +34,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import android.widget.Toast;
@@ -43,6 +44,7 @@ import com.google.android.gms.location.places.ui.PlacePicker;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.components.AudioView;
import org.thoughtcrime.securesms.components.DocumentView;
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
@@ -204,7 +206,9 @@ public class AttachmentManager {
public void setMedia(@NonNull final GlideRequests glideRequests,
@NonNull final Uri uri,
@NonNull final MediaType mediaType,
- @NonNull final MediaConstraints constraints)
+ @NonNull final MediaConstraints constraints,
+ final int width,
+ final int height)
{
inflateStub();
@@ -220,11 +224,11 @@ public class AttachmentManager {
protected @Nullable Slide doInBackground(Void... params) {
try {
if (PartAuthority.isLocalUri(uri)) {
- return getManuallyCalculatedSlideInfo(uri);
+ return getManuallyCalculatedSlideInfo(uri, width, height);
} else {
- Slide result = getContentResolverSlideInfo(uri);
+ Slide result = getContentResolverSlideInfo(uri, width, height);
- if (result == null) return getManuallyCalculatedSlideInfo(uri);
+ if (result == null) return getManuallyCalculatedSlideInfo(uri, width, height);
else return result;
}
} catch (IOException e) {
@@ -256,7 +260,8 @@ public class AttachmentManager {
documentView.setDocument((DocumentSlide) slide, false);
removableMediaView.display(documentView, false);
} else {
- thumbnail.setImageResource(glideRequests, slide, false, true);
+ Attachment attachment = slide.asAttachment();
+ thumbnail.setImageResource(glideRequests, slide, false, true, attachment.getWidth(), attachment.getHeight());
removableMediaView.display(thumbnail, mediaType == MediaType.IMAGE);
}
@@ -264,7 +269,7 @@ public class AttachmentManager {
}
}
- private @Nullable Slide getContentResolverSlideInfo(Uri uri) {
+ private @Nullable Slide getContentResolverSlideInfo(Uri uri, int width, int height) {
Cursor cursor = null;
long start = System.currentTimeMillis();
@@ -276,8 +281,14 @@ public class AttachmentManager {
long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
String mimeType = context.getContentResolver().getType(uri);
+ if (width == 0 || height == 0) {
+ Pair dimens = MediaUtil.getDimensions(context, mimeType, uri);
+ width = dimens.first;
+ height = dimens.second;
+ }
+
Log.w(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
- return mediaType.createSlide(context, uri, fileName, mimeType, fileSize);
+ return mediaType.createSlide(context, uri, fileName, mimeType, fileSize, width, height);
}
} finally {
if (cursor != null) cursor.close();
@@ -286,7 +297,7 @@ public class AttachmentManager {
return null;
}
- private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri) throws IOException {
+ private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri, int width, int height) throws IOException {
long start = System.currentTimeMillis();
Long mediaSize = null;
String fileName = null;
@@ -302,8 +313,18 @@ public class AttachmentManager {
mediaSize = MediaUtil.getMediaSize(context, uri);
}
+ if (mimeType == null) {
+ mimeType = MediaUtil.getMimeType(context, uri);
+ }
+
+ if (width == 0 || height == 0) {
+ Pair dimens = MediaUtil.getDimensions(context, mimeType, uri);
+ width = dimens.first;
+ height = dimens.second;
+ }
+
Log.w(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
- return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize);
+ return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize, width, height);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@@ -493,15 +514,17 @@ public class AttachmentManager {
@NonNull Uri uri,
@Nullable String fileName,
@Nullable String mimeType,
- long dataSize)
+ long dataSize,
+ int width,
+ int height)
{
if (mimeType == null) {
mimeType = "application/octet-stream";
}
switch (this) {
- case IMAGE: return new ImageSlide(context, uri, dataSize);
- case GIF: return new GifSlide(context, uri, dataSize);
+ case IMAGE: return new ImageSlide(context, uri, dataSize, width, height);
+ case GIF: return new GifSlide(context, uri, dataSize, width, height);
case AUDIO: return new AudioSlide(context, uri, dataSize, false);
case VIDEO: return new VideoSlide(context, uri, dataSize);
case DOCUMENT: return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
index 443bf0383f..5010e63624 100644
--- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
@@ -34,11 +34,11 @@ import org.thoughtcrime.securesms.util.ResUtil;
public class AudioSlide extends Slide {
public AudioSlide(Context context, Uri uri, long dataSize, boolean voiceNote) {
- super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, false, null, voiceNote));
+ super(context, constructAttachmentFromUri(context, uri, MediaUtil.AUDIO_UNSPECIFIED, dataSize, 0, 0, false, null, voiceNote));
}
public AudioSlide(Context context, Uri uri, long dataSize, String contentType, boolean voiceNote) {
- super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, null, null, voiceNote));
+ super(context, new UriAttachment(uri, null, contentType, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, dataSize, 0, 0, null, null, voiceNote));
}
public AudioSlide(Context context, Attachment attachment) {
diff --git a/src/org/thoughtcrime/securesms/mms/DocumentSlide.java b/src/org/thoughtcrime/securesms/mms/DocumentSlide.java
index 3d7e8d37b8..72d19a1a5e 100644
--- a/src/org/thoughtcrime/securesms/mms/DocumentSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/DocumentSlide.java
@@ -19,7 +19,7 @@ public class DocumentSlide extends Slide {
@NonNull String contentType, long size,
@Nullable String fileName)
{
- super(context, constructAttachmentFromUri(context, uri, contentType, size, true, StorageUtil.getCleanFileName(fileName), false));
+ super(context, constructAttachmentFromUri(context, uri, contentType, size, 0, 0, true, StorageUtil.getCleanFileName(fileName), false));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/mms/GifSlide.java b/src/org/thoughtcrime/securesms/mms/GifSlide.java
index f6443180f7..2bdc688a31 100644
--- a/src/org/thoughtcrime/securesms/mms/GifSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/GifSlide.java
@@ -13,8 +13,8 @@ public class GifSlide extends ImageSlide {
super(context, attachment);
}
- public GifSlide(Context context, Uri uri, long size) {
- super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, true, null, false));
+ public GifSlide(Context context, Uri uri, long size, int width, int height) {
+ super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_GIF, size, width, height, true, null, false));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
index 795b651f0c..be15b339d7 100644
--- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
@@ -36,8 +36,8 @@ public class ImageSlide extends Slide {
super(context, attachment);
}
- public ImageSlide(Context context, Uri uri, long size) {
- super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, true, null, false));
+ public ImageSlide(Context context, Uri uri, long size, int width, int height) {
+ super(context, constructAttachmentFromUri(context, uri, MediaUtil.IMAGE_JPEG, size, width, height, true, null, false));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/mms/LocationSlide.java b/src/org/thoughtcrime/securesms/mms/LocationSlide.java
index f679b8bf82..e5f613d2f9 100644
--- a/src/org/thoughtcrime/securesms/mms/LocationSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/LocationSlide.java
@@ -14,7 +14,7 @@ public class LocationSlide extends ImageSlide {
public LocationSlide(@NonNull Context context, @NonNull Uri uri, long size, @NonNull SignalPlace place)
{
- super(context, uri, size);
+ super(context, uri, size, 0, 0);
this.place = place;
}
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index 623cd3c847..7e49b6484c 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -22,6 +22,7 @@ import android.net.Uri;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.util.Pair;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment;
@@ -132,14 +133,25 @@ public abstract class Slide {
@NonNull Uri uri,
@NonNull String defaultMime,
long size,
+ int width,
+ int height,
boolean hasThumbnail,
@Nullable String fileName,
boolean voiceNote)
{
try {
- Optional resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri));
- String fastPreflightId = String.valueOf(SecureRandom.getInstance("SHA1PRNG").nextLong());
- return new UriAttachment(uri, hasThumbnail ? uri : null, resolvedType.or(defaultMime), AttachmentDatabase.TRANSFER_PROGRESS_STARTED, size, fileName, fastPreflightId, voiceNote);
+ String resolvedType = Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime);
+ String fastPreflightId = String.valueOf(SecureRandom.getInstance("SHA1PRNG").nextLong());
+ return new UriAttachment(uri,
+ hasThumbnail ? uri : null,
+ resolvedType,
+ AttachmentDatabase.TRANSFER_PROGRESS_STARTED,
+ size,
+ width,
+ height,
+ fileName,
+ fastPreflightId,
+ voiceNote);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
index 146cb7bdef..278934f8c6 100644
--- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.util.ResUtil;
public class VideoSlide extends Slide {
public VideoSlide(Context context, Uri uri, long dataSize) {
- super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, MediaUtil.hasVideoThumbnail(uri), null, false));
+ super(context, constructAttachmentFromUri(context, uri, MediaUtil.VIDEO_UNSPECIFIED, dataSize, 0, 0, MediaUtil.hasVideoThumbnail(uri), null, false));
}
public VideoSlide(Context context, Attachment attachment) {
diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
index dc8f391ceb..90ccb50479 100644
--- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java
+++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
@@ -12,6 +12,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.*;
import android.support.annotation.WorkerThread;
+import android.support.media.ExifInterface;
import android.util.Log;
import android.util.Pair;
@@ -131,6 +132,26 @@ public class BitmapUtil {
return options;
}
+ @Nullable
+ public static Pair getExifDimensions(InputStream inputStream) throws IOException {
+ ExifInterface exif = new ExifInterface(inputStream);
+ int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
+ int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
+ if (width == 0 && height == 0) {
+ return null;
+ }
+
+ int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
+ if (orientation == ExifInterface.ORIENTATION_ROTATE_90 ||
+ orientation == ExifInterface.ORIENTATION_ROTATE_270 ||
+ orientation == ExifInterface.ORIENTATION_TRANSVERSE ||
+ orientation == ExifInterface.ORIENTATION_TRANSPOSE)
+ {
+ return new Pair<>(height, width);
+ }
+ return new Pair<>(width, height);
+ }
+
public static Pair getDimensions(InputStream inputStream) throws BitmapDecodingException {
BitmapFactory.Options options = getImageDimensions(inputStream);
return new Pair<>(options.outWidth, options.outHeight);
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index 2f023e1c77..b0f5441db9 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -3,18 +3,27 @@ package org.thoughtcrime.securesms.util;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.support.media.ExifInterface;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.webkit.MimeTypeMap;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.bumptech.glide.load.resource.gif.GifDrawable;
+
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.mms.AudioSlide;
+import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.DocumentSlide;
import org.thoughtcrime.securesms.mms.GifSlide;
+import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.MmsSlide;
import org.thoughtcrime.securesms.mms.PartAuthority;
@@ -22,8 +31,10 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.concurrent.ExecutionException;
public class MediaUtil {
@@ -100,6 +111,65 @@ public class MediaUtil {
return size;
}
+ @WorkerThread
+ public static Pair getDimensions(@NonNull Context context, @Nullable String contentType, @Nullable Uri uri) {
+ if (uri == null || !MediaUtil.isImageType(contentType)) {
+ return new Pair<>(0, 0);
+ }
+
+ Pair dimens = null;
+
+ if (MediaUtil.isGif(contentType)) {
+ try {
+ GifDrawable drawable = GlideApp.with(context)
+ .asGif()
+ .skipMemoryCache(true)
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .load(new DecryptableUri(uri))
+ .submit()
+ .get();
+ dimens = new Pair<>(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Was unable to complete work for GIF dimensions.", e);
+ } catch (ExecutionException e) {
+ Log.w(TAG, "Glide experienced an exception while trying to get GIF dimensions.", e);
+ }
+ } else {
+ InputStream attachmentStream = null;
+ try {
+ if (MediaUtil.isJpegType(contentType)) {
+ attachmentStream = PartAuthority.getAttachmentStream(context, uri);
+ dimens = BitmapUtil.getExifDimensions(attachmentStream);
+ attachmentStream.close();
+ attachmentStream = null;
+ }
+ if (dimens == null) {
+ attachmentStream = PartAuthority.getAttachmentStream(context, uri);
+ dimens = BitmapUtil.getDimensions(attachmentStream);
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Failed to find file when retrieving media dimensions.", e);
+ } catch (IOException e) {
+ Log.w(TAG, "Experienced a read error when retrieving media dimensions.", e);
+ } catch (BitmapDecodingException e) {
+ Log.w(TAG, "Bitmap decoding error when retrieving dimensions.", e);
+ } finally {
+ if (attachmentStream != null) {
+ try {
+ attachmentStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to close stream after retrieving dimensions.", e);
+ }
+ }
+ }
+ }
+ if (dimens == null) {
+ dimens = new Pair<>(0, 0);
+ }
+ Log.d(TAG, "Dimensions for [" + uri + "] are " + dimens.first + " x " + dimens.second);
+ return dimens;
+ }
+
public static boolean isMms(String contentType) {
return !TextUtils.isEmpty(contentType) && contentType.trim().equals("application/mms");
}