mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:18:27 +00:00
parent
9ba19df2af
commit
f42d100f15
@ -45,6 +45,9 @@
|
||||
<meta-data android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
|
||||
<meta-data android:name="org.thoughtcrime.securesms.mms.TextSecureGlideModule"
|
||||
android:value="GlideModule" />
|
||||
|
||||
<activity android:name=".RegistrationProblemsActivity"
|
||||
android:theme="@style/TextSecure.Light.Dialog"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
@ -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
|
||||
}
|
||||
|
4
proguard-glide.pro
Normal file
4
proguard-glide.pro
Normal file
@ -0,0 +1,4 @@
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
@ -36,7 +36,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/attachment_thumbnail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="150dip"
|
||||
|
@ -19,14 +19,14 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ForegroundImageView
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/media_bubble_height"
|
||||
android:layout_marginRight="@dimen/message_bubble_end_padding"
|
||||
android:visibility="gone"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:scaleType="centerInside"
|
||||
android:adjustViewBounds="true"
|
||||
android:contentDescription="@string/conversation_item__mms_image_description"
|
||||
app:riv_corner_radius="@dimen/message_bubble_corner_radius"
|
||||
|
@ -17,7 +17,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ForegroundImageView
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/media_bubble_height"
|
||||
|
@ -6,7 +6,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ForegroundImageView
|
||||
<org.thoughtcrime.securesms.components.ThumbnailView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<color name="textsecure_primary">#ff2090ea</color>
|
||||
<color name="textsecure_primary_dark">#ff145c95</color>
|
||||
<color name="textsecure_primary_dark">#ff1c7ac5</color>
|
||||
|
||||
<color name="white">#ffffffff</color>
|
||||
<color name="black">#ff000000</color>
|
||||
|
@ -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<MessageRecord> batchSelected;
|
||||
private SelectionClickListener selectionClickListener;
|
||||
private ForegroundImageView mediaThumbnail;
|
||||
private ThumbnailView mediaThumbnail;
|
||||
private Button mmsDownloadButton;
|
||||
private TextView mmsDownloadingLabel;
|
||||
|
||||
private ListenableFutureTask<SlideDeck> slideDeckFuture;
|
||||
private ListenableFutureTask<Pair<Drawable, Boolean>> 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<Pair<Drawable, Boolean>> {
|
||||
private final Object tag;
|
||||
|
||||
public ThumbnailListener(Object tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(final Pair<Drawable, Boolean> 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<SlideDeck> {
|
||||
@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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ViewHolder> {
|
||||
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<ViewHolder> {
|
||||
|
||||
@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<ViewHolder> {
|
||||
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<Pair<Drawable, Boolean>>() {
|
||||
@Override
|
||||
public void onSuccess(final Pair<Drawable, Boolean> 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));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
202
src/org/thoughtcrime/securesms/components/ThumbnailView.java
Normal file
202
src/org/thoughtcrime/securesms/components/ThumbnailView.java
Normal file
@ -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<SlideDeck> 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<SlideDeck> 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<SlideDeck> {
|
||||
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<Uri, Bitmap> {
|
||||
private PduPart part;
|
||||
|
||||
public PduThumbnailSetListener(@NonNull PduPart part) {
|
||||
this.part = part;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onException(Exception e, Uri model, Target<Bitmap> target, boolean isFirstResource) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Bitmap resource, Uri model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
|
||||
part.setThumbnail(resource);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Pair<Drawable, Boolean>>() {
|
||||
@Override
|
||||
public void onSuccess(final Pair<Drawable, Boolean> 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() {
|
||||
|
@ -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<Pair<Drawable,Boolean>> 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 {
|
||||
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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<DecryptableUri> {
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* THe default factory for {@link com.bumptech.glide.load.model.stream.StreamUriLoader}s.
|
||||
*/
|
||||
public static class Factory implements ModelLoaderFactory<DecryptableUri, InputStream> {
|
||||
|
||||
@Override
|
||||
public StreamModelLoader<DecryptableUri> 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<InputStream> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Uri, SoftReference<Drawable>> thumbnailCache =
|
||||
Collections.synchronizedMap(new LRUCache<Uri, SoftReference<Drawable>>(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<Pair<Drawable,Boolean>> 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<Pair<Drawable,Boolean>> resolveThumbnail(Context context) {
|
||||
final WeakReference<Context> weakContext = new WeakReference<>(context);
|
||||
|
||||
Callable<Pair<Drawable,Boolean>> slideCallable = new Callable<Pair<Drawable, Boolean>>() {
|
||||
@Override
|
||||
public Pair<Drawable, Boolean> 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<Pair<Drawable,Boolean>> futureTask = new ListenableFutureTask<>(slideCallable);
|
||||
MmsDatabase.slideResolver.execute(futureTask);
|
||||
return futureTask;
|
||||
}
|
||||
|
||||
private Drawable getCachedThumbnail() {
|
||||
synchronized (thumbnailCache) {
|
||||
SoftReference<Drawable> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Pair<Drawable,Boolean>> 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
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> 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<String>(text));
|
||||
|
||||
return text;
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
Log.w("TextSlide", uee);
|
||||
return new String(getPartData());
|
||||
}
|
||||
}
|
||||
|
||||
private static PduPart getPartForMessage(String message) {
|
||||
PduPart part = new PduPart();
|
||||
|
||||
|
48
src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java
Normal file
48
src/org/thoughtcrime/securesms/mms/ThumbnailTransform.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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<Pair<Drawable,Boolean>> 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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user