mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
18 Commits
attachment
...
alpha-1.3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d145a082f5 | ||
|
|
4fd012c31a | ||
|
|
95d06770bf | ||
|
|
428247b7b2 | ||
|
|
a921361a56 | ||
|
|
fe7dfa721e | ||
|
|
92eb06a9e9 | ||
|
|
5beed1a748 | ||
|
|
774047d856 | ||
|
|
fc28e7aa88 | ||
|
|
78459499b2 | ||
|
|
c2973608d7 | ||
|
|
be1c33cb42 | ||
|
|
c955466bda | ||
|
|
593a0c4632 | ||
|
|
ed20b2d8d6 | ||
|
|
9ab9e02f8a | ||
|
|
3f70ae3c8c |
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
# QR codes
|
# QR codes
|
||||||
-keep class com.google.zxing.Result
|
-keep class com.google.zxing.Result
|
||||||
|
-keepclassmembers enum * {
|
||||||
|
public static **[] values();
|
||||||
|
public static ** valueOf(java.lang.String);
|
||||||
|
}
|
||||||
|
|
||||||
# RSS libraries
|
# RSS libraries
|
||||||
-keep,includedescriptorclasses class com.rometools.rome.feed.synd.impl.** { *; }
|
-keep,includedescriptorclasses class com.rometools.rome.feed.synd.impl.** { *; }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.BrambleAndroidModule;
|
|||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
import org.briarproject.bramble.account.BriarAccountModule;
|
import org.briarproject.bramble.account.BriarAccountModule;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -13,6 +14,7 @@ import dagger.Component;
|
|||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
AppModule.class,
|
AppModule.class,
|
||||||
|
AttachmentModule.class,
|
||||||
BriarCoreModule.class,
|
BriarCoreModule.class,
|
||||||
BrambleAndroidModule.class,
|
BrambleAndroidModule.class,
|
||||||
BriarAccountModule.class,
|
BriarAccountModule.class,
|
||||||
|
|||||||
@@ -47,15 +47,17 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
);
|
);
|
||||||
private final MessageId msgId = new MessageId(getRandomId());
|
private final MessageId msgId = new MessageId(getRandomId());
|
||||||
|
|
||||||
|
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||||
private final AttachmentRetriever retriever =
|
private final AttachmentRetriever retriever =
|
||||||
new AttachmentRetriever(null, dimensions);
|
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
|
||||||
|
new ImageSizeCalculator(imageHelper));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmallJpegImage() throws Exception {
|
public void testSmallJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(smallKitten);
|
InputStream is = getUrlInputStream(smallKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(160, item.getWidth());
|
assertEquals(160, item.getWidth());
|
||||||
assertEquals(240, item.getHeight());
|
assertEquals(240, item.getHeight());
|
||||||
@@ -70,8 +72,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testBigJpegImage() throws Exception {
|
public void testBigJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(originalKitten);
|
InputStream is = getUrlInputStream(originalKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(1728, item.getWidth());
|
assertEquals(1728, item.getWidth());
|
||||||
assertEquals(2592, item.getHeight());
|
assertEquals(2592, item.getHeight());
|
||||||
@@ -86,8 +88,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallPngImage() throws Exception {
|
public void testSmallPngImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||||
InputStream is = getUrlInputStream(pngKitten);
|
InputStream is = getUrlInputStream(pngKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(737, item.getWidth());
|
assertEquals(737, item.getWidth());
|
||||||
assertEquals(510, item.getHeight());
|
assertEquals(510, item.getHeight());
|
||||||
@@ -102,8 +104,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testUberGif() throws Exception {
|
public void testUberGif() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(uberGif);
|
InputStream is = getUrlInputStream(uberGif);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -117,8 +119,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testLottaPixels() throws Exception {
|
public void testLottaPixels() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(lottaPixel);
|
InputStream is = getUrlInputStream(lottaPixel);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(64250, item.getWidth());
|
assertEquals(64250, item.getWidth());
|
||||||
assertEquals(64250, item.getHeight());
|
assertEquals(64250, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -132,8 +134,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testImageIoCrash() throws Exception {
|
public void testImageIoCrash() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(imageIoCrash);
|
InputStream is = getUrlInputStream(imageIoCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1184, item.getWidth());
|
assertEquals(1184, item.getWidth());
|
||||||
assertEquals(448, item.getHeight());
|
assertEquals(448, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -147,8 +149,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testGimpCrash() throws Exception {
|
public void testGimpCrash() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(gimpCrash);
|
InputStream is = getUrlInputStream(gimpCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -162,8 +164,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testOptiPngAfl() throws Exception {
|
public void testOptiPngAfl() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(optiPngAfl);
|
InputStream is = getUrlInputStream(optiPngAfl);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(32, item.getWidth());
|
assertEquals(32, item.getWidth());
|
||||||
assertEquals(32, item.getHeight());
|
assertEquals(32, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -177,8 +179,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testLibrawError() throws Exception {
|
public void testLibrawError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(librawError);
|
InputStream is = getUrlInputStream(librawError);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +188,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated.gif");
|
InputStream is = getAssetInputStream("animated.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(65535, item.getWidth());
|
assertEquals(65535, item.getWidth());
|
||||||
assertEquals(65535, item.getHeight());
|
assertEquals(65535, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -201,8 +203,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated2.gif");
|
InputStream is = getAssetInputStream("animated2.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(10000, item.getWidth());
|
assertEquals(10000, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -216,8 +218,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testSmallGifLargeDimensions() throws Exception {
|
public void testSmallGifLargeDimensions() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("error_large.gif");
|
InputStream is = getAssetInputStream("error_large.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(16384, item.getWidth());
|
assertEquals(16384, item.getWidth());
|
||||||
assertEquals(16384, item.getHeight());
|
assertEquals(16384, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -231,8 +233,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testHighError() throws Exception {
|
public void testHighError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_high.jpg");
|
InputStream is = getAssetInputStream("error_high.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||||
@@ -246,8 +248,8 @@ public class AttachmentRetrieverIntegrationTest {
|
|||||||
public void testWideError() throws Exception {
|
public void testWideError() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(h, is);
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||||
assertEquals(1920, item.getWidth());
|
assertEquals(1920, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||||
@@ -68,7 +69,8 @@ import dagger.Component;
|
|||||||
BriarCoreModule.class,
|
BriarCoreModule.class,
|
||||||
BrambleAndroidModule.class,
|
BrambleAndroidModule.class,
|
||||||
BriarAccountModule.class,
|
BriarAccountModule.class,
|
||||||
AppModule.class
|
AppModule.class,
|
||||||
|
AttachmentModule.class
|
||||||
})
|
})
|
||||||
public interface AndroidComponent
|
public interface AndroidComponent
|
||||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.account;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.TextInputEditText;
|
import android.support.design.widget.TextInputEditText;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
|
import android.text.Editable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -10,7 +11,6 @@ import android.widget.Button;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
|
|||||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
|
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.setError;
|
import static org.briarproject.briar.android.util.UiUtils.setError;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public class AuthorNameFragment extends SetupFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
|
public void onTextChanged(CharSequence authorName, int i, int i1, int i2) {
|
||||||
int authorNameLength = StringUtils.toUtf8(authorName.toString()).length;
|
int authorNameLength = toUtf8(authorName.toString().trim()).length;
|
||||||
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
|
boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH;
|
||||||
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
||||||
boolean enabled = authorNameLength > 0 && !error;
|
boolean enabled = authorNameLength > 0 && !error;
|
||||||
@@ -89,8 +90,11 @@ public class AuthorNameFragment extends SetupFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
setupController.setAuthorName(authorNameInput.getText().toString());
|
Editable text = authorNameInput.getText();
|
||||||
setupController.showPasswordFragment();
|
if (text != null) {
|
||||||
|
setupController.setAuthorName(text.toString().trim());
|
||||||
|
setupController.showPasswordFragment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory.Options;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@@ -12,11 +14,17 @@ import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.jsoup.UnsupportedMimeTypeException;
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.graphics.Bitmap.CompressFormat.JPEG;
|
||||||
|
import static android.graphics.BitmapFactory.decodeStream;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
@@ -24,6 +32,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentCreationTask {
|
class AttachmentCreationTask {
|
||||||
@@ -31,8 +40,11 @@ class AttachmentCreationTask {
|
|||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(AttachmentCreationTask.class.getName());
|
getLogger(AttachmentCreationTask.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final Collection<Uri> uris;
|
private final Collection<Uri> uris;
|
||||||
private final boolean needsSize;
|
private final boolean needsSize;
|
||||||
@@ -43,24 +55,26 @@ class AttachmentCreationTask {
|
|||||||
|
|
||||||
AttachmentCreationTask(MessagingManager messagingManager,
|
AttachmentCreationTask(MessagingManager messagingManager,
|
||||||
ContentResolver contentResolver,
|
ContentResolver contentResolver,
|
||||||
AttachmentCreator attachmentCreator, GroupId groupId,
|
AttachmentCreator attachmentCreator,
|
||||||
Collection<Uri> uris, boolean needsSize) {
|
ImageSizeCalculator imageSizeCalculator,
|
||||||
|
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.contentResolver = contentResolver;
|
this.contentResolver = contentResolver;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.uris = uris;
|
this.uris = uris;
|
||||||
this.needsSize = needsSize;
|
this.needsSize = needsSize;
|
||||||
this.attachmentCreator = attachmentCreator;
|
this.attachmentCreator = attachmentCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
void cancel() {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
attachmentCreator = null;
|
attachmentCreator = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
public void storeAttachments() {
|
void storeAttachments() {
|
||||||
for (Uri uri: uris) processUri(uri);
|
for (Uri uri : uris) processUri(uri);
|
||||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||||
if (!canceled && attachmentCreator != null)
|
if (!canceled && attachmentCreator != null)
|
||||||
attachmentCreator.onAttachmentCreationFinished();
|
attachmentCreator.onAttachmentCreationFinished();
|
||||||
@@ -98,6 +112,8 @@ class AttachmentCreationTask {
|
|||||||
}
|
}
|
||||||
InputStream is = contentResolver.openInputStream(uri);
|
InputStream is = contentResolver.openInputStream(uri);
|
||||||
if (is == null) throw new IOException();
|
if (is == null) throw new IOException();
|
||||||
|
is = compressImage(is, contentType);
|
||||||
|
contentType = "image/jpeg";
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
AttachmentHeader h = messagingManager
|
AttachmentHeader h = messagingManager
|
||||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||||
@@ -113,4 +129,48 @@ class AttachmentCreationTask {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream compressImage(InputStream is, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = createBitmap(is, contentType);
|
||||||
|
for (int quality = 100; quality >= 0; quality -= 10) {
|
||||||
|
if (!bitmap.compress(JPEG, quality, out))
|
||||||
|
throw new IOException();
|
||||||
|
if (out.size() <= MAX_IMAGE_SIZE) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Compressed image to "
|
||||||
|
+ out.size() + " bytes, quality " + quality);
|
||||||
|
}
|
||||||
|
return new ByteArrayInputStream(out.toByteArray());
|
||||||
|
}
|
||||||
|
out.reset();
|
||||||
|
}
|
||||||
|
throw new IOException();
|
||||||
|
} finally {
|
||||||
|
tryToClose(is, LOG, WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createBitmap(InputStream is, String contentType)
|
||||||
|
throws IOException {
|
||||||
|
is = new BufferedInputStream(is);
|
||||||
|
Size size = imageSizeCalculator.getSize(is, contentType);
|
||||||
|
if (size.error) throw new IOException();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Original image size: " + size.width + "x" + size.height);
|
||||||
|
int dimension = Math.max(size.width, size.height);
|
||||||
|
int inSampleSize = 1;
|
||||||
|
while (dimension > MAX_ATTACHMENT_DIMENSION) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
dimension /= 2;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Scaling attachment by factor of " + inSampleSize);
|
||||||
|
Options options = new Options();
|
||||||
|
options.inSampleSize = inSampleSize;
|
||||||
|
Bitmap bitmap = decodeStream(is, null, options);
|
||||||
|
if (bitmap == null) throw new IOException();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +1,24 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.arch.lifecycle.LiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
|
||||||
import org.jsoup.UnsupportedMimeTypeException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentCreator {
|
public interface AttachmentCreator {
|
||||||
|
|
||||||
private static Logger LOG = getLogger(AttachmentCreator.class.getName());
|
|
||||||
|
|
||||||
private final Application app;
|
|
||||||
@IoExecutor
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final MessagingManager messagingManager;
|
|
||||||
private final AttachmentRetriever retriever;
|
|
||||||
|
|
||||||
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
|
||||||
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
|
||||||
new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
private final MutableLiveData<AttachmentResult> result =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
@Nullable
|
|
||||||
private AttachmentCreationTask task;
|
|
||||||
|
|
||||||
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
|
|
||||||
MessagingManager messagingManager, AttachmentRetriever retriever) {
|
|
||||||
this.app = app;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.messagingManager = messagingManager;
|
|
||||||
this.retriever = retriever;
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public LiveData<AttachmentResult> storeAttachments(
|
LiveData<AttachmentResult> storeAttachments(LiveData<GroupId> groupId,
|
||||||
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
Collection<Uri> newUris);
|
||||||
if (task != null || !uris.isEmpty())
|
|
||||||
throw new IllegalStateException();
|
|
||||||
uris.addAll(newUris);
|
|
||||||
observeForeverOnce(groupId, id -> {
|
|
||||||
if (id == null) throw new IllegalStateException();
|
|
||||||
boolean needsSize = uris.size() == 1;
|
|
||||||
task = new AttachmentCreationTask(messagingManager,
|
|
||||||
app.getContentResolver(), this, id, uris, needsSize);
|
|
||||||
ioExecutor.execute(() -> task.storeAttachments());
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be only called after configuration changes.
|
* This should be only called after configuration changes.
|
||||||
@@ -84,68 +26,10 @@ public class AttachmentCreator {
|
|||||||
* They are already being created and returned by the existing LiveData.
|
* They are already being created and returned by the existing LiveData.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
LiveData<AttachmentResult> getLiveAttachments();
|
||||||
if (task == null || uris.isEmpty())
|
|
||||||
throw new IllegalStateException();
|
|
||||||
// A task is already running. It will update the result LiveData.
|
|
||||||
// So nothing more to do here.
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
|
||||||
boolean needsSize) {
|
|
||||||
// get and cache AttachmentItem for ImagePreview
|
|
||||||
try {
|
|
||||||
Attachment a = retriever.getMessageAttachment(h);
|
|
||||||
AttachmentItem item = retriever.getAttachmentItem(h, a, needsSize);
|
|
||||||
if (item.hasError()) throw new IOException();
|
|
||||||
AttachmentItemResult itemResult =
|
|
||||||
new AttachmentItemResult(uri, item);
|
|
||||||
itemResults.add(itemResult);
|
|
||||||
result.postValue(getResult(false));
|
|
||||||
} catch (IOException | DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
onAttachmentError(uri, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentError(Uri uri, Throwable t) {
|
|
||||||
// get error message
|
|
||||||
String errorMsg;
|
|
||||||
if (t instanceof UnsupportedMimeTypeException) {
|
|
||||||
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
|
||||||
errorMsg = app.getString(
|
|
||||||
R.string.image_attach_error_invalid_mime_type, mimeType);
|
|
||||||
} else if (t instanceof FileTooBigException) {
|
|
||||||
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
|
||||||
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
|
||||||
} else {
|
|
||||||
errorMsg = null; // generic error
|
|
||||||
}
|
|
||||||
AttachmentItemResult itemResult =
|
|
||||||
new AttachmentItemResult(uri, errorMsg);
|
|
||||||
itemResults.add(itemResult);
|
|
||||||
result.postValue(getResult(false));
|
|
||||||
// expect to receive a cancel from the UI
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
|
||||||
void onAttachmentCreationFinished() {
|
|
||||||
result.postValue(getResult(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
|
||||||
headers.add(itemResult.getItem().getHeader());
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the attachments as sent and adds the items to the cache for display
|
* Marks the attachments as sent and adds the items to the cache for display
|
||||||
@@ -153,66 +37,24 @@ public class AttachmentCreator {
|
|||||||
* @param id The MessageId of the sent message.
|
* @param id The MessageId of the sent message.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onAttachmentsSent(MessageId id) {
|
void onAttachmentsSent(MessageId id);
|
||||||
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() == null) throw new IllegalStateException();
|
|
||||||
items.add(itemResult.getItem());
|
|
||||||
}
|
|
||||||
retriever.cachePut(id, items);
|
|
||||||
resetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Needs to be called when created attachments will not be sent anymore.
|
* Needs to be called when created attachments will not be sent anymore.
|
||||||
*/
|
*/
|
||||||
@UiThread
|
@UiThread
|
||||||
public void cancel() {
|
void cancel();
|
||||||
if (task == null) throw new AssertionError();
|
|
||||||
task.cancel();
|
|
||||||
deleteUnsentAttachments();
|
|
||||||
resetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void resetState() {
|
void deleteUnsentAttachments();
|
||||||
task = null;
|
|
||||||
uris.clear();
|
|
||||||
itemResults.clear();
|
|
||||||
result.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
@IoExecutor
|
||||||
public void deleteUnsentAttachments() {
|
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||||
// Make a copy for the IoExecutor as we clear the itemResults soon
|
boolean needsSize);
|
||||||
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
|
||||||
for (AttachmentItemResult itemResult : itemResults) {
|
|
||||||
// check if we are trying to send attachment items with errors
|
|
||||||
if (itemResult.getItem() != null)
|
|
||||||
headers.add(itemResult.getItem().getHeader());
|
|
||||||
}
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
for (AttachmentHeader header : headers) {
|
|
||||||
try {
|
|
||||||
messagingManager.removeAttachment(header);
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AttachmentResult getResult(boolean finished) {
|
@IoExecutor
|
||||||
// Make a copy of the list,
|
void onAttachmentError(Uri uri, Throwable t);
|
||||||
// because our copy will continue to change in the background.
|
|
||||||
// (As it's a CopyOnWriteArrayList,
|
|
||||||
// the code that receives the result can safely do simple things
|
|
||||||
// like iterating over the list,
|
|
||||||
// but anything that involves calling more than one list method
|
|
||||||
// is still unsafe.)
|
|
||||||
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
|
||||||
return new AttachmentResult(items, finished);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
@IoExecutor
|
||||||
|
void onAttachmentCreationFinished();
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentCreatorImpl implements AttachmentCreator {
|
||||||
|
|
||||||
|
private static Logger LOG =
|
||||||
|
getLogger(AttachmentCreatorImpl.class.getName());
|
||||||
|
|
||||||
|
private final Application app;
|
||||||
|
@IoExecutor
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final AttachmentRetriever retriever;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
|
|
||||||
|
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
|
||||||
|
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
|
||||||
|
new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AttachmentCreationTask task;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile MutableLiveData<AttachmentResult> result;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
|
||||||
|
MessagingManager messagingManager, AttachmentRetriever retriever,
|
||||||
|
ImageSizeCalculator imageSizeCalculator) {
|
||||||
|
this.app = app;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.retriever = retriever;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> storeAttachments(
|
||||||
|
LiveData<GroupId> groupId, Collection<Uri> newUris) {
|
||||||
|
if (task != null || result != null || !uris.isEmpty())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
|
||||||
|
this.result = result;
|
||||||
|
uris.addAll(newUris);
|
||||||
|
observeForeverOnce(groupId, id -> {
|
||||||
|
if (id == null) throw new IllegalStateException();
|
||||||
|
boolean needsSize = uris.size() == 1;
|
||||||
|
task = new AttachmentCreationTask(messagingManager,
|
||||||
|
app.getContentResolver(), this, imageSizeCalculator, id,
|
||||||
|
uris, needsSize);
|
||||||
|
ioExecutor.execute(() -> task.storeAttachments());
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (task == null || result == null || uris.isEmpty())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
// A task is already running. It will update the result LiveData.
|
||||||
|
// So nothing more to do here.
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||||
|
boolean needsSize) {
|
||||||
|
// get and cache AttachmentItem for ImagePreview
|
||||||
|
try {
|
||||||
|
Attachment a = retriever.getMessageAttachment(h);
|
||||||
|
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
|
||||||
|
if (item.hasError()) throw new IOException();
|
||||||
|
AttachmentItemResult itemResult =
|
||||||
|
new AttachmentItemResult(uri, item);
|
||||||
|
itemResults.add(itemResult);
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(false));
|
||||||
|
} catch (IOException | DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
onAttachmentError(uri, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentError(Uri uri, Throwable t) {
|
||||||
|
// get error message
|
||||||
|
String errorMsg;
|
||||||
|
if (t instanceof UnsupportedMimeTypeException) {
|
||||||
|
String mimeType = ((UnsupportedMimeTypeException) t).getMimeType();
|
||||||
|
errorMsg = app.getString(
|
||||||
|
R.string.image_attach_error_invalid_mime_type, mimeType);
|
||||||
|
} else if (t instanceof FileTooBigException) {
|
||||||
|
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
||||||
|
errorMsg = app.getString(R.string.image_attach_error_too_big, mb);
|
||||||
|
} else {
|
||||||
|
errorMsg = null; // generic error
|
||||||
|
}
|
||||||
|
AttachmentItemResult itemResult =
|
||||||
|
new AttachmentItemResult(uri, errorMsg);
|
||||||
|
itemResults.add(itemResult);
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(false));
|
||||||
|
// expect to receive a cancel from the UI
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@IoExecutor
|
||||||
|
public void onAttachmentCreationFinished() {
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) result.postValue(getResult(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||||
|
headers.add(itemResult.getItem().getHeader());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onAttachmentsSent(MessageId id) {
|
||||||
|
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() == null) throw new IllegalStateException();
|
||||||
|
items.add(itemResult.getItem());
|
||||||
|
}
|
||||||
|
retriever.cachePut(id, items);
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void cancel() {
|
||||||
|
if (task == null) throw new AssertionError();
|
||||||
|
task.cancel();
|
||||||
|
deleteUnsentAttachments();
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void resetState() {
|
||||||
|
task = null;
|
||||||
|
uris.clear();
|
||||||
|
itemResults.clear();
|
||||||
|
MutableLiveData<AttachmentResult> result = this.result;
|
||||||
|
if (result != null) {
|
||||||
|
result.setValue(null);
|
||||||
|
this.result = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void deleteUnsentAttachments() {
|
||||||
|
// Make a copy for the IoExecutor as we clear the itemResults soon
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
// check if we are trying to send attachment items with errors
|
||||||
|
if (itemResult.getItem() != null)
|
||||||
|
headers.add(itemResult.getItem().getHeader());
|
||||||
|
}
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
for (AttachmentHeader header : headers) {
|
||||||
|
try {
|
||||||
|
messagingManager.removeAttachment(header);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttachmentResult getResult(boolean finished) {
|
||||||
|
// Make a copy of the list,
|
||||||
|
// because our copy will continue to change in the background.
|
||||||
|
// (As it's a CopyOnWriteArrayList,
|
||||||
|
// the code that receives the result can safely do simple things
|
||||||
|
// like iterating over the list,
|
||||||
|
// but anything that involves calling more than one list method
|
||||||
|
// is still unsafe.)
|
||||||
|
Collection<AttachmentItemResult> items = new ArrayList<>(itemResults);
|
||||||
|
return new AttachmentResult(items, finished);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentDimensions {
|
class AttachmentDimensions {
|
||||||
|
|
||||||
final int defaultSize;
|
final int defaultSize;
|
||||||
final int minWidth, maxWidth;
|
final int minWidth, maxWidth;
|
||||||
@@ -26,7 +26,7 @@ public class AttachmentDimensions {
|
|||||||
this.maxHeight = maxHeight;
|
this.maxHeight = maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||||
int defaultSize =
|
int defaultSize =
|
||||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
int minWidth = res.getDimensionPixelSize(
|
int minWidth = res.getDimensionPixelSize(
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public class AttachmentItem implements Parcelable {
|
|||||||
header = new AttachmentHeader(messageId, mimeType);
|
header = new AttachmentHeader(messageId, mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentHeader getHeader() {
|
public AttachmentHeader getHeader() {
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import android.arch.lifecycle.LiveData;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
@NotNullByDefault
|
||||||
public interface AttachmentManager {
|
public interface AttachmentManager {
|
||||||
|
|
||||||
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class AttachmentModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
|
||||||
|
return imageHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
|
||||||
|
return new ImageSizeCalculator(imageHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
AttachmentDimensions provideAttachmentDimensions(Application app) {
|
||||||
|
return getAttachmentDimensions(app.getResources());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
AttachmentRetriever provideAttachmentRetriever(
|
||||||
|
AttachmentRetrieverImpl attachmentRetriever) {
|
||||||
|
return attachmentRetriever;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
AttachmentCreator provideAttachmentCreator(
|
||||||
|
AttachmentCreatorImpl attachmentCreator) {
|
||||||
|
return attachmentCreator;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,276 +1,29 @@
|
|||||||
package org.briarproject.briar.android.attachment;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.BitmapFactory.Options;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.VisibleForTesting;
|
|
||||||
import android.support.media.ExifInterface;
|
|
||||||
import android.webkit.MimeTypeMap;
|
|
||||||
|
|
||||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Pair;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
|
||||||
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
|
||||||
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
|
||||||
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
|
||||||
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static java.util.logging.Logger.getLogger;
|
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentRetriever {
|
public interface AttachmentRetriever {
|
||||||
|
|
||||||
private static final Logger LOG =
|
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
|
||||||
getLogger(AttachmentRetriever.class.getName());
|
|
||||||
private static final int READ_LIMIT = 1024 * 8192;
|
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
|
||||||
private final ImageHelper imageHelper;
|
|
||||||
private final int defaultSize;
|
|
||||||
private final int minWidth, maxWidth;
|
|
||||||
private final int minHeight, maxHeight;
|
|
||||||
|
|
||||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
|
||||||
new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
AttachmentRetriever(MessagingManager messagingManager,
|
|
||||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
|
||||||
this.messagingManager = messagingManager;
|
|
||||||
this.imageHelper = imageHelper;
|
|
||||||
defaultSize = dimensions.defaultSize;
|
|
||||||
minWidth = dimensions.minWidth;
|
|
||||||
maxWidth = dimensions.maxWidth;
|
|
||||||
minHeight = dimensions.minHeight;
|
|
||||||
maxHeight = dimensions.maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AttachmentRetriever(MessagingManager messagingManager,
|
|
||||||
AttachmentDimensions dimensions) {
|
|
||||||
this(messagingManager, dimensions, new ImageHelper() {
|
|
||||||
@Override
|
|
||||||
public DecodeResult decodeStream(InputStream is) {
|
|
||||||
Options options = new Options();
|
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeStream(is, null, options);
|
|
||||||
String mimeType = options.outMimeType;
|
|
||||||
if (mimeType == null) mimeType = "";
|
|
||||||
return new DecodeResult(options.outWidth, options.outHeight,
|
|
||||||
mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getExtensionFromMimeType(String mimeType) {
|
|
||||||
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
|
||||||
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cachePut(MessageId messageId, List<AttachmentItem> attachments) {
|
|
||||||
attachmentCache.put(messageId, attachments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
List<AttachmentItem> cacheGet(MessageId messageId);
|
||||||
return attachmentCache.get(messageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
Attachment getMessageAttachment(AttachmentHeader h) throws DbException;
|
||||||
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
|
||||||
List<AttachmentHeader> headers) throws DbException {
|
|
||||||
long start = now();
|
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
|
||||||
new ArrayList<>(headers.size());
|
|
||||||
for (AttachmentHeader h : headers) {
|
|
||||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
|
||||||
attachments.add(new Pair<>(h, a));
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading attachments", start);
|
|
||||||
return attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DatabaseExecutor
|
|
||||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
|
||||||
return messagingManager.getAttachment(h.getMessageId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
|
||||||
* <p>
|
|
||||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
|
||||||
*/
|
|
||||||
public List<AttachmentItem> getAttachmentItems(
|
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
|
||||||
boolean needsSize = attachments.size() == 1;
|
|
||||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
|
||||||
for (Pair<AttachmentHeader, Attachment> a : attachments) {
|
|
||||||
AttachmentItem item =
|
|
||||||
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||||
* {@link InputStream} which will be closed when this method returns.
|
* {@link InputStream} which will be closed when this method returns.
|
||||||
*/
|
*/
|
||||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
AttachmentItem getAttachmentItem(Attachment a, boolean needsSize);
|
||||||
boolean needsSize) {
|
|
||||||
if (!needsSize) {
|
|
||||||
String extension =
|
|
||||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
|
||||||
boolean hasError = false;
|
|
||||||
if (extension == null) {
|
|
||||||
extension = "";
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Size size = new Size();
|
|
||||||
InputStream is = new MarkEnforcingInputStream(
|
|
||||||
new BufferedInputStream(a.getStream()));
|
|
||||||
is.mark(READ_LIMIT);
|
|
||||||
try {
|
|
||||||
// use exif to get size
|
|
||||||
if (h.getContentType().equals("image/jpeg")) {
|
|
||||||
size = getSizeFromExif(is);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// use BitmapFactory to get size
|
|
||||||
if (size.error) {
|
|
||||||
is.reset();
|
|
||||||
// need to mark again to re-add read limit
|
|
||||||
is.mark(READ_LIMIT);
|
|
||||||
size = getSizeFromBitmap(is);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
} finally {
|
|
||||||
tryToClose(is, LOG, WARNING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate thumbnail size
|
|
||||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
|
||||||
if (!size.error) {
|
|
||||||
thumbnailSize =
|
|
||||||
getThumbnailSize(size.width, size.height, size.mimeType);
|
|
||||||
}
|
|
||||||
// get file extension
|
|
||||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
|
||||||
boolean hasError = extension == null || size.error;
|
|
||||||
if (!h.getContentType().equals(size.mimeType)) {
|
|
||||||
if (LOG.isLoggable(WARNING)) {
|
|
||||||
LOG.warning("Header has different mime type (" +
|
|
||||||
h.getContentType() + ") than image (" + size.mimeType +
|
|
||||||
").");
|
|
||||||
}
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
if (extension == null) extension = "";
|
|
||||||
return new AttachmentItem(h, size.width, size.height, extension,
|
|
||||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
|
||||||
*/
|
|
||||||
private Size getSizeFromExif(InputStream is) throws IOException {
|
|
||||||
ExifInterface exif = new ExifInterface(is);
|
|
||||||
// these can return 0 independent of default value
|
|
||||||
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
|
||||||
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
|
||||||
if (width == 0 || height == 0) return new Size();
|
|
||||||
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
|
||||||
if (orientation == ORIENTATION_ROTATE_90 ||
|
|
||||||
orientation == ORIENTATION_ROTATE_270 ||
|
|
||||||
orientation == ORIENTATION_TRANSVERSE ||
|
|
||||||
orientation == ORIENTATION_TRANSPOSE) {
|
|
||||||
//noinspection SuspiciousNameCombination
|
|
||||||
return new Size(height, width, "image/jpeg");
|
|
||||||
}
|
|
||||||
return new Size(width, height, "image/jpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the size of any image {@link InputStream}.
|
|
||||||
*/
|
|
||||||
private Size getSizeFromBitmap(InputStream is) {
|
|
||||||
DecodeResult result = imageHelper.decodeStream(is);
|
|
||||||
if (result.width < 1 || result.height < 1) return new Size();
|
|
||||||
return new Size(result.width, result.height, result.mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
|
||||||
float widthPercentage = maxWidth / (float) width;
|
|
||||||
float heightPercentage = maxHeight / (float) height;
|
|
||||||
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
|
||||||
if (scaleFactor > 1) scaleFactor = 1f;
|
|
||||||
int thumbnailWidth = (int) (width * scaleFactor);
|
|
||||||
int thumbnailHeight = (int) (height * scaleFactor);
|
|
||||||
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
|
||||||
widthPercentage = minWidth / (float) width;
|
|
||||||
heightPercentage = minHeight / (float) height;
|
|
||||||
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
|
||||||
thumbnailWidth = (int) (width * scaleFactor);
|
|
||||||
thumbnailHeight = (int) (height * scaleFactor);
|
|
||||||
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
|
||||||
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
|
||||||
}
|
|
||||||
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Size {
|
|
||||||
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
private final String mimeType;
|
|
||||||
private final boolean error;
|
|
||||||
|
|
||||||
private Size(int width, int height, String mimeType) {
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
this.error = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Size() {
|
|
||||||
this.width = 0;
|
|
||||||
this.height = 0;
|
|
||||||
this.mimeType = "";
|
|
||||||
this.error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(AttachmentRetrieverImpl.class.getName());
|
||||||
|
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final ImageHelper imageHelper;
|
||||||
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
|
private final int defaultSize;
|
||||||
|
private final int minWidth, maxWidth;
|
||||||
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
|
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
AttachmentRetrieverImpl(MessagingManager messagingManager,
|
||||||
|
AttachmentDimensions dimensions, ImageHelper imageHelper,
|
||||||
|
ImageSizeCalculator imageSizeCalculator) {
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.imageHelper = imageHelper;
|
||||||
|
this.imageSizeCalculator = imageSizeCalculator;
|
||||||
|
defaultSize = dimensions.defaultSize;
|
||||||
|
minWidth = dimensions.minWidth;
|
||||||
|
maxWidth = dimensions.maxWidth;
|
||||||
|
minHeight = dimensions.minHeight;
|
||||||
|
maxHeight = dimensions.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cachePut(MessageId messageId,
|
||||||
|
List<AttachmentItem> attachments) {
|
||||||
|
attachmentCache.put(messageId, attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||||
|
return attachmentCache.get(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||||
|
throws DbException {
|
||||||
|
return messagingManager.getAttachment(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
|
||||||
|
AttachmentHeader h = a.getHeader();
|
||||||
|
if (!needsSize) {
|
||||||
|
String extension =
|
||||||
|
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||||
|
boolean hasError = false;
|
||||||
|
if (extension == null) {
|
||||||
|
extension = "";
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream is = new BufferedInputStream(a.getStream());
|
||||||
|
Size size = imageSizeCalculator.getSize(is, h.getContentType());
|
||||||
|
|
||||||
|
// calculate thumbnail size
|
||||||
|
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||||
|
if (!size.error) {
|
||||||
|
thumbnailSize =
|
||||||
|
getThumbnailSize(size.width, size.height, size.mimeType);
|
||||||
|
}
|
||||||
|
// get file extension
|
||||||
|
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||||
|
boolean hasError = extension == null || size.error;
|
||||||
|
if (!h.getContentType().equals(size.mimeType)) {
|
||||||
|
if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Header has different mime type (" +
|
||||||
|
h.getContentType() + ") than image (" + size.mimeType +
|
||||||
|
").");
|
||||||
|
}
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
if (extension == null) extension = "";
|
||||||
|
return new AttachmentItem(h, size.width, size.height, extension,
|
||||||
|
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||||
|
float widthPercentage = maxWidth / (float) width;
|
||||||
|
float heightPercentage = maxHeight / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
if (scaleFactor > 1) scaleFactor = 1f;
|
||||||
|
int thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
int thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
|
||||||
|
widthPercentage = minWidth / (float) width;
|
||||||
|
heightPercentage = minHeight / (float) height;
|
||||||
|
scaleFactor = Math.max(widthPercentage, heightPercentage);
|
||||||
|
thumbnailWidth = (int) (width * scaleFactor);
|
||||||
|
thumbnailHeight = (int) (height * scaleFactor);
|
||||||
|
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
|
||||||
|
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
|
||||||
|
}
|
||||||
|
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
interface ImageHelper {
|
public interface ImageHelper {
|
||||||
|
|
||||||
DecodeResult decodeStream(InputStream is);
|
DecodeResult decodeStream(InputStream is);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageHelperImpl implements ImageHelper {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ImageHelperImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DecodeResult decodeStream(InputStream is) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeStream(is, null, options);
|
||||||
|
String mimeType = options.outMimeType;
|
||||||
|
if (mimeType == null) mimeType = "";
|
||||||
|
return new DecodeResult(options.outWidth, options.outHeight,
|
||||||
|
mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getExtensionFromMimeType(String mimeType) {
|
||||||
|
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||||
|
return mimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.support.media.ExifInterface;
|
||||||
|
|
||||||
|
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
|
||||||
|
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
|
||||||
|
import static android.support.media.ExifInterface.TAG_ORIENTATION;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImageSizeCalculator {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ImageSizeCalculator.class.getName());
|
||||||
|
|
||||||
|
private static final int READ_LIMIT = 1024 * 8192;
|
||||||
|
|
||||||
|
private final ImageHelper imageHelper;
|
||||||
|
|
||||||
|
ImageSizeCalculator(ImageHelper imageHelper) {
|
||||||
|
this.imageHelper = imageHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size getSize(InputStream is, String contentType) {
|
||||||
|
Size size = new Size();
|
||||||
|
is = new MarkEnforcingInputStream(is);
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
if (contentType.equals("image/jpeg")) {
|
||||||
|
try {
|
||||||
|
// use exif to get size
|
||||||
|
size = getSizeFromExif(is);
|
||||||
|
is.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (size.error) {
|
||||||
|
// need to mark again to re-add read limit
|
||||||
|
is.mark(READ_LIMIT);
|
||||||
|
try {
|
||||||
|
// use BitmapFactory to get size
|
||||||
|
size = getSizeFromBitmap(is);
|
||||||
|
is.reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromExif(InputStream is) throws IOException {
|
||||||
|
ExifInterface exif = new ExifInterface(is);
|
||||||
|
// these can return 0 independent of default value
|
||||||
|
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
|
||||||
|
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
|
||||||
|
if (width == 0 || height == 0) return new Size();
|
||||||
|
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
|
||||||
|
if (orientation == ORIENTATION_ROTATE_90 ||
|
||||||
|
orientation == ORIENTATION_ROTATE_270 ||
|
||||||
|
orientation == ORIENTATION_TRANSVERSE ||
|
||||||
|
orientation == ORIENTATION_TRANSPOSE) {
|
||||||
|
//noinspection SuspiciousNameCombination
|
||||||
|
return new Size(height, width, "image/jpeg");
|
||||||
|
}
|
||||||
|
return new Size(width, height, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the size of any image {@link InputStream}.
|
||||||
|
*/
|
||||||
|
private Size getSizeFromBitmap(InputStream is) {
|
||||||
|
DecodeResult result = imageHelper.decodeStream(is);
|
||||||
|
if (result.width < 1 || result.height < 1) return new Size();
|
||||||
|
return new Size(result.width, result.height, result.mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
class Size {
|
||||||
|
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
final String mimeType;
|
||||||
|
final boolean error;
|
||||||
|
|
||||||
|
Size(int width, int height, String mimeType) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size() {
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.mimeType = "";
|
||||||
|
this.error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,19 +82,20 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String getNicknameOrNull() {
|
private String getNicknameOrNull() {
|
||||||
Editable name = contactNameInput.getText();
|
Editable text = contactNameInput.getText();
|
||||||
if (name == null || name.toString().trim().length() == 0) {
|
if (text == null || text.toString().trim().length() == 0) {
|
||||||
contactNameLayout.setError(getString(R.string.nickname_missing));
|
contactNameLayout.setError(getString(R.string.nickname_missing));
|
||||||
contactNameInput.requestFocus();
|
contactNameInput.requestFocus();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (utf8IsTooLong(name.toString(), MAX_AUTHOR_NAME_LENGTH)) {
|
String name = text.toString().trim();
|
||||||
|
if (utf8IsTooLong(name, MAX_AUTHOR_NAME_LENGTH)) {
|
||||||
contactNameLayout.setError(getString(R.string.name_too_long));
|
contactNameLayout.setError(getString(R.string.name_too_long));
|
||||||
contactNameInput.requestFocus();
|
contactNameInput.requestFocus();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
contactNameLayout.setError(null);
|
contactNameLayout.setError(null);
|
||||||
return name.toString().trim();
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAddButtonClicked() {
|
private void onAddButtonClicked() {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onSetButtonClicked() {
|
private void onSetButtonClicked() {
|
||||||
String alias = aliasEditText.getText().toString();
|
String alias = aliasEditText.getText().toString().trim();
|
||||||
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
||||||
aliasEditLayout.setError(getString(R.string.name_too_long));
|
aliasEditLayout.setError(getString(R.string.name_too_long));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchMessageException;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
@@ -81,6 +82,7 @@ import org.briarproject.briar.api.messaging.Attachment;
|
|||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -107,10 +109,12 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
|
|||||||
import static android.view.Gravity.RIGHT;
|
import static android.view.Gravity.RIGHT;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.sort;
|
import static java.util.Collections.sort;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -138,7 +142,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ConversationActivity.class.getName());
|
getLogger(ConversationActivity.class.getName());
|
||||||
|
|
||||||
private static final int TRANSITION_DURATION_MS = 500;
|
private static final int TRANSITION_DURATION_MS = 500;
|
||||||
private static final int ONBOARDING_DELAY_MS = 250;
|
private static final int ONBOARDING_DELAY_MS = 250;
|
||||||
@@ -171,6 +175,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
volatile GroupInvitationManager groupInvitationManager;
|
volatile GroupInvitationManager groupInvitationManager;
|
||||||
|
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||||
|
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
private final Observer<String> contactNameObserver = name -> {
|
private final Observer<String> contactNameObserver = name -> {
|
||||||
requireNonNull(name);
|
requireNonNull(name);
|
||||||
loadMessages();
|
loadMessages();
|
||||||
@@ -434,29 +440,40 @@ public class ConversationActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
|
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
|
||||||
throws DbException {
|
try {
|
||||||
MessageId id = h.getId();
|
MessageId id = h.getId();
|
||||||
// If the message has text, load it
|
// If the message has text, load it
|
||||||
if (h.hasText()) {
|
if (h.hasText()) {
|
||||||
String text = textCache.get(id);
|
String text = textCache.get(id);
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
LOG.info("Eagerly loading text for latest message");
|
LOG.info("Eagerly loading text for latest message");
|
||||||
text = messagingManager.getMessageText(id);
|
text = messagingManager.getMessageText(id);
|
||||||
textCache.put(id, requireNonNull(text));
|
textCache.put(id, requireNonNull(text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// If the message has a single image, load its size - for multiple
|
||||||
// If the message has a single image, load its size - for multiple
|
// images we use a grid so the size is fixed
|
||||||
// images we use a grid so the size is fixed
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
if (h.getAttachmentHeaders().size() == 1) {
|
if (headers.size() == 1) {
|
||||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
LOG.info("Eagerly loading image size for latest message");
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
items = attachmentRetriever.getAttachmentItems(
|
AttachmentHeader header = headers.get(0);
|
||||||
attachmentRetriever.getMessageAttachments(
|
try {
|
||||||
h.getAttachmentHeaders()));
|
Attachment a = attachmentRetriever
|
||||||
attachmentRetriever.cachePut(id, items);
|
.getMessageAttachment(header);
|
||||||
|
AttachmentItem item =
|
||||||
|
attachmentRetriever.getAttachmentItem(a, true);
|
||||||
|
attachmentRetriever.cachePut(id, singletonList(item));
|
||||||
|
} catch (NoSuchMessageException e) {
|
||||||
|
LOG.info("Attachment not received yet");
|
||||||
|
missingAttachments.put(header.getMessageId(), h);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,16 +551,30 @@ public class ConversationActivity extends BriarActivity
|
|||||||
&& adapter.isScrolledToBottom(layoutManager);
|
&& adapter.isScrolledToBottom(layoutManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMessageAttachments(MessageId messageId,
|
private void loadMessageAttachments(PrivateMessageHeader h) {
|
||||||
List<AttachmentHeader> headers) {
|
// TODO: Use placeholders for missing/invalid attachments
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
|
||||||
attachmentRetriever.getMessageAttachments(headers);
|
|
||||||
// TODO move getting the items off to IoExecutor, if size == 1
|
// TODO move getting the items off to IoExecutor, if size == 1
|
||||||
List<AttachmentItem> items =
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
attachmentRetriever.getAttachmentItems(attachments);
|
boolean needsSize = headers.size() == 1;
|
||||||
displayMessageAttachments(messageId, items);
|
List<AttachmentItem> items = new ArrayList<>(headers.size());
|
||||||
|
for (AttachmentHeader header : headers) {
|
||||||
|
try {
|
||||||
|
Attachment a = attachmentRetriever
|
||||||
|
.getMessageAttachment(header);
|
||||||
|
AttachmentItem item = attachmentRetriever
|
||||||
|
.getAttachmentItem(a, needsSize);
|
||||||
|
items.add(item);
|
||||||
|
} catch (NoSuchMessageException e) {
|
||||||
|
LOG.info("Attachment not received yet");
|
||||||
|
missingAttachments.put(header.getMessageId(), h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't cache items unless all are present and valid
|
||||||
|
attachmentRetriever.cachePut(h.getId(), items);
|
||||||
|
displayMessageAttachments(h.getId(), items);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -553,7 +584,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageAttachments(MessageId m,
|
private void displayMessageAttachments(MessageId m,
|
||||||
List<AttachmentItem> items) {
|
List<AttachmentItem> items) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
attachmentRetriever.cachePut(m, items);
|
|
||||||
Pair<Integer, ConversationMessageItem> pair =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItem(m);
|
adapter.getMessageItem(m);
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
@@ -567,6 +597,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void eventOccurred(Event e) {
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof AttachmentReceivedEvent) {
|
||||||
|
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
|
||||||
|
if (a.getContactId().equals(contactId)) {
|
||||||
|
LOG.info("Attachment received");
|
||||||
|
onAttachmentReceived(a.getMessageId());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) {
|
if (c.getContactId().equals(contactId)) {
|
||||||
@@ -617,6 +654,15 @@ public class ConversationActivity extends BriarActivity
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void onAttachmentReceived(MessageId attachmentId) {
|
||||||
|
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
|
||||||
|
if (h != null) {
|
||||||
|
LOG.info("Missing attachment received");
|
||||||
|
loadMessageAttachments(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void onNewConversationMessage(ConversationMessageHeader h) {
|
private void onNewConversationMessage(ConversationMessageHeader h) {
|
||||||
if (h instanceof ConversationRequest ||
|
if (h instanceof ConversationRequest ||
|
||||||
@@ -903,11 +949,11 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentItem> attachments =
|
||||||
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
attachmentRetriever.cacheGet(h.getId());
|
||||||
if (attachments == null) {
|
if (attachments == null) {
|
||||||
loadMessageAttachments(m, headers);
|
loadMessageAttachments(h);
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
return attachments;
|
return attachments;
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
@@ -51,7 +50,6 @@ import static java.util.logging.Logger.getLogger;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@@ -101,10 +99,13 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@IoExecutor Executor ioExecutor, TransactionManager db,
|
TransactionManager db,
|
||||||
MessagingManager messagingManager, ContactManager contactManager,
|
MessagingManager messagingManager,
|
||||||
|
ContactManager contactManager,
|
||||||
SettingsManager settingsManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory) {
|
PrivateMessageFactory privateMessageFactory,
|
||||||
|
AttachmentRetriever attachmentRetriever,
|
||||||
|
AttachmentCreator attachmentCreator) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -112,10 +113,8 @@ public class ConversationViewModel extends AndroidViewModel
|
|||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
this.attachmentRetriever = attachmentRetriever;
|
||||||
getAttachmentDimensions(application.getResources()));
|
this.attachmentCreator = attachmentCreator;
|
||||||
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
|
||||||
ioExecutor, messagingManager, attachmentRetriever);
|
|
||||||
messagingGroupId = Transformations
|
messagingGroupId = Transformations
|
||||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
|
|||||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
import org.briarproject.briar.api.introduction.IntroductionResponse;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||||
@@ -56,8 +55,7 @@ class ConversationVisitor implements
|
|||||||
if (h.getAttachmentHeaders().isEmpty()) {
|
if (h.getAttachmentHeaders().isEmpty()) {
|
||||||
attachments = emptyList();
|
attachments = emptyList();
|
||||||
} else {
|
} else {
|
||||||
attachments = attachmentCache
|
attachments = attachmentCache.getAttachmentItems(h);
|
||||||
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
|
|
||||||
}
|
}
|
||||||
if (h.isLocal()) {
|
if (h.isLocal()) {
|
||||||
item = new ConversationMessageItem(
|
item = new ConversationMessageItem(
|
||||||
@@ -295,7 +293,6 @@ class ConversationVisitor implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AttachmentCache {
|
interface AttachmentCache {
|
||||||
List<AttachmentItem> getAttachmentItems(MessageId m,
|
List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h);
|
||||||
List<AttachmentHeader> headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
@@ -135,10 +134,10 @@ public class ImageViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||||
@Nullable Runnable afterCopy) {
|
@Nullable Runnable afterCopy) {
|
||||||
MessageId messageId = attachment.getMessageId();
|
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
Attachment a = messagingManager.getAttachment(messageId);
|
Attachment a =
|
||||||
|
messagingManager.getAttachment(attachment.getHeader());
|
||||||
copyImageFromDb(a, osp, afterCopy);
|
copyImageFromDb(a, osp, afterCopy);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import com.bumptech.glide.load.data.DataFetcher;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -50,11 +50,12 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
|
|||||||
@Override
|
@Override
|
||||||
public void loadData(Priority priority,
|
public void loadData(Priority priority,
|
||||||
DataCallback<? super InputStream> callback) {
|
DataCallback<? super InputStream> callback) {
|
||||||
MessageId id = attachment.getMessageId();
|
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
if (cancel) return;
|
if (cancel) return;
|
||||||
try {
|
try {
|
||||||
inputStream = messagingManager.getAttachment(id).getStream();
|
Attachment a =
|
||||||
|
messagingManager.getAttachment(attachment.getHeader());
|
||||||
|
inputStream = a.getStream();
|
||||||
callback.onDataReady(inputStream);
|
callback.onDataReady(inputStream);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
callback.onLoadFailed(e);
|
callback.onLoadFailed(e);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:hint="@string/set_contact_alias_hint"
|
android:hint="@string/set_contact_alias_hint"
|
||||||
android:inputType="textPersonName"
|
android:inputType="text|textCapWords"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ package org.briarproject.briar.android.attachment;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.lib.legacy.ClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@@ -25,32 +24,30 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
|||||||
100, 50, 200, 75, 300
|
100, 50, 200, 75, 300
|
||||||
);
|
);
|
||||||
private final MessageId msgId = new MessageId(getRandomId());
|
private final MessageId msgId = new MessageId(getRandomId());
|
||||||
private final Attachment attachment = new Attachment(
|
|
||||||
new BufferedInputStream(
|
|
||||||
new ByteArrayInputStream(getRandomBytes(42))));
|
|
||||||
|
|
||||||
private final MessagingManager messagingManager =
|
|
||||||
context.mock(MessagingManager.class);
|
|
||||||
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
||||||
private final AttachmentRetriever controller =
|
private final ImageSizeCalculator imageSizeCalculator;
|
||||||
new AttachmentRetriever(
|
private final AttachmentRetriever retriever;
|
||||||
messagingManager,
|
|
||||||
dimensions,
|
public AttachmentRetrieverTest() {
|
||||||
imageHelper
|
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||||
);
|
MessagingManager messagingManager =
|
||||||
|
context.mock(MessagingManager.class);
|
||||||
|
imageSizeCalculator = context.mock(ImageSizeCalculator.class);
|
||||||
|
retriever = new AttachmentRetrieverImpl(messagingManager, dimensions,
|
||||||
|
imageHelper, imageSizeCalculator);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoSize() {
|
public void testNoSize() {
|
||||||
String mimeType = "image/jpeg";
|
String mimeType = "image/jpeg";
|
||||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
Attachment attachment = getAttachment(mimeType);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||||
will(returnValue("jpg"));
|
will(returnValue("jpg"));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AttachmentItem item =
|
AttachmentItem item = retriever.getAttachmentItem(attachment, false);
|
||||||
controller.getAttachmentItem(h, attachment, false);
|
|
||||||
assertEquals(mimeType, item.getMimeType());
|
assertEquals(mimeType, item.getMimeType());
|
||||||
assertEquals("jpg", item.getExtension());
|
assertEquals("jpg", item.getExtension());
|
||||||
assertFalse(item.hasError());
|
assertFalse(item.hasError());
|
||||||
@@ -59,31 +56,31 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testNoSizeWrongMimeTypeProducesError() {
|
public void testNoSizeWrongMimeTypeProducesError() {
|
||||||
String mimeType = "application/octet-stream";
|
String mimeType = "application/octet-stream";
|
||||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
Attachment attachment = getAttachment(mimeType);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AttachmentItem item =
|
AttachmentItem item = retriever.getAttachmentItem(attachment, false);
|
||||||
controller.getAttachmentItem(h, attachment, false);
|
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmallJpegImage() {
|
public void testSmallJpegImage() {
|
||||||
String mimeType = "image/jpeg";
|
String mimeType = "image/jpeg";
|
||||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
Attachment attachment = getAttachment(mimeType);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||||
will(returnValue(new DecodeResult(160, 240, mimeType)));
|
with(mimeType));
|
||||||
|
will(returnValue(new Size(160, 240, mimeType)));
|
||||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||||
will(returnValue("jpg"));
|
will(returnValue("jpg"));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(160, item.getWidth());
|
assertEquals(160, item.getWidth());
|
||||||
assertEquals(240, item.getHeight());
|
assertEquals(240, item.getHeight());
|
||||||
@@ -97,16 +94,17 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testBigJpegImage() {
|
public void testBigJpegImage() {
|
||||||
String mimeType = "image/jpeg";
|
String mimeType = "image/jpeg";
|
||||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
Attachment attachment = getAttachment(mimeType);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||||
will(returnValue(new DecodeResult(1728, 2592, mimeType)));
|
with(mimeType));
|
||||||
|
will(returnValue(new Size(1728, 2592, mimeType)));
|
||||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||||
will(returnValue("jpg"));
|
will(returnValue("jpg"));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||||
assertEquals(1728, item.getWidth());
|
assertEquals(1728, item.getWidth());
|
||||||
assertEquals(2592, item.getHeight());
|
assertEquals(2592, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -116,21 +114,24 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMalformedError() {
|
public void testMalformedError() {
|
||||||
AttachmentHeader h = getAttachmentHeader("image/jpeg");
|
String mimeType = "image/jpeg";
|
||||||
|
Attachment attachment = getAttachment(mimeType);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||||
will(returnValue(new DecodeResult(0, 0, "")));
|
with(mimeType));
|
||||||
|
will(returnValue(new Size()));
|
||||||
oneOf(imageHelper).getExtensionFromMimeType("");
|
oneOf(imageHelper).getExtensionFromMimeType("");
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AttachmentHeader getAttachmentHeader(String contentType) {
|
private Attachment getAttachment(String contentType) {
|
||||||
return new AttachmentHeader(msgId, contentType);
|
AttachmentHeader header = new AttachmentHeader(msgId, contentType);
|
||||||
|
InputStream in = new ByteArrayInputStream(getRandomBytes(42));
|
||||||
|
return new Attachment(header, in);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
package org.briarproject.briar.api.messaging;
|
package org.briarproject.briar.api.messaging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
public class Attachment {
|
public class Attachment {
|
||||||
|
|
||||||
|
private final AttachmentHeader header;
|
||||||
private final InputStream stream;
|
private final InputStream stream;
|
||||||
|
|
||||||
public Attachment(InputStream stream) {
|
public Attachment(AttachmentHeader header, InputStream stream) {
|
||||||
|
this.header = header;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AttachmentHeader getHeader() {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
public InputStream getStream() {
|
public InputStream getStream() {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.briar.api.messaging;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception that is thrown when an {@link AttachmentHeader} is used to
|
||||||
|
* load an {@link Attachment}, and the header refers to a message that is not
|
||||||
|
* an attachment, or to an attachment that does not have the expected content
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public class InvalidAttachmentException extends DbException {
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* The current minor version of the messaging client.
|
* The current minor version of the messaging client.
|
||||||
*/
|
*/
|
||||||
int MINOR_VERSION = 1;
|
int MINOR_VERSION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local private message.
|
* Stores a local private message.
|
||||||
@@ -40,7 +40,7 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
/**
|
/**
|
||||||
* Stores a local attachment message.
|
* Stores a local attachment message.
|
||||||
*
|
*
|
||||||
* @throws FileTooBigException
|
* @throws FileTooBigException If the attachment is too big
|
||||||
*/
|
*/
|
||||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||||
String contentType, InputStream is) throws DbException, IOException;
|
String contentType, InputStream is) throws DbException, IOException;
|
||||||
@@ -68,9 +68,13 @@ public interface MessagingManager extends ConversationClient {
|
|||||||
String getMessageText(MessageId m) throws DbException;
|
String getMessageText(MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the attachment with the given ID.
|
* Returns the attachment with the given message ID and content type.
|
||||||
|
*
|
||||||
|
* @throws InvalidAttachmentException If the header refers to a message
|
||||||
|
* that is not an attachment, or to an attachment that does not have the
|
||||||
|
* expected content type
|
||||||
*/
|
*/
|
||||||
Attachment getAttachment(MessageId m) throws DbException;
|
Attachment getAttachment(AttachmentHeader h) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the contact with the given {@link ContactId} does support
|
* Returns true if the contact with the given {@link ContactId} does support
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
|||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.FileTooBigException;
|
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||||
|
import org.briarproject.briar.api.messaging.InvalidAttachmentException;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
@@ -374,13 +375,20 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Attachment getAttachment(MessageId m) throws DbException {
|
public Attachment getAttachment(AttachmentHeader h) throws DbException {
|
||||||
// TODO: Support large messages
|
// TODO: Support large messages
|
||||||
|
MessageId m = h.getMessageId();
|
||||||
byte[] body = clientHelper.getMessage(m).getBody();
|
byte[] body = clientHelper.getMessage(m).getBody();
|
||||||
try {
|
try {
|
||||||
BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(m);
|
BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(m);
|
||||||
|
Long messageType = meta.getOptionalLong(MSG_KEY_MSG_TYPE);
|
||||||
|
if (messageType == null || messageType != ATTACHMENT)
|
||||||
|
throw new InvalidAttachmentException();
|
||||||
|
String contentType = meta.getString(MSG_KEY_CONTENT_TYPE);
|
||||||
|
if (!contentType.equals(h.getContentType()))
|
||||||
|
throw new InvalidAttachmentException();
|
||||||
int offset = meta.getLong(MSG_KEY_DESCRIPTOR_LENGTH).intValue();
|
int offset = meta.getLong(MSG_KEY_DESCRIPTOR_LENGTH).intValue();
|
||||||
return new Attachment(new ByteArrayInputStream(body, offset,
|
return new Attachment(h, new ByteArrayInputStream(body, offset,
|
||||||
body.length - offset));
|
body.length - offset));
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class MessagingModule {
|
|||||||
conversationManager.registerConversationClient(messagingManager);
|
conversationManager.registerConversationClient(messagingManager);
|
||||||
// Advertise the current or previous minor version depending on the
|
// Advertise the current or previous minor version depending on the
|
||||||
// feature flag
|
// feature flag
|
||||||
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 1 : 0;
|
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 2 : 0;
|
||||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||||
minorVersion, messagingManager);
|
minorVersion, messagingManager);
|
||||||
return messagingManager;
|
return messagingManager;
|
||||||
|
|||||||
Reference in New Issue
Block a user