Update to glide 4.x

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2017-10-11 17:12:46 -07:00
parent 17dd681dc8
commit 10f224ede5
35 changed files with 639 additions and 633 deletions

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
@@ -40,8 +40,9 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
@@ -58,7 +59,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
import org.thoughtcrime.securesms.mms.RoundedCorners;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
@@ -306,16 +307,19 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
new Crop(data.getData()).output(outputFile).asSquare().start(this);
break;
case Crop.REQUEST_CROP:
Glide.with(this).load(Crop.getOutput(data)).asBitmap()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop().override(AVATAR_SIZE, AVATAR_SIZE)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
setAvatar(Crop.getOutput(data), resource);
}
});
GlideApp.with(this)
.asBitmap()
.load(Crop.getOutput(data))
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.override(AVATAR_SIZE, AVATAR_SIZE)
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
setAvatar(Crop.getOutput(data), resource);
}
});
}
}
@@ -573,12 +577,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private <T> void setAvatar(T model, Bitmap bitmap) {
avatarBmp = bitmap;
Glide.with(this)
.load(model)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(new RoundedCorners(this, avatar.getWidth() / 2))
.into(avatar);
GlideApp.with(this)
.load(model)
.circleCrop()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(avatar);
}
private static class GroupData {

View File

@@ -4,7 +4,6 @@ import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.MenuItem;
import android.widget.Toast;

View File

@@ -20,7 +20,6 @@ import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.signature.MediaStoreSignature;
@@ -28,6 +27,7 @@ import com.bumptech.glide.signature.MediaStoreSignature;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.util.ViewUtil;
public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks<Cursor> {
@@ -111,12 +111,11 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
Key signature = new MediaStoreSignature(mimeType, dateModified, orientation);
Glide.with(getContext())
.fromMediaStore()
.load(uri)
.signature(signature)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(viewHolder.imageView);
GlideApp.with(getContext())
.load(uri)
.signature(signature)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(viewHolder.imageView);
viewHolder.imageView.setOnClickListener(new OnClickListener() {
@Override

View File

@@ -15,22 +15,25 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.RoundedCorners;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
public class ThumbnailView extends FrameLayout {
private static final String TAG = ThumbnailView.class.getSimpleName();
@@ -141,18 +144,18 @@ public class ThumbnailView extends FrameLayout {
if (slide.getThumbnailUri() != null) buildThumbnailGlideRequest(slide, masterSecret).into(image);
else if (slide.hasPlaceholder()) buildPlaceholderGlideRequest(slide).into(image);
else Glide.clear(image);
else Glide.with(getContext()).clear(image);
}
public void setImageResource(@NonNull MasterSecret masterSecret, @NonNull Uri uri) {
if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE);
Glide.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.crossFade()
.transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint))
.into(image);
GlideApp.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(new RoundedCorners(radius))
.transition(withCrossFade())
.into(image);
}
public void setThumbnailClickListener(SlideClickListener listener) {
@@ -164,7 +167,7 @@ public class ThumbnailView extends FrameLayout {
}
public void clear() {
if (isContextValid()) Glide.clear(image);
if (isContextValid()) Glide.with(getContext()).clear(image);
if (transferControls.isPresent()) getTransferControls().clear();
slide = null;
@@ -181,24 +184,23 @@ public class ThumbnailView extends FrameLayout {
!((Activity)getContext()).isDestroyed();
}
private GenericRequestBuilder buildThumbnailGlideRequest(@NonNull Slide slide, @NonNull MasterSecret masterSecret) {
@SuppressWarnings("ConstantConditions")
DrawableRequestBuilder<DecryptableUri> builder = Glide.with(getContext())
.load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.crossFade()
.transform(new RoundedCorners(getContext(), true, radius, backgroundColorHint));
private RequestBuilder buildThumbnailGlideRequest(@NonNull Slide slide, @NonNull MasterSecret masterSecret) {
RequestBuilder builder = GlideApp.with(getContext())
.load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.transform(new RoundedCorners(radius))
.transition(withCrossFade());
if (slide.isInProgress()) return builder;
else return builder.error(R.drawable.ic_missing_thumbnail_picture);
else return builder.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture));
}
private GenericRequestBuilder buildPlaceholderGlideRequest(Slide slide) {
return Glide.with(getContext())
.load(slide.getPlaceholderRes(getContext().getTheme()))
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter();
private RequestBuilder buildPlaceholderGlideRequest(Slide slide) {
return GlideApp.with(getContext())
.asBitmap()
.load(slide.getPlaceholderRes(getContext().getTheme()))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter();
}
private class ThumbnailClickDispatcher implements View.OnClickListener {

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.annotation.Nullable;
@@ -10,37 +9,33 @@ import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
import com.bumptech.glide.request.target.Target;
import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
import com.github.chrisbanes.photoview.PhotoView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.subsampling.AttachmentBitmapDecoder;
import org.thoughtcrime.securesms.components.subsampling.AttachmentRegionDecoder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.io.IOException;
import java.io.InputStream;
import uk.co.senab.photoview.PhotoViewAttacher;
public class ZoomingImageView extends FrameLayout {
private static final String TAG = ZoomingImageView.class.getName();
private final ImageView imageView;
private final PhotoViewAttacher imageViewAttacher;
private final PhotoView photoView;
private final SubsamplingScaleImageView subsamplingImageView;
public ZoomingImageView(Context context) {
@@ -56,9 +51,8 @@ public class ZoomingImageView extends FrameLayout {
inflate(context, R.layout.zooming_image_view, this);
this.imageView = (ImageView) findViewById(R.id.image_view);
this.subsamplingImageView = (SubsamplingScaleImageView) findViewById(R.id.subsampling_image_view);
this.imageViewAttacher = new PhotoViewAttacher(imageView);
this.photoView = findViewById(R.id.image_view);
this.subsamplingImageView = findViewById(R.id.subsampling_image_view);
this.subsamplingImageView.setBitmapDecoderClass(AttachmentBitmapDecoder.class);
this.subsamplingImageView.setRegionDecoderClass(AttachmentRegionDecoder.class);
@@ -74,7 +68,7 @@ public class ZoomingImageView extends FrameLayout {
new AsyncTask<Void, Void, Pair<Integer, Integer>>() {
@Override
protected @Nullable Pair<Integer, Integer> doInBackground(Void... params) {
if (contentType.equals("image/gif")) return null;
if (MediaUtil.isGif(contentType)) return null;
try {
InputStream inputStream = PartAuthority.getAttachmentStream(context, masterSecret, uri);
@@ -100,32 +94,26 @@ public class ZoomingImageView extends FrameLayout {
}
private void setImageViewUri(MasterSecret masterSecret, Uri uri) {
photoView.setVisibility(View.VISIBLE);
subsamplingImageView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
Glide.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontTransform()
.dontAnimate()
.into(new GlideDrawableImageViewTarget(imageView) {
@Override protected void setResource(GlideDrawable resource) {
super.setResource(resource);
imageViewAttacher.update();
}
});
GlideApp.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontTransform()
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.into(photoView);
}
private void setSubsamplingImageViewUri(Uri uri) {
subsamplingImageView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
photoView.setVisibility(View.GONE);
subsamplingImageView.setImage(ImageSource.uri(uri));
}
public void cleanup() {
imageView.setImageDrawable(null);
photoView.setImageDrawable(null);
subsamplingImageView.recycle();
}
}

View File

@@ -11,13 +11,12 @@ import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.ContactPhotoUriLoader.ContactPhotoUri;
import org.thoughtcrime.securesms.profiles.AvatarPhotoUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.profiles.AvatarPhotoUriLoader.AvatarPhotoUri;
import java.util.concurrent.ExecutionException;
@@ -58,10 +57,13 @@ public class ContactPhotoFactory {
if (uri == null) return getSignalAvatarContactPhoto(context, address, name, targetSize);
try {
Bitmap bitmap = Glide.with(context)
.load(new ContactPhotoUri(uri)).asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop().into(targetSize, targetSize).get();
Bitmap bitmap = GlideApp.with(context)
.asBitmap()
.load(new ContactPhotoUri(uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.submit(targetSize, targetSize)
.get();
return new BitmapContactPhoto(bitmap);
} catch (ExecutionException e) {
return getSignalAvatarContactPhoto(context, address, name, targetSize);
@@ -83,14 +85,14 @@ public class ContactPhotoFactory {
int targetSize)
{
try {
Bitmap bitmap = Glide.with(context)
.load(new AvatarPhotoUri(address))
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.centerCrop()
.into(targetSize, targetSize)
.get();
Bitmap bitmap = GlideApp.with(context)
.asBitmap()
.load(new AvatarPhotoUri(address))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.centerCrop()
.submit(targetSize, targetSize)
.get();
return new BitmapContactPhoto(bitmap);
} catch (IllegalArgumentException e) {

View File

@@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.giph.ui;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@@ -11,16 +13,18 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.giph.model.GiphyImage;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -37,7 +41,7 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
private Context context;
private OnItemClickListener listener;
class GiphyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, RequestListener<String, GlideDrawable> {
class GiphyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, RequestListener<Drawable> {
public AspectRatioImageView thumbnail;
public GiphyImage image;
@@ -58,7 +62,7 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
}
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
Log.w(TAG, e);
synchronized (this) {
@@ -69,10 +73,11 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
}
return false;
}
@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
synchronized (this) {
if (image.getGifUrl().equals(model)) {
this.modelReady = true;
@@ -83,6 +88,7 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
return false;
}
public File getFile(boolean forMms) throws ExecutionException, InterruptedException {
synchronized (this) {
while (!modelReady) {
@@ -103,7 +109,7 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
}
GiphyAdapter(Context context, List<GiphyImage> images) {
this.context = context;
this.context = context.getApplicationContext();
this.images = images;
}
@@ -134,32 +140,33 @@ public class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHol
holder.thumbnail.setAspectRatio(image.getGifAspectRatio());
holder.gifProgress.setVisibility(View.GONE);
DrawableRequestBuilder<String> thumbnailRequest = Glide.with(context)
.load(image.getStillUrl());
RequestBuilder<Drawable> thumbnailRequest = GlideApp.with(context)
.load(image.getStillUrl())
.diskCacheStrategy(DiskCacheStrategy.ALL);
if (Util.isLowMemory(context)) {
Glide.with(context)
.load(image.getStillUrl())
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.thumbnail);
GlideApp.with(context)
.load(image.getStillUrl())
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(holder.thumbnail);
holder.setModelReady();
} else {
Glide.with(context)
.load(image.getGifUrl())
.thumbnail(thumbnailRequest)
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(holder)
.into(holder.thumbnail);
GlideApp.with(context)
.load(image.getGifUrl())
.thumbnail(thumbnailRequest)
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.listener(holder)
.into(holder.thumbnail);
}
}
@Override
public void onViewRecycled(GiphyViewHolder holder) {
super.onViewRecycled(holder);
Glide.clear(holder.thumbnail);
Glide.with(context).clear(holder.thumbnail);
}
@Override

View File

@@ -1,6 +1,9 @@
package org.thoughtcrime.securesms.glide;
import android.support.annotation.NonNull;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.util.ContentLengthInputStream;
@@ -17,7 +20,7 @@ import okhttp3.ResponseBody;
/**
* Fetches an {@link InputStream} using the okhttp library.
*/
public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
class OkHttpStreamFetcher implements DataFetcher<InputStream> {
private static final String TAG = OkHttpStreamFetcher.class.getName();
@@ -26,33 +29,38 @@ public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
private InputStream stream;
private ResponseBody responseBody;
public OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {
OkHttpStreamFetcher(OkHttpClient client, GlideUrl url) {
this.client = client;
this.url = url;
}
@Override
public InputStream loadData(Priority priority) throws Exception {
Request.Builder requestBuilder = new Request.Builder()
.url(url.toStringUrl());
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
try {
Request.Builder requestBuilder = new Request.Builder()
.url(url.toStringUrl());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
String key = headerEntry.getKey();
requestBuilder.addHeader(key, headerEntry.getValue());
}
Request request = requestBuilder.build();
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
callback.onDataReady(stream);
} catch (IOException e) {
callback.onLoadFailed(e);
}
Request request = requestBuilder.build();
Response response = client.newCall(request).execute();
responseBody = response.body();
if (!response.isSuccessful()) {
throw new IOException("Request failed with code: " + response.code());
}
long contentLength = responseBody.contentLength();
stream = ContentLengthInputStream.obtain(responseBody.byteStream(), contentLength);
return stream;
}
@Override
@@ -69,13 +77,20 @@ public class OkHttpStreamFetcher implements DataFetcher<InputStream> {
}
}
@Override
public String getId() {
return url.getCacheKey();
}
@Override
public void cancel() {
// TODO: call cancel on the client when this method is called on a background thread. See #257
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}

View File

@@ -1,12 +1,12 @@
package org.thoughtcrime.securesms.glide;
import android.content.Context;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.giph.net.GiphyProxySelector;
@@ -19,9 +19,23 @@ import okhttp3.OkHttpClient;
*/
public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
/**
* The default factory for {@link OkHttpUrlLoader}s.
*/
private final OkHttpClient client;
private OkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(GlideUrl glideUrl, int width, int height, Options options) {
return new LoadData<>(glideUrl, new OkHttpStreamFetcher(client, glideUrl));
}
@Override
public boolean handles(GlideUrl glideUrl) {
return true;
}
public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
private static volatile OkHttpClient internalClient;
private OkHttpClient client;
@@ -39,22 +53,16 @@ public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
return internalClient;
}
/**
* Constructor for a new Factory that runs requests using a static singleton client.
*/
public Factory() {
this(getInternalClient());
}
/**
* Constructor for a new Factory that runs requests using given client.
*/
private Factory(OkHttpClient client) {
this.client = client;
}
@Override
public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) {
public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new OkHttpUrlLoader(client);
}
@@ -63,15 +71,4 @@ public class OkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> {
// Do nothing, this instance doesn't own the client.
}
}
private final OkHttpClient client;
private OkHttpUrlLoader(OkHttpClient client) {
this.client = client;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) {
return new OkHttpStreamFetcher(client, model);
}
}

View File

@@ -1,40 +1,45 @@
package org.thoughtcrime.securesms.mms;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.data.StreamLocalUriFetcher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class AttachmentStreamLocalUriFetcher implements DataFetcher<InputStream> {
class AttachmentStreamLocalUriFetcher implements DataFetcher<InputStream> {
private static final String TAG = AttachmentStreamLocalUriFetcher.class.getSimpleName();
private File attachment;
private byte[] key;
private InputStream is;
public AttachmentStreamLocalUriFetcher(File attachment, byte[] key) {
AttachmentStreamLocalUriFetcher(File attachment, byte[] key) {
this.attachment = attachment;
this.key = key;
}
@Override public InputStream loadData(Priority priority) throws Exception {
is = new AttachmentCipherInputStream(attachment, key, Optional.<byte[]>absent());
return is;
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
try {
is = new AttachmentCipherInputStream(attachment, key, Optional.absent());
callback.onDataReady(is);
} catch (IOException | InvalidMessageException e) {
callback.onLoadFailed(e);
}
}
@Override public void cleanup() {
@Override
public void cleanup() {
try {
if (is != null) is.close();
is = null;
@@ -43,11 +48,20 @@ public class AttachmentStreamLocalUriFetcher implements DataFetcher<InputStream>
}
}
@Override public String getId() {
return AttachmentStreamLocalUriFetcher.class.getCanonicalName() + "::" + attachment.getAbsolutePath();
@Override
public void cancel() {}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@Override public void cancel() {
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}

View File

@@ -1,39 +1,38 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import java.io.File;
import java.io.InputStream;
import java.security.MessageDigest;
/**
* 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 AttachmentStreamUriLoader implements StreamModelLoader<AttachmentModel> {
private final Context context;
public class AttachmentStreamUriLoader implements ModelLoader<AttachmentModel, InputStream> {
/**
* THe default factory for {@link com.bumptech.glide.load.model.stream.StreamUriLoader}s.
*/
public static class Factory implements ModelLoaderFactory<AttachmentModel, InputStream> {
@Nullable
@Override
public LoadData<InputStream> buildLoadData(AttachmentModel attachmentModel, int width, int height, Options options) {
return new LoadData<>(attachmentModel, new AttachmentStreamLocalUriFetcher(attachmentModel.attachment, attachmentModel.key));
}
@Override
public boolean handles(AttachmentModel attachmentModel) {
return true;
}
static class Factory implements ModelLoaderFactory<AttachmentModel, InputStream> {
@Override
public StreamModelLoader<AttachmentModel> build(Context context, GenericLoaderFactory factories) {
return new AttachmentStreamUriLoader(context);
public ModelLoader<AttachmentModel, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new AttachmentStreamUriLoader();
}
@Override
@@ -42,16 +41,7 @@ public class AttachmentStreamUriLoader implements StreamModelLoader<AttachmentMo
}
}
public AttachmentStreamUriLoader(Context context) {
this.context = context;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(AttachmentModel model, int width, int height) {
return new AttachmentStreamLocalUriFetcher(model.attachment, model.key);
}
public static class AttachmentModel {
public static class AttachmentModel implements Key {
public @NonNull File attachment;
public @NonNull byte[] key;
@@ -60,6 +50,11 @@ public class AttachmentStreamUriLoader implements StreamModelLoader<AttachmentMo
this.key = key;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(attachment.toString().getBytes());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -12,11 +12,12 @@ import com.bumptech.glide.load.data.StreamLocalUriFetcher;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class ContactPhotoLocalUriFetcher extends StreamLocalUriFetcher {
class ContactPhotoLocalUriFetcher extends StreamLocalUriFetcher {
private static final String TAG = ContactPhotoLocalUriFetcher.class.getSimpleName();
public ContactPhotoLocalUriFetcher(Context context, Uri uri) {
super(context, uri);
ContactPhotoLocalUriFetcher(Context context, Uri uri) {
super(context.getContentResolver(), uri);
}
@Override

View File

@@ -3,26 +3,49 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.data.StreamLocalUriFetcher;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.mms.ContactPhotoUriLoader.ContactPhotoUri;
import java.io.InputStream;
import java.security.MessageDigest;
public class ContactPhotoUriLoader implements ModelLoader<ContactPhotoUri, InputStream> {
public class ContactPhotoUriLoader implements StreamModelLoader<ContactPhotoUri> {
private final Context context;
/**
* THe default factory for {@link com.bumptech.glide.load.model.stream.StreamUriLoader}s.
*/
public static class Factory implements ModelLoaderFactory<ContactPhotoUri, InputStream> {
private ContactPhotoUriLoader(Context context) {
this.context = context;
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(ContactPhotoUri contactPhotoUri, int width, int height, Options options) {
return new LoadData<>(contactPhotoUri, new StreamLocalUriFetcher(context.getContentResolver(), contactPhotoUri.uri));
}
@Override
public boolean handles(ContactPhotoUri contactPhotoUri) {
return true;
}
static class Factory implements ModelLoaderFactory<ContactPhotoUri, InputStream> {
private final Context context;
Factory(Context context) {
this.context = context.getApplicationContext();
}
@Override
public StreamModelLoader<ContactPhotoUri> build(Context context, GenericLoaderFactory factories) {
public ModelLoader<ContactPhotoUri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new ContactPhotoUriLoader(context);
}
@@ -32,21 +55,28 @@ public class ContactPhotoUriLoader implements StreamModelLoader<ContactPhotoUri>
}
}
public ContactPhotoUriLoader(Context context) {
this.context = context;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(ContactPhotoUri model, int width, int height) {
return new ContactPhotoLocalUriFetcher(context, model.uri);
}
public static class ContactPhotoUri {
public static class ContactPhotoUri implements Key {
public @NonNull Uri uri;
public ContactPhotoUri(@NonNull Uri uri) {
this.uri = uri;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(uri.toString().getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof ContactPhotoUri)) return false;
return this.uri.equals(((ContactPhotoUri)other).uri);
}
public int hashCode() {
return uri.hashCode();
}
}
}

View File

@@ -17,15 +17,15 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class DecryptableStreamLocalUriFetcher extends StreamLocalUriFetcher {
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);
DecryptableStreamLocalUriFetcher(Context context, MasterSecret masterSecret, Uri uri) {
super(context.getContentResolver(), uri);
this.context = context;
this.masterSecret = masterSecret;
}

View File

@@ -3,33 +3,49 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import java.io.InputStream;
import java.security.MessageDigest;
public class DecryptableStreamUriLoader implements ModelLoader<DecryptableUri, 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> {
private DecryptableStreamUriLoader(Context context) {
this.context = context;
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(DecryptableUri decryptableUri, int width, int height, Options options) {
return new LoadData<>(decryptableUri, new DecryptableStreamLocalUriFetcher(context, decryptableUri.masterSecret, decryptableUri.uri));
}
@Override
public boolean handles(DecryptableUri decryptableUri) {
return true;
}
static class Factory implements ModelLoaderFactory<DecryptableUri, InputStream> {
private final Context context;
Factory(Context context) {
this.context = context.getApplicationContext();
}
@Override
public StreamModelLoader<DecryptableUri> build(Context context, GenericLoaderFactory factories) {
public ModelLoader<DecryptableUri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new DecryptableStreamUriLoader(context);
}
@@ -39,16 +55,7 @@ public class DecryptableStreamUriLoader implements StreamModelLoader<Decryptable
}
}
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 static class DecryptableUri implements Key {
public @NonNull MasterSecret masterSecret;
public @NonNull Uri uri;
@@ -57,6 +64,11 @@ public class DecryptableStreamUriLoader implements StreamModelLoader<Decryptable
this.uri = uri;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(uri.toString().getBytes());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@@ -37,7 +37,6 @@ import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.thoughtcrime.securesms.database.ApnDatabase;
import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -130,6 +129,7 @@ public abstract class LegacyMmsConnection {
Log.w(TAG, "Checking route to address: " + host + ", " + inetAddress.getHostAddress());
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
try {
final Method requestRouteMethod = manager.getClass().getMethod("requestRouteToHostAddress", Integer.TYPE, InetAddress.class);
final boolean routeToHostObtained = (Boolean) requestRouteMethod.invoke(manager, MmsRadio.TYPE_MOBILE_MMS, inetAddress);
@@ -143,10 +143,7 @@ public abstract class LegacyMmsConnection {
Log.w(TAG, ite);
}
final int ipAddress = Conversions.byteArrayToIntLittleEndian(ipAddressBytes, 0);
final boolean routeToHostObtained = manager.requestRouteToHost(MmsRadio.TYPE_MOBILE_MMS, ipAddress);
Log.w(TAG, "requestRouteToHost(" + ipAddress + ") -> " + routeToHostObtained);
return routeToHostObtained;
return false;
}
protected static byte[] parseResponse(InputStream is) throws IOException {

View File

@@ -11,8 +11,13 @@ import android.util.Log;
import org.thoughtcrime.securesms.util.Util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MmsRadio {
private static final String TAG = MmsRadio.class.getSimpleName();
private static MmsRadio instance;
public static synchronized MmsRadio getInstance(Context context) {
@@ -52,8 +57,17 @@ public class MmsRadio {
if (connectedCounter == 0) {
Log.w("MmsRadio", "Turning off MMS radio...");
connectivityManager.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
try {
final Method stopUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("stopUsingNetworkFeature", Integer.TYPE, String.class);
stopUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
Log.w(TAG, nsme);
} catch (IllegalAccessException iae) {
Log.w(TAG, iae);
} catch (InvocationTargetException ite) {
Log.w(TAG, ite);
}
if (connectivityListener != null) {
Log.w("MmsRadio", "Unregistering receiver...");
context.unregisterReceiver(connectivityListener);
@@ -63,8 +77,18 @@ public class MmsRadio {
}
public synchronized void connect() throws MmsRadioException {
int status = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
FEATURE_ENABLE_MMS);
int status;
try {
final Method startUsingNetworkFeatureMethod = connectivityManager.getClass().getMethod("startUsingNetworkFeature", Integer.TYPE, String.class);
status = (int)startUsingNetworkFeatureMethod.invoke(connectivityManager, ConnectivityManager.TYPE_MOBILE, FEATURE_ENABLE_MMS);
} catch (NoSuchMethodException nsme) {
throw new MmsRadioException(nsme);
} catch (IllegalAccessException iae) {
throw new MmsRadioException(iae);
} catch (InvocationTargetException ite) {
throw new MmsRadioException(ite);
}
Log.w("MmsRadio", "startUsingNetworkFeature status: " + status);

View File

@@ -4,4 +4,8 @@ public class MmsRadioException extends Throwable {
public MmsRadioException(String s) {
super(s);
}
public MmsRadioException(Exception e) {
super(e);
}
}

View File

@@ -1,105 +0,0 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
import org.thoughtcrime.securesms.util.ResUtil;
public class RoundedCorners extends BitmapTransformation {
private final boolean crop;
private final int radius;
private final int colorHint;
public RoundedCorners(@NonNull Context context, boolean crop, int radius, int colorHint) {
super(context);
this.crop = crop;
this.radius = radius;
this.colorHint = colorHint;
}
public RoundedCorners(@NonNull Context context, int radius) {
this(context, true, radius, ResUtil.getColor(context, android.R.attr.windowBackground));
}
@Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth,
int outHeight)
{
final Bitmap toRound = crop ? centerCrop(pool, toTransform, outWidth, outHeight)
: fitCenter(pool, toTransform, outWidth, outHeight);
final Bitmap rounded = round(pool, toRound);
if (toRound != null && toRound != rounded && toRound != toTransform && !pool.put(toRound)) {
toRound.recycle();
}
return rounded;
}
private Bitmap centerCrop(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
final Bitmap toReuse = pool.get(outWidth, outHeight, getSafeConfig(toTransform));
final Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) {
toReuse.recycle();
}
return transformed;
}
private Bitmap fitCenter(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return TransformationUtils.fitCenter(toTransform, pool, outWidth, outHeight);
}
private Bitmap round(@NonNull BitmapPool pool, @Nullable Bitmap toRound) {
if (toRound == null) {
return null;
}
Bitmap result = pool.get(toRound.getWidth(), toRound.getHeight(), getSafeConfig(toRound));
if (result == null) {
result = Bitmap.createBitmap(toRound.getWidth(), toRound.getHeight(), getSafeConfig(toRound));
}
Canvas canvas = new Canvas(result);
if (Config.RGB_565.equals(result.getConfig())) {
Paint cornerPaint = new Paint();
cornerPaint.setColor(colorHint);
canvas.drawRect(0, 0, radius, radius, cornerPaint);
canvas.drawRect(0, toRound.getHeight() - radius, radius, toRound.getHeight(), cornerPaint);
canvas.drawRect(toRound.getWidth() - radius, 0, toRound.getWidth(), radius, cornerPaint);
canvas.drawRect(toRound.getWidth() - radius, toRound.getHeight() - radius, toRound.getWidth(), toRound.getHeight(), cornerPaint);
}
Paint shaderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
shaderPaint.setShader(new BitmapShader(toRound, TileMode.CLAMP, TileMode.CLAMP));
canvas.drawRoundRect(new RectF(0, 0, toRound.getWidth(), toRound.getHeight()), radius, radius, shaderPaint);
return result;
}
private static Bitmap.Config getSafeConfig(Bitmap bitmap) {
return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888;
}
@Override
public String getId() {
return RoundedCorners.class.getCanonicalName();
}
}

View File

@@ -1,13 +1,16 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.util.Log;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.engine.cache.DiskCache;
import com.bumptech.glide.load.engine.cache.DiskCacheAdapter;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.module.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
import org.thoughtcrime.securesms.glide.OkHttpUrlLoader;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
@@ -18,19 +21,27 @@ import org.thoughtcrime.securesms.profiles.AvatarPhotoUriLoader.AvatarPhotoUri;
import java.io.InputStream;
public class TextSecureGlideModule implements GlideModule {
@GlideModule
public class SignalGlideModule extends AppGlideModule {
@Override
public boolean isManifestParsingEnabled() {
return false;
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setLogLevel(Log.ERROR);
// builder.setDiskCache(new NoopDiskCacheFactory());
}
@Override
public void registerComponents(Context context, Glide glide) {
glide.register(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory());
glide.register(ContactPhotoUri.class, InputStream.class, new ContactPhotoUriLoader.Factory());
glide.register(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
glide.register(AvatarPhotoUri.class, InputStream.class, new AvatarPhotoUriLoader.Factory());
public void registerComponents(Context context, Glide glide, Registry registry) {
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
registry.append(ContactPhotoUri.class, InputStream.class, new ContactPhotoUriLoader.Factory(context));
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
registry.append(AvatarPhotoUri.class, InputStream.class, new AvatarPhotoUriLoader.Factory(context));
}
public static class NoopDiskCacheFactory implements DiskCache.Factory {

View File

@@ -14,7 +14,6 @@ import android.support.v4.app.NotificationCompat.Action;
import android.support.v4.app.RemoteInput;
import android.text.SpannableStringBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R;
@@ -22,6 +21,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
@@ -225,12 +225,12 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
@SuppressWarnings("ConstantConditions")
Uri uri = slideDeck.getThumbnailSlide().getThumbnailUri();
return Glide.with(context)
.load(new DecryptableStreamUriLoader.DecryptableUri(masterSecret, uri))
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(500, 500)
.get();
return GlideApp.with(context)
.asBitmap()
.load(new DecryptableStreamUriLoader.DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit(500, 500)
.get();
} catch (InterruptedException | ExecutionException e) {
throw new AssertionError(e);
}

View File

@@ -5,32 +5,34 @@ import android.content.Context;
import android.support.annotation.NonNull;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.data.DataFetcher;
import org.thoughtcrime.securesms.database.Address;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class AvatarPhotoUriFetcher implements DataFetcher<InputStream> {
class AvatarPhotoUriFetcher implements DataFetcher<InputStream> {
private final Context context;
private final Address address;
private InputStream inputStream;
public AvatarPhotoUriFetcher(@NonNull Context context, @NonNull Address address) {
AvatarPhotoUriFetcher(@NonNull Context context, @NonNull Address address) {
this.context = context.getApplicationContext();
this.address = address;
}
@Override
public InputStream loadData(Priority priority) throws IOException {
inputStream = AvatarHelper.getInputStreamFor(context, address);
return inputStream;
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
try {
inputStream = AvatarHelper.getInputStreamFor(context, address);
callback.onDataReady(inputStream);
} catch (IOException e) {
callback.onLoadFailed(e);
}
}
@Override
@@ -40,13 +42,20 @@ public class AvatarPhotoUriFetcher implements DataFetcher<InputStream> {
} catch (IOException e) {}
}
@Override
public String getId() {
return address.serialize();
}
@Override
public void cancel() {
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}

View File

@@ -3,24 +3,48 @@ package org.thoughtcrime.securesms.profiles;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.database.Address;
import java.io.InputStream;
import java.security.MessageDigest;
public class AvatarPhotoUriLoader implements StreamModelLoader<AvatarPhotoUriLoader.AvatarPhotoUri> {
public class AvatarPhotoUriLoader implements ModelLoader<AvatarPhotoUriLoader.AvatarPhotoUri, InputStream> {
private final Context context;
private AvatarPhotoUriLoader(Context context) {
this.context = context;
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(AvatarPhotoUri avatarPhotoUri, int width, int height, Options options) {
return new LoadData<>(avatarPhotoUri, new AvatarPhotoUriFetcher(context, avatarPhotoUri.address));
}
@Override
public boolean handles(AvatarPhotoUri avatarPhotoUri) {
return true;
}
public static class Factory implements ModelLoaderFactory<AvatarPhotoUri, InputStream> {
private final Context context;
public Factory(Context context) {
this.context = context.getApplicationContext();
}
@Override
public StreamModelLoader<AvatarPhotoUri> build(Context context, GenericLoaderFactory factories) {
public ModelLoader<AvatarPhotoUri, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new AvatarPhotoUriLoader(context);
}
@@ -28,21 +52,29 @@ public class AvatarPhotoUriLoader implements StreamModelLoader<AvatarPhotoUriLoa
public void teardown() {}
}
public AvatarPhotoUriLoader(Context context) {
this.context = context;
}
@Override
public DataFetcher<InputStream> getResourceFetcher(AvatarPhotoUri model, int width, int height) {
return new AvatarPhotoUriFetcher(context, model.address);
}
public static class AvatarPhotoUri {
public static class AvatarPhotoUri implements Key {
public @NonNull Address address;
public AvatarPhotoUri(@NonNull Address address) {
this.address = address;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof AvatarPhotoUri)) return false;
return this.address.equals(((AvatarPhotoUri)other).address);
}
@Override
public int hashCode() {
return address.hashCode();
}
}
}

View File

@@ -1,4 +1,4 @@
/**
/*
* Copyright (C) 2016 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
@@ -31,10 +31,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideApp;
public class StickerSelectFragment extends Fragment implements LoaderManager.LoaderCallbacks<String[]> {
@@ -58,7 +58,7 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa
@Nullable Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.scribble_select_sticker_fragment, container, false);
this.recyclerView = (RecyclerView)view.findViewById(R.id.stickers_recycler_view);
this.recyclerView = view.findViewById(R.id.stickers_recycler_view);
return view;
}
@@ -113,10 +113,10 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa
public void onBindViewHolder(StickerViewHolder holder, int position) {
holder.fileName = stickerFiles[position];
Glide.with(context)
.load(Uri.parse("file:///android_asset/" + holder.fileName))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(holder.image);
GlideApp.with(context)
.load(Uri.parse("file:///android_asset/" + holder.fileName))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(holder.image);
}
@Override
@@ -127,7 +127,7 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa
@Override
public void onViewRecycled(StickerViewHolder holder) {
super.onViewRecycled(holder);
Glide.clear(holder.image);
GlideApp.with(context).clear(holder.image);
}
private void onStickerSelected(String fileName) {
@@ -141,22 +141,19 @@ public class StickerSelectFragment extends Fragment implements LoaderManager.Loa
StickerViewHolder(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.sticker_image);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int pos = getAdapterPosition();
if (pos >= 0) {
onStickerSelected(fileName);
}
image = itemView.findViewById(R.id.sticker_image);
itemView.setOnClickListener(view -> {
int pos = getAdapterPosition();
if (pos >= 0) {
onStickerSelected(fileName);
}
});
}
}
}
public interface StickerSelectionListener {
public void onStickerSelected(String name);
interface StickerSelectionListener {
void onStickerSelected(String name);
}

View File

@@ -30,13 +30,13 @@ import android.util.Log;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
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.GlideApp;
import org.thoughtcrime.securesms.scribbles.widget.entity.MotionEntity;
import org.thoughtcrime.securesms.scribbles.widget.entity.TextEntity;
import org.thoughtcrime.securesms.util.Util;
@@ -81,11 +81,11 @@ public class ScribbleView extends FrameLayout {
this.imageUri = uri;
this.masterSecret = masterSecret;
Glide.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter()
.into(imageView);
GlideApp.with(getContext())
.load(new DecryptableUri(masterSecret, uri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter()
.into(imageView);
}
public @NonNull ListenableFuture<Bitmap> getRenderedImage() {
@@ -110,13 +110,13 @@ public class ScribbleView extends FrameLayout {
height = 768;
}
return Glide.with(context)
.load(new DecryptableUri(masterSecret, imageUri))
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(width, height)
.get();
return GlideApp.with(context)
.asBitmap()
.load(new DecryptableUri(masterSecret, imageUri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(width, height)
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
return null;

View File

@@ -15,14 +15,9 @@ import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.bitmap.BitmapResource;
import com.bumptech.glide.load.resource.bitmap.Downsampler;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import java.io.BufferedInputStream;
@@ -30,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.microedition.khronos.egl.EGL10;
@@ -49,90 +45,80 @@ public class BitmapUtil {
public static <T> byte[] createScaledBytes(Context context, T model, MediaConstraints constraints)
throws BitmapDecodingException
{
int quality = MAX_COMPRESSION_QUALITY;
int attempts = 0;
byte[] bytes;
Bitmap scaledBitmap = Downsampler.AT_MOST.decode(getInputStreamForModel(context, model),
Glide.get(context).getBitmapPool(),
constraints.getImageMaxWidth(context),
constraints.getImageMaxHeight(context),
DecodeFormat.PREFER_RGB_565);
if (scaledBitmap == null) {
throw new BitmapDecodingException("Unable to decode image");
}
try {
do {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
scaledBitmap.compress(CompressFormat.JPEG, quality, baos);
bytes = baos.toByteArray();
int quality = MAX_COMPRESSION_QUALITY;
int attempts = 0;
byte[] bytes;
Log.w(TAG, "iteration with quality " + quality + " size " + (bytes.length / 1024) + "kb");
if (quality == MIN_COMPRESSION_QUALITY) break;
Bitmap scaledBitmap = GlideApp.with(context)
.asBitmap()
.load(model)
.downsample(DownsampleStrategy.AT_MOST)
.submit(constraints.getImageMaxWidth(context),
constraints.getImageMaxWidth(context))
.get();
if (scaledBitmap == null) {
throw new BitmapDecodingException("Unable to decode image");
}
int nextQuality = (int)Math.floor(quality * Math.sqrt((double)constraints.getImageMaxSize(context) / bytes.length));
if (quality - nextQuality < MIN_COMPRESSION_QUALITY_DECREASE) {
nextQuality = quality - MIN_COMPRESSION_QUALITY_DECREASE;
try {
do {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
scaledBitmap.compress(CompressFormat.JPEG, quality, baos);
bytes = baos.toByteArray();
Log.w(TAG, "iteration with quality " + quality + " size " + (bytes.length / 1024) + "kb");
if (quality == MIN_COMPRESSION_QUALITY) break;
int nextQuality = (int)Math.floor(quality * Math.sqrt((double)constraints.getImageMaxSize(context) / bytes.length));
if (quality - nextQuality < MIN_COMPRESSION_QUALITY_DECREASE) {
nextQuality = quality - MIN_COMPRESSION_QUALITY_DECREASE;
}
quality = Math.max(nextQuality, MIN_COMPRESSION_QUALITY);
}
quality = Math.max(nextQuality, MIN_COMPRESSION_QUALITY);
while (bytes.length > constraints.getImageMaxSize(context) && attempts++ < MAX_COMPRESSION_ATTEMPTS);
if (bytes.length > constraints.getImageMaxSize(context)) {
throw new BitmapDecodingException("Unable to scale image below: " + bytes.length);
}
Log.w(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)");
return bytes;
} finally {
if (scaledBitmap != null) scaledBitmap.recycle();
}
while (bytes.length > constraints.getImageMaxSize(context) && attempts++ < MAX_COMPRESSION_ATTEMPTS);
if (bytes.length > constraints.getImageMaxSize(context)) {
throw new BitmapDecodingException("Unable to scale image below: " + bytes.length);
}
Log.w(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)");
return bytes;
} finally {
if (scaledBitmap != null) scaledBitmap.recycle();
} catch (InterruptedException | ExecutionException e) {
throw new BitmapDecodingException(e);
}
}
public static <T> Bitmap createScaledBitmap(Context context, T model, int maxWidth, int maxHeight)
throws BitmapDecodingException
{
final Pair<Integer, Integer> dimensions = getDimensions(getInputStreamForModel(context, model));
final Pair<Integer, Integer> clamped = clampDimensions(dimensions.first, dimensions.second,
maxWidth, maxHeight);
return createScaledBitmapInto(context, model, clamped.first, clamped.second);
}
private static <T> InputStream getInputStreamForModel(Context context, T model)
throws BitmapDecodingException
{
try {
return Glide.buildStreamModelLoader(model, context)
.getResourceFetcher(model, -1, -1)
.loadData(Priority.NORMAL);
} catch (Exception e) {
return GlideApp.with(context)
.asBitmap()
.load(model)
.downsample(DownsampleStrategy.AT_MOST)
.submit(maxWidth, maxHeight)
.get();
} catch (InterruptedException | ExecutionException e) {
throw new BitmapDecodingException(e);
}
}
private static <T> Bitmap createScaledBitmapInto(Context context, T model, int width, int height)
throws BitmapDecodingException
{
final Bitmap rough = Downsampler.AT_LEAST.decode(getInputStreamForModel(context, model),
Glide.get(context).getBitmapPool(),
width, height,
DecodeFormat.PREFER_RGB_565);
final Resource<Bitmap> resource = BitmapResource.obtain(rough, Glide.get(context).getBitmapPool());
final Resource<Bitmap> result = new FitCenter(context).transform(resource, width, height);
if (result == null) {
throw new BitmapDecodingException("unable to transform Bitmap");
}
return result.get();
}
public static <T> Bitmap createScaledBitmap(Context context, T model, float scale)
throws BitmapDecodingException
{
Pair<Integer, Integer> dimens = getDimensions(getInputStreamForModel(context, model));
return createScaledBitmapInto(context, model,
(int)(dimens.first * scale), (int)(dimens.second * scale));
try {
return GlideApp.with(context)
.asBitmap()
.load(model)
.sizeMultiplier(scale)
.submit()
.get();
} catch (InterruptedException | ExecutionException e) {
throw new BitmapDecodingException(e);
}
}
private static BitmapFactory.Options getImageDimensions(InputStream inputStream)
@@ -253,27 +239,6 @@ public class BitmapUtil {
return output;
}
private static Pair<Integer, Integer> clampDimensions(int inWidth, int inHeight, int maxWidth, int maxHeight) {
if (inWidth > maxWidth || inHeight > maxHeight) {
final float aspectWidth, aspectHeight;
if (inWidth == 0 || inHeight == 0) {
aspectWidth = maxWidth;
aspectHeight = maxHeight;
} else if (inWidth >= inHeight) {
aspectWidth = maxWidth;
aspectHeight = (aspectWidth / inWidth) * inHeight;
} else {
aspectHeight = maxHeight;
aspectWidth = (aspectHeight / inHeight) * inWidth;
}
return new Pair<>(Math.round(aspectWidth), Math.round(aspectHeight));
} else {
return new Pair<>(inWidth, inHeight);
}
}
public static Bitmap createFromDrawable(final Drawable drawable, final int width, final int height) {
final AtomicBoolean created = new AtomicBoolean(false);
final Bitmap[] result = new Bitmap[1];

View File

@@ -11,8 +11,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -20,6 +18,7 @@ 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;
@@ -67,12 +66,12 @@ public class MediaUtil {
{
try {
int maxSize = context.getResources().getDimensionPixelSize(R.dimen.media_bubble_height);
return Glide.with(context)
.load(new DecryptableUri(masterSecret, uri))
.asBitmap()
.centerCrop()
.into(maxSize, maxSize)
.get();
return GlideApp.with(context)
.asBitmap()
.load(new DecryptableUri(masterSecret, uri))
.centerCrop()
.into(maxSize, maxSize)
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
throw new BitmapDecodingException(e);