mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-15 07:28:30 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4316fefe61 | ||
|
|
14b750d202 | ||
|
|
7b0a0eccd0 | ||
|
|
884c875c84 | ||
|
|
83f15e231b | ||
|
|
677b2a7494 | ||
|
|
7016f75886 | ||
|
|
76c6af6537 | ||
|
|
81e5e851f7 | ||
|
|
2ae890ef57 | ||
|
|
5fadc88646 |
@@ -11,7 +11,7 @@ RUN dpkg --add-architecture i386 && \
|
||||
|
||||
ENV ANDROID_SDK_FILENAME android-sdk_r24.4.1-linux.tgz
|
||||
ENV ANDROID_SDK_URL https://dl.google.com/android/${ANDROID_SDK_FILENAME}
|
||||
ENV ANDROID_API_LEVELS android-25
|
||||
ENV ANDROID_API_LEVELS android-26
|
||||
ENV ANDROID_BUILD_TOOLS_VERSION 25.0.2
|
||||
ENV ANDROID_HOME /usr/local/android-sdk-linux
|
||||
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
|
||||
|
||||
22
build.gradle
22
build.gradle
@@ -63,7 +63,7 @@ dependencies {
|
||||
|
||||
compile 'org.whispersystems:jobmanager:1.0.2'
|
||||
compile 'org.whispersystems:libpastelog:1.0.7'
|
||||
compile 'org.whispersystems:signal-service-android:2.6.10'
|
||||
compile 'org.whispersystems:signal-service-android:2.6.11'
|
||||
compile 'org.whispersystems:webrtc-android:M59-S1'
|
||||
|
||||
compile "me.leolin:ShortcutBadger:1.1.16"
|
||||
@@ -71,8 +71,8 @@ dependencies {
|
||||
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
|
||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||
compile 'com.github.chrisbanes:PhotoView:2.1.3'
|
||||
compile 'com.github.bumptech.glide:glide:4.2.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.2.0'
|
||||
compile 'com.github.bumptech.glide:glide:4.3.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.3.0'
|
||||
compile 'com.makeramen:roundedimageview:2.1.0'
|
||||
compile 'com.pnikosis:materialish-progress:1.5'
|
||||
compile 'org.greenrobot:eventbus:3.0.0'
|
||||
@@ -147,14 +147,14 @@ dependencyVerification {
|
||||
'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718',
|
||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
|
||||
'org.whispersystems:signal-service-android:784b748c15cb3d2824b8fa2b0f9d8fd8b990b39e7a50259bac27bdcad845ce20',
|
||||
'org.whispersystems:signal-service-android:89f8630cc1737c3d52178dc46926f0755d75fed3ac9b94d067c0a42e4e3169c9',
|
||||
'org.whispersystems:webrtc-android:de647643afbbea45a26a4f24db75aa10bc8de45426e8eb0d9d563cc10af4f582',
|
||||
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
|
||||
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
|
||||
'com.github.chrisbanes:PhotoView:ed06775308da260e1fd86d1d3288988fcd3d80db24ce0d7c9fcfedc39e622292',
|
||||
'com.github.bumptech.glide:glide:555350c4b9d163f1d3772a64a92119086073ed88340eb284391b1acc1bb5dd6c',
|
||||
'com.github.bumptech.glide:glide:cf770a66bdb42d90663672a3e44b8e4f4fb060073294af5ebd323c5db415b22f',
|
||||
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
|
||||
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
|
||||
'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c',
|
||||
@@ -185,10 +185,10 @@ dependencyVerification {
|
||||
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
|
||||
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
|
||||
'org.whispersystems:signal-protocol-android:5b8acded7f2a40178eb90ab8e8cbfec89d170d91b3ff5e78487d1098df6185a1',
|
||||
'org.whispersystems:signal-service-java:308d9e61b753760d0f3828eb3181db58469e75c763bdce5a8335df6c4af47695',
|
||||
'com.github.bumptech.glide:gifdecoder:217da4520c568a93aea9c7ce3b3cac2c61fabed5113b07ae38698054f6d2d8b6',
|
||||
'com.github.bumptech.glide:disklrucache:795c13245498c0cd806c3af71ee57b3f179cbd1609440a3021c211c364ef74d3',
|
||||
'com.github.bumptech.glide:annotations:057927a236f3229e72cfbac8bed0e9fb398473daf7d933390f59ea4cb79c137b',
|
||||
'org.whispersystems:signal-service-java:ef89da56b915490bb907d848eae79efdf1218e985763e7dd2e8047c7ccb03c0c',
|
||||
'com.github.bumptech.glide:gifdecoder:fe793861d4d4619b5041d3bd68186000b6151581292053e88c96a5d0b60e5337',
|
||||
'com.github.bumptech.glide:disklrucache:b5cf8f76b423a6c86edbe82380958adbe6a2f1d5afbd6add23a9c8ad141eb406',
|
||||
'com.github.bumptech.glide:annotations:10a910f62ee27de5f0e44a72acb7fe31ed1e45b3ffac82fb3a8ebada150765f1',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541',
|
||||
@@ -225,8 +225,8 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionCode 304
|
||||
versionName "4.11.5"
|
||||
versionCode 306
|
||||
versionName "4.12.1"
|
||||
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 22
|
||||
|
||||
@@ -856,6 +856,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleDisplayQuickContact() {
|
||||
if (recipient.getAddress().isGroup()) return false;
|
||||
|
||||
if (recipient.getContactUri() != null) {
|
||||
ContactsContract.QuickContact.showQuickContact(ConversationActivity.this, titleView, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null);
|
||||
} else {
|
||||
handleAddToContacts();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleAddAttachment() {
|
||||
if (this.isMmsEnabled || isSecureText) {
|
||||
if (attachmentTypeSelector == null) {
|
||||
@@ -1209,6 +1221,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
});
|
||||
|
||||
titleView.setOnClickListener(v -> handleConversationSettings());
|
||||
titleView.setOnLongClickListener(v -> handleDisplayQuickContact());
|
||||
titleView.setOnBackClickedListener(view -> super.onBackPressed());
|
||||
unblockButton.setOnClickListener(v -> handleUnblock());
|
||||
makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms());
|
||||
|
||||
@@ -207,7 +207,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
private void setStatusIcons(ThreadRecord thread) {
|
||||
if (!thread.isOutgoing() || thread.isOutgoingCall()) {
|
||||
if (!thread.isOutgoing() || thread.isOutgoingCall() || thread.isVerificationStatusChange()) {
|
||||
deliveryStatusIndicator.setNone();
|
||||
alertView.setNone();
|
||||
} else if (thread.isFailed()) {
|
||||
|
||||
@@ -84,6 +84,12 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
this.avatar.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnLongClickListener(@Nullable OnLongClickListener listener) {
|
||||
this.content.setOnLongClickListener(listener);
|
||||
this.avatar.setOnLongClickListener(listener);
|
||||
}
|
||||
|
||||
public void setOnBackClickedListener(@Nullable OnClickListener listener) {
|
||||
this.back.setOnClickListener(listener);
|
||||
}
|
||||
@@ -107,7 +113,7 @@ public class ConversationTitleView extends RelativeLayout {
|
||||
this.subtitle.setText(Stream.of(recipient.getParticipants())
|
||||
.filter(r -> !r.getAddress().serialize().equals(localNumber))
|
||||
.map(Recipient::toShortString)
|
||||
.collect(Collectors.joining(",")));
|
||||
.collect(Collectors.joining(", ")));
|
||||
|
||||
this.subtitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
|
||||
import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.IntentUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -73,6 +75,9 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
|
||||
private static final int REQUEST_CODE_AVATAR = 1;
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
@Inject SignalServiceAccountManager accountManager;
|
||||
|
||||
private InputAwareLayout container;
|
||||
@@ -91,6 +96,9 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
dynamicTheme.onCreate(this);
|
||||
dynamicLanguage.onCreate(this);
|
||||
|
||||
setContentView(R.layout.profile_create_activity);
|
||||
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||
@@ -104,6 +112,13 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
||||
ApplicationContext.getInstance(this).injectDependencies(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (container.isInputOpen()) container.hideCurrentInput(name);
|
||||
|
||||
@@ -146,6 +146,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.transition(withCrossFade())
|
||||
.centerCrop()
|
||||
.into(image);
|
||||
}
|
||||
|
||||
@@ -175,6 +176,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
RequestBuilder builder = glideRequests.load(new DecryptableUri(masterSecret, slide.getThumbnailUri()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transform(new RoundedCorners(radius))
|
||||
.centerCrop()
|
||||
.transition(withCrossFade());
|
||||
|
||||
if (slide.isInProgress()) return builder;
|
||||
|
||||
@@ -144,6 +144,10 @@ public abstract class DisplayRecord {
|
||||
return SmsDatabase.Types.isMissedCall(type);
|
||||
}
|
||||
|
||||
public boolean isVerificationStatusChange() {
|
||||
return SmsDatabase.Types.isIdentityDefault(type) || SmsDatabase.Types.isIdentityVerified(type);
|
||||
}
|
||||
|
||||
public int getDeliveryStatus() {
|
||||
return deliveryStatus;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,18 @@ public class GiphyImage {
|
||||
return images.downsized.url;
|
||||
}
|
||||
|
||||
public long getGifSize() {
|
||||
return images.downsized.size;
|
||||
}
|
||||
|
||||
public String getGifMmsUrl() {
|
||||
return images.fixed_height_downsampled.url;
|
||||
}
|
||||
|
||||
public long getMmsGifSize() {
|
||||
return images.fixed_height_downsampled.size;
|
||||
}
|
||||
|
||||
public float getGifAspectRatio() {
|
||||
return (float)images.downsized.width / (float)images.downsized.height;
|
||||
}
|
||||
@@ -24,6 +32,10 @@ public class GiphyImage {
|
||||
return images.downsized_still.url;
|
||||
}
|
||||
|
||||
public long getStillSize() {
|
||||
return images.downsized_still.size;
|
||||
}
|
||||
|
||||
public static class ImageTypes {
|
||||
@JsonProperty
|
||||
private ImageData fixed_height;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.thoughtcrime.securesms.giph.model;
|
||||
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.load.Key;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class GiphyPaddedUrl implements Key {
|
||||
|
||||
private final String target;
|
||||
private final long size;
|
||||
|
||||
public GiphyPaddedUrl(@NonNull String target, long size) {
|
||||
this.target = target;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(MessageDigest messageDigest) {
|
||||
messageDigest.update(target.getBytes());
|
||||
messageDigest.update(Conversions.longToByteArray(size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof GiphyPaddedUrl)) return false;
|
||||
|
||||
GiphyPaddedUrl that = (GiphyPaddedUrl)other;
|
||||
|
||||
return this.target.equals(that.target) && this.size == that.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return target.hashCode() ^ (int)size;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,12 +24,15 @@ 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.giph.model.GiphyPaddedUrl;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
@@ -69,7 +72,7 @@ class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHolder> {
|
||||
Log.w(TAG, e);
|
||||
|
||||
synchronized (this) {
|
||||
if (image.getGifUrl().equals(model)) {
|
||||
if (new GiphyPaddedUrl(image.getGifUrl(), image.getGifSize()).equals(model)) {
|
||||
this.modelReady = true;
|
||||
notifyAll();
|
||||
}
|
||||
@@ -81,7 +84,7 @@ class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHolder> {
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||
synchronized (this) {
|
||||
if (image.getGifUrl().equals(model)) {
|
||||
if (new GiphyPaddedUrl(image.getGifUrl(), image.getGifSize()).equals(model)) {
|
||||
this.modelReady = true;
|
||||
notifyAll();
|
||||
}
|
||||
@@ -99,7 +102,8 @@ class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHolder> {
|
||||
}
|
||||
|
||||
return Glide.with(context)
|
||||
.load(forMms ? image.getGifMmsUrl() : image.getGifUrl())
|
||||
.load(forMms ? new GiphyPaddedUrl(image.getGifMmsUrl(), image.getMmsGifSize()) :
|
||||
new GiphyPaddedUrl(image.getGifUrl(), image.getGifSize()))
|
||||
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.get();
|
||||
}
|
||||
@@ -144,18 +148,19 @@ class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHolder> {
|
||||
holder.gifProgress.setVisibility(View.GONE);
|
||||
|
||||
RequestBuilder<Drawable> thumbnailRequest = GlideApp.with(context)
|
||||
.load(image.getStillUrl())
|
||||
.load(new GiphyPaddedUrl(image.getStillUrl(), image.getStillSize()))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL);
|
||||
|
||||
if (Util.isLowMemory(context)) {
|
||||
glideRequests.load(image.getStillUrl())
|
||||
glideRequests.load(new GiphyPaddedUrl(image.getStillUrl(), image.getStillSize()))
|
||||
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.listener(holder)
|
||||
.into(holder.thumbnail);
|
||||
|
||||
holder.setModelReady();
|
||||
} else {
|
||||
glideRequests.load(image.getGifUrl())
|
||||
glideRequests.load(new GiphyPaddedUrl(image.getGifUrl(), image.getGifSize()))
|
||||
.thumbnail(thumbnailRequest)
|
||||
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
|
||||
285
src/org/thoughtcrime/securesms/glide/GiphyPaddedUrlFetcher.java
Normal file
285
src/org/thoughtcrime/securesms/glide/GiphyPaddedUrlFetcher.java
Normal file
@@ -0,0 +1,285 @@
|
||||
package org.thoughtcrime.securesms.glide;
|
||||
|
||||
|
||||
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.util.ContentLengthInputStream;
|
||||
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyPaddedUrl;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
class GiphyPaddedUrlFetcher implements DataFetcher<InputStream> {
|
||||
|
||||
private static final String TAG = GiphyPaddedUrlFetcher.class.getSimpleName();
|
||||
|
||||
private static final long MB = 1024 * 1024;
|
||||
private static final long KB = 1024;
|
||||
|
||||
private final OkHttpClient client;
|
||||
private final GiphyPaddedUrl url;
|
||||
|
||||
private List<ResponseBody> bodies;
|
||||
private List<InputStream> rangeStreams;
|
||||
private InputStream stream;
|
||||
|
||||
GiphyPaddedUrlFetcher(@NonNull OkHttpClient client,
|
||||
@NonNull GiphyPaddedUrl url)
|
||||
{
|
||||
this.client = client;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
|
||||
bodies = new LinkedList<>();
|
||||
rangeStreams = new LinkedList<>();
|
||||
stream = null;
|
||||
|
||||
try {
|
||||
List<ByteRange> requestPattern = getRequestPattern(url.getSize());
|
||||
|
||||
for (ByteRange range : requestPattern) {
|
||||
Request request = new Request.Builder()
|
||||
.addHeader("Range", "bytes=" + range.start + "-" + range.end)
|
||||
.addHeader("Accept-Encoding", "identity")
|
||||
.url(url.getTarget())
|
||||
.get()
|
||||
.build();
|
||||
|
||||
Response response = client.newCall(request).execute();
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Bad response: " + response.code() + " - " + response.message());
|
||||
}
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
|
||||
if (responseBody == null) throw new IOException("Response body was null");
|
||||
else bodies.add(responseBody);
|
||||
|
||||
rangeStreams.add(new SkippingInputStream(ContentLengthInputStream.obtain(responseBody.byteStream(), responseBody.contentLength()), range.ignoreFirst));
|
||||
}
|
||||
|
||||
stream = new InputStreamList(rangeStreams);
|
||||
callback.onDataReady(stream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
callback.onLoadFailed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (rangeStreams != null) {
|
||||
for (InputStream rangeStream : rangeStreams) {
|
||||
try {
|
||||
if (rangeStream != null) rangeStream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (bodies != null) {
|
||||
for (ResponseBody body : bodies) {
|
||||
if (body != null) body.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Class<InputStream> getDataClass() {
|
||||
return InputStream.class;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return DataSource.REMOTE;
|
||||
}
|
||||
|
||||
private List<ByteRange> getRequestPattern(long size) throws IOException {
|
||||
if (size > MB) return getRequestPattern(size, MB);
|
||||
else if (size > 500 * KB) return getRequestPattern(size, 500 * KB);
|
||||
else if (size > 100 * KB) return getRequestPattern(size, 100 * KB);
|
||||
else if (size > 50 * KB) return getRequestPattern(size, 50 * KB);
|
||||
else if (size > KB) return getRequestPattern(size, KB);
|
||||
|
||||
throw new IOException("Unsupported size: " + size);
|
||||
}
|
||||
|
||||
private List<ByteRange> getRequestPattern(long size, long increment) {
|
||||
List<ByteRange> results = new LinkedList<>();
|
||||
|
||||
long offset = 0;
|
||||
|
||||
while (size - offset > increment) {
|
||||
results.add(new ByteRange(offset, offset + increment - 1, 0));
|
||||
offset += increment;
|
||||
}
|
||||
|
||||
if (size - offset > 0) {
|
||||
results.add(new ByteRange(size - increment, size-1, increment - (size - offset)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static class ByteRange {
|
||||
private final long start;
|
||||
private final long end;
|
||||
private final long ignoreFirst;
|
||||
|
||||
private ByteRange(long start, long end, long ignoreFirst) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.ignoreFirst = ignoreFirst;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SkippingInputStream extends FilterInputStream {
|
||||
|
||||
private long skip;
|
||||
|
||||
SkippingInputStream(InputStream in, long skip) {
|
||||
super(in);
|
||||
this.skip = skip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (skip != 0) {
|
||||
skipFully(skip);
|
||||
skip = 0;
|
||||
}
|
||||
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer) throws IOException {
|
||||
if (skip != 0) {
|
||||
skipFully(skip);
|
||||
skip = 0;
|
||||
}
|
||||
|
||||
return super.read(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int offset, int length) throws IOException {
|
||||
if (skip != 0) {
|
||||
skipFully(skip);
|
||||
skip = 0;
|
||||
}
|
||||
|
||||
return super.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return Util.toIntExact(super.available() - skip);
|
||||
}
|
||||
|
||||
private void skipFully(long amount) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
while (amount > 0) {
|
||||
int read = super.read(buffer, 0, Math.min(buffer.length, Util.toIntExact(amount)));
|
||||
|
||||
if (read != -1) amount -= read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InputStreamList extends InputStream {
|
||||
|
||||
private final List<InputStream> inputStreams;
|
||||
|
||||
private int currentStreamIndex = 0;
|
||||
|
||||
InputStreamList(List<InputStream> inputStreams) {
|
||||
this.inputStreams = inputStreams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
while (currentStreamIndex < inputStreams.size()) {
|
||||
int result = inputStreams.get(currentStreamIndex).read();
|
||||
|
||||
if (result == -1) currentStreamIndex++;
|
||||
else return result;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int offset, int length) throws IOException {
|
||||
while (currentStreamIndex < inputStreams.size()) {
|
||||
int result = inputStreams.get(currentStreamIndex).read(buffer, offset, length);
|
||||
|
||||
if (result == -1) currentStreamIndex++;
|
||||
else return result;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (InputStream stream : inputStreams) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() {
|
||||
int total = 0;
|
||||
|
||||
for (int i=currentStreamIndex;i<inputStreams.size();i++) {
|
||||
try {
|
||||
int available = inputStreams.get(i).available();
|
||||
|
||||
if (available != -1) total += available;
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.thoughtcrime.securesms.glide;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
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.MultiModelLoaderFactory;
|
||||
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyPaddedUrl;
|
||||
import org.thoughtcrime.securesms.giph.net.GiphyProxySelector;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class GiphyPaddedUrlLoader implements ModelLoader<GiphyPaddedUrl, InputStream> {
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
private GiphyPaddedUrlLoader(OkHttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public LoadData<InputStream> buildLoadData(GiphyPaddedUrl url, int width, int height, Options options) {
|
||||
return new LoadData<>(url, new GiphyPaddedUrlFetcher(client, url));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handles(GiphyPaddedUrl url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<GiphyPaddedUrl, InputStream> {
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
public Factory() {
|
||||
this.client = new OkHttpClient.Builder().proxySelector(new GiphyProxySelector()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelLoader<GiphyPaddedUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
|
||||
return new GiphyPaddedUrlLoader(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
@@ -165,8 +166,13 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
Log.w(TAG, "Downloading attachment with no digest...");
|
||||
}
|
||||
|
||||
return new SignalServiceAttachmentPointer(id, null, key, relay, Optional.fromNullable(attachment.getDigest()), Optional.fromNullable(attachment.getFileName()), attachment.isVoiceNote());
|
||||
} catch (InvalidMessageException | IOException e) {
|
||||
return new SignalServiceAttachmentPointer(id, null, key, relay,
|
||||
Optional.of(Util.toIntExact(attachment.getSize())),
|
||||
Optional.absent(),
|
||||
Optional.fromNullable(attachment.getDigest()),
|
||||
Optional.fromNullable(attachment.getFileName()),
|
||||
attachment.isVoiceNote());
|
||||
} catch (InvalidMessageException | IOException | ArithmeticException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new InvalidPartException(e);
|
||||
}
|
||||
|
||||
@@ -81,9 +81,9 @@ public class AvatarDownloadJob extends MasterSecretJob implements InjectableType
|
||||
attachment = File.createTempFile("avatar", "tmp", context.getCacheDir());
|
||||
attachment.deleteOnExit();
|
||||
|
||||
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, relay, digest, fileName, false);
|
||||
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, relay, Optional.of(0), Optional.absent(), digest, fileName, false);
|
||||
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
|
||||
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key), 500, 500);
|
||||
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
|
||||
|
||||
database.updateAvatar(encodeId, avatar);
|
||||
inputStream.close();
|
||||
|
||||
@@ -608,7 +608,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
.getExpiringMessageManager()
|
||||
.scheduleDeletion(messageId, true,
|
||||
message.getExpirationStartTimestamp(),
|
||||
message.getMessage().getExpiresInSeconds());
|
||||
message.getMessage().getExpiresInSeconds() * 1000);
|
||||
}
|
||||
|
||||
return threadId;
|
||||
|
||||
@@ -19,19 +19,25 @@ class AttachmentStreamLocalUriFetcher implements DataFetcher<InputStream> {
|
||||
|
||||
private static final String TAG = AttachmentStreamLocalUriFetcher.class.getSimpleName();
|
||||
|
||||
private File attachment;
|
||||
private byte[] key;
|
||||
private final File attachment;
|
||||
private final byte[] key;
|
||||
private final Optional<byte[]> digest;
|
||||
private final long plaintextLength;
|
||||
|
||||
private InputStream is;
|
||||
|
||||
AttachmentStreamLocalUriFetcher(File attachment, byte[] key) {
|
||||
this.attachment = attachment;
|
||||
this.key = key;
|
||||
AttachmentStreamLocalUriFetcher(File attachment, long plaintextLength, byte[] key, Optional<byte[]> digest) {
|
||||
this.attachment = attachment;
|
||||
this.plaintextLength = plaintextLength;
|
||||
this.digest = digest;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
|
||||
try {
|
||||
is = new AttachmentCipherInputStream(attachment, key, Optional.absent());
|
||||
if (!digest.isPresent()) throw new InvalidMessageException("No attachment digest!");
|
||||
is = AttachmentCipherInputStream.createFor(attachment, plaintextLength, key, digest.get());
|
||||
callback.onDataReady(is);
|
||||
} catch (IOException | InvalidMessageException e) {
|
||||
callback.onLoadFailed(e);
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@@ -20,7 +21,7 @@ public class AttachmentStreamUriLoader implements ModelLoader<AttachmentModel, I
|
||||
@Nullable
|
||||
@Override
|
||||
public LoadData<InputStream> buildLoadData(AttachmentModel attachmentModel, int width, int height, Options options) {
|
||||
return new LoadData<>(attachmentModel, new AttachmentStreamLocalUriFetcher(attachmentModel.attachment, attachmentModel.key));
|
||||
return new LoadData<>(attachmentModel, new AttachmentStreamLocalUriFetcher(attachmentModel.attachment, attachmentModel.plaintextLength, attachmentModel.key, attachmentModel.digest));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,12 +43,18 @@ public class AttachmentStreamUriLoader implements ModelLoader<AttachmentModel, I
|
||||
}
|
||||
|
||||
public static class AttachmentModel implements Key {
|
||||
public @NonNull File attachment;
|
||||
public @NonNull byte[] key;
|
||||
public @NonNull File attachment;
|
||||
public @NonNull byte[] key;
|
||||
public @NonNull Optional<byte[]> digest;
|
||||
public long plaintextLength;
|
||||
|
||||
public AttachmentModel(@NonNull File attachment, @NonNull byte[] key) {
|
||||
this.attachment = attachment;
|
||||
this.key = key;
|
||||
public AttachmentModel(@NonNull File attachment, @NonNull byte[] key,
|
||||
long plaintextLength, @NonNull Optional<byte[]> digest)
|
||||
{
|
||||
this.attachment = attachment;
|
||||
this.key = key;
|
||||
this.digest = digest;
|
||||
this.plaintextLength = plaintextLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,9 @@ import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
|
||||
import org.thoughtcrime.securesms.giph.model.GiphyPaddedUrl;
|
||||
import org.thoughtcrime.securesms.glide.ContactPhotoLoader;
|
||||
import org.thoughtcrime.securesms.glide.GiphyPaddedUrlLoader;
|
||||
import org.thoughtcrime.securesms.glide.OkHttpUrlLoader;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
@@ -39,6 +41,7 @@ public class SignalGlideModule extends AppGlideModule {
|
||||
registry.append(ContactPhoto.class, InputStream.class, new ContactPhotoLoader.Factory(context));
|
||||
registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context));
|
||||
registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory());
|
||||
registry.append(GiphyPaddedUrl.class, InputStream.class, new GiphyPaddedUrlLoader.Factory());
|
||||
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user