mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
21 Commits
attachment
...
beta-1.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b022afa67 | ||
|
|
e8b454b25b | ||
|
|
54c05b5ffe | ||
|
|
d145a082f5 | ||
|
|
4fd012c31a | ||
|
|
95d06770bf | ||
|
|
428247b7b2 | ||
|
|
a921361a56 | ||
|
|
fe7dfa721e | ||
|
|
92eb06a9e9 | ||
|
|
5beed1a748 | ||
|
|
774047d856 | ||
|
|
fc28e7aa88 | ||
|
|
78459499b2 | ||
|
|
c2973608d7 | ||
|
|
be1c33cb42 | ||
|
|
c955466bda | ||
|
|
593a0c4632 | ||
|
|
ed20b2d8d6 | ||
|
|
9ab9e02f8a | ||
|
|
3f70ae3c8c |
@@ -11,8 +11,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 10107
|
||||
versionName "1.1.7"
|
||||
versionCode 10108
|
||||
versionName "1.1.8"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -22,8 +22,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 26
|
||||
versionCode 10107
|
||||
versionName "1.1.7"
|
||||
versionCode 10108
|
||||
versionName "1.1.8"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
buildConfigField "String", "GitHash",
|
||||
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
# QR codes
|
||||
-keep class com.google.zxing.Result
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# RSS libraries
|
||||
-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.account.BriarAccountModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
@@ -13,6 +14,7 @@ import dagger.Component;
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
AppModule.class,
|
||||
AttachmentModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -47,15 +47,17 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
);
|
||||
private final MessageId msgId = new MessageId(getRandomId());
|
||||
|
||||
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||
private final AttachmentRetriever retriever =
|
||||
new AttachmentRetriever(null, dimensions);
|
||||
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
|
||||
new ImageSizeCalculator(imageHelper));
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(smallKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(160, item.getWidth());
|
||||
assertEquals(240, item.getHeight());
|
||||
@@ -70,8 +72,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testBigJpegImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(originalKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(1728, item.getWidth());
|
||||
assertEquals(2592, item.getHeight());
|
||||
@@ -86,8 +88,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testSmallPngImage() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||
InputStream is = getUrlInputStream(pngKitten);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(737, item.getWidth());
|
||||
assertEquals(510, item.getHeight());
|
||||
@@ -102,8 +104,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testUberGif() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(uberGif);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -117,8 +119,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testLottaPixels() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(lottaPixel);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(64250, item.getWidth());
|
||||
assertEquals(64250, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -132,8 +134,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testImageIoCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(imageIoCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(1184, item.getWidth());
|
||||
assertEquals(448, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -147,8 +149,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testGimpCrash() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(gimpCrash);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -162,8 +164,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testOptiPngAfl() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(optiPngAfl);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(32, item.getWidth());
|
||||
assertEquals(32, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
@@ -177,8 +179,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testLibrawError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getUrlInputStream(librawError);
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertTrue(item.hasError());
|
||||
}
|
||||
|
||||
@@ -186,8 +188,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testSmallAnimatedGifMaxDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(65535, item.getWidth());
|
||||
assertEquals(65535, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -201,8 +203,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testSmallAnimatedGifHugeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("animated2.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(10000, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -216,8 +218,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testSmallGifLargeDimensions() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||
InputStream is = getAssetInputStream("error_large.gif");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(16384, item.getWidth());
|
||||
assertEquals(16384, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -231,8 +233,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testHighError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_high.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||
@@ -246,8 +248,8 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
public void testWideError() throws Exception {
|
||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||
Attachment a = new Attachment(is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||
Attachment a = new Attachment(h, is);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, true);
|
||||
assertEquals(1920, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
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.briar.BriarCoreEagerSingletons;
|
||||
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.login.SignInReminderReceiver;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||
@@ -68,7 +69,8 @@ import dagger.Component;
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
AppModule.class
|
||||
AppModule.class,
|
||||
AttachmentModule.class
|
||||
})
|
||||
public interface AndroidComponent
|
||||
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.account;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
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.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
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 java.util.Objects.requireNonNull;
|
||||
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.showSoftKeyboard;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
|
||||
@Override
|
||||
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;
|
||||
setError(authorNameWrapper, getString(R.string.name_too_long), error);
|
||||
boolean enabled = authorNameLength > 0 && !error;
|
||||
@@ -89,8 +90,11 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setupController.setAuthorName(authorNameInput.getText().toString());
|
||||
setupController.showPasswordFragment();
|
||||
Editable text = authorNameInput.getText();
|
||||
if (text != null) {
|
||||
setupController.setAuthorName(text.toString().trim());
|
||||
setupController.showPasswordFragment();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.briar.android.attachment;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
import android.net.Uri;
|
||||
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.jsoup.UnsupportedMimeTypeException;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
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.Logger.getLogger;
|
||||
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.now;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||
|
||||
@NotNullByDefault
|
||||
class AttachmentCreationTask {
|
||||
@@ -31,8 +40,11 @@ class AttachmentCreationTask {
|
||||
private static Logger LOG =
|
||||
getLogger(AttachmentCreationTask.class.getName());
|
||||
|
||||
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
|
||||
|
||||
private final MessagingManager messagingManager;
|
||||
private final ContentResolver contentResolver;
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final GroupId groupId;
|
||||
private final Collection<Uri> uris;
|
||||
private final boolean needsSize;
|
||||
@@ -43,24 +55,26 @@ class AttachmentCreationTask {
|
||||
|
||||
AttachmentCreationTask(MessagingManager messagingManager,
|
||||
ContentResolver contentResolver,
|
||||
AttachmentCreator attachmentCreator, GroupId groupId,
|
||||
Collection<Uri> uris, boolean needsSize) {
|
||||
AttachmentCreator attachmentCreator,
|
||||
ImageSizeCalculator imageSizeCalculator,
|
||||
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
|
||||
this.messagingManager = messagingManager;
|
||||
this.contentResolver = contentResolver;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
this.groupId = groupId;
|
||||
this.uris = uris;
|
||||
this.needsSize = needsSize;
|
||||
this.attachmentCreator = attachmentCreator;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
void cancel() {
|
||||
canceled = true;
|
||||
attachmentCreator = null;
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
public void storeAttachments() {
|
||||
for (Uri uri: uris) processUri(uri);
|
||||
void storeAttachments() {
|
||||
for (Uri uri : uris) processUri(uri);
|
||||
AttachmentCreator attachmentCreator = this.attachmentCreator;
|
||||
if (!canceled && attachmentCreator != null)
|
||||
attachmentCreator.onAttachmentCreationFinished();
|
||||
@@ -98,6 +112,8 @@ class AttachmentCreationTask {
|
||||
}
|
||||
InputStream is = contentResolver.openInputStream(uri);
|
||||
if (is == null) throw new IOException();
|
||||
is = compressImage(is, contentType);
|
||||
contentType = "image/jpeg";
|
||||
long timestamp = System.currentTimeMillis();
|
||||
AttachmentHeader h = messagingManager
|
||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||
@@ -113,4 +129,48 @@ class AttachmentCreationTask {
|
||||
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;
|
||||
|
||||
|
||||
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 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
|
||||
public class 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;
|
||||
}
|
||||
public interface AttachmentCreator {
|
||||
|
||||
@UiThread
|
||||
public LiveData<AttachmentResult> storeAttachments(
|
||||
LiveData<GroupId> groupId, 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;
|
||||
}
|
||||
LiveData<AttachmentResult> storeAttachments(LiveData<GroupId> groupId,
|
||||
Collection<Uri> newUris);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@UiThread
|
||||
public 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));
|
||||
}
|
||||
LiveData<AttachmentResult> getLiveAttachments();
|
||||
|
||||
@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;
|
||||
}
|
||||
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@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();
|
||||
}
|
||||
void onAttachmentsSent(MessageId id);
|
||||
|
||||
/**
|
||||
* Needs to be called when created attachments will not be sent anymore.
|
||||
*/
|
||||
@UiThread
|
||||
public void cancel() {
|
||||
if (task == null) throw new AssertionError();
|
||||
task.cancel();
|
||||
deleteUnsentAttachments();
|
||||
resetState();
|
||||
}
|
||||
void cancel();
|
||||
|
||||
@UiThread
|
||||
private void resetState() {
|
||||
task = null;
|
||||
uris.clear();
|
||||
itemResults.clear();
|
||||
result.setValue(null);
|
||||
}
|
||||
void deleteUnsentAttachments();
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@IoExecutor
|
||||
void onAttachmentHeaderReceived(Uri uri, AttachmentHeader h,
|
||||
boolean needsSize);
|
||||
|
||||
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);
|
||||
}
|
||||
@IoExecutor
|
||||
void onAttachmentError(Uri uri, Throwable t);
|
||||
|
||||
}
|
||||
@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
|
||||
@NotNullByDefault
|
||||
public class AttachmentDimensions {
|
||||
class AttachmentDimensions {
|
||||
|
||||
final int defaultSize;
|
||||
final int minWidth, maxWidth;
|
||||
@@ -26,7 +26,7 @@ public class AttachmentDimensions {
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||
int defaultSize =
|
||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||
int minWidth = res.getDimensionPixelSize(
|
||||
|
||||
@@ -68,7 +68,7 @@ public class AttachmentItem implements Parcelable {
|
||||
header = new AttachmentHeader(messageId, mimeType);
|
||||
}
|
||||
|
||||
AttachmentHeader getHeader() {
|
||||
public AttachmentHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import android.arch.lifecycle.LiveData;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public interface AttachmentManager {
|
||||
|
||||
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;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapFactory.Options;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
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.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
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
|
||||
public class AttachmentRetriever {
|
||||
public interface AttachmentRetriever {
|
||||
|
||||
private static final Logger LOG =
|
||||
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);
|
||||
}
|
||||
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
|
||||
|
||||
@Nullable
|
||||
public List<AttachmentItem> cacheGet(MessageId messageId) {
|
||||
return attachmentCache.get(messageId);
|
||||
}
|
||||
List<AttachmentItem> cacheGet(MessageId messageId);
|
||||
|
||||
@DatabaseExecutor
|
||||
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;
|
||||
}
|
||||
Attachment getMessageAttachment(AttachmentHeader h) throws DbException;
|
||||
|
||||
/**
|
||||
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
|
||||
* {@link InputStream} which will be closed when this method returns.
|
||||
*/
|
||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
AttachmentItem getAttachmentItem(Attachment a, boolean needsSize);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@NotNullByDefault
|
||||
interface ImageHelper {
|
||||
public interface ImageHelper {
|
||||
|
||||
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
|
||||
private String getNicknameOrNull() {
|
||||
Editable name = contactNameInput.getText();
|
||||
if (name == null || name.toString().trim().length() == 0) {
|
||||
Editable text = contactNameInput.getText();
|
||||
if (text == null || text.toString().trim().length() == 0) {
|
||||
contactNameLayout.setError(getString(R.string.nickname_missing));
|
||||
contactNameInput.requestFocus();
|
||||
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));
|
||||
contactNameInput.requestFocus();
|
||||
return null;
|
||||
}
|
||||
contactNameLayout.setError(null);
|
||||
return name.toString().trim();
|
||||
return name;
|
||||
}
|
||||
|
||||
private void onAddButtonClicked() {
|
||||
|
||||
@@ -82,7 +82,7 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||
}
|
||||
|
||||
private void onSetButtonClicked() {
|
||||
String alias = aliasEditText.getText().toString();
|
||||
String alias = aliasEditText.getText().toString().trim();
|
||||
if (toUtf8(alias).length > MAX_AUTHOR_NAME_LENGTH) {
|
||||
aliasEditLayout.setError(getString(R.string.name_too_long));
|
||||
} 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.DbException;
|
||||
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.EventBus;
|
||||
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.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
|
||||
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.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.sort;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.logging.Level.INFO;
|
||||
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.logException;
|
||||
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";
|
||||
|
||||
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 ONBOARDING_DELAY_MS = 250;
|
||||
@@ -171,6 +175,8 @@ public class ConversationActivity extends BriarActivity
|
||||
volatile GroupInvitationManager groupInvitationManager;
|
||||
|
||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
|
||||
new ConcurrentHashMap<>();
|
||||
private final Observer<String> contactNameObserver = name -> {
|
||||
requireNonNull(name);
|
||||
loadMessages();
|
||||
@@ -434,29 +440,40 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
|
||||
throws DbException {
|
||||
MessageId id = h.getId();
|
||||
// If the message has text, load it
|
||||
if (h.hasText()) {
|
||||
String text = textCache.get(id);
|
||||
if (text == null) {
|
||||
LOG.info("Eagerly loading text for latest message");
|
||||
text = messagingManager.getMessageText(id);
|
||||
textCache.put(id, requireNonNull(text));
|
||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
|
||||
try {
|
||||
MessageId id = h.getId();
|
||||
// If the message has text, load it
|
||||
if (h.hasText()) {
|
||||
String text = textCache.get(id);
|
||||
if (text == null) {
|
||||
LOG.info("Eagerly loading text for latest message");
|
||||
text = messagingManager.getMessageText(id);
|
||||
textCache.put(id, requireNonNull(text));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the message has a single image, load its size - for multiple
|
||||
// images we use a grid so the size is fixed
|
||||
if (h.getAttachmentHeaders().size() == 1) {
|
||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||
if (items == null) {
|
||||
LOG.info("Eagerly loading image size for latest message");
|
||||
items = attachmentRetriever.getAttachmentItems(
|
||||
attachmentRetriever.getMessageAttachments(
|
||||
h.getAttachmentHeaders()));
|
||||
attachmentRetriever.cachePut(id, items);
|
||||
// If the message has a single image, load its size - for multiple
|
||||
// images we use a grid so the size is fixed
|
||||
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||
if (headers.size() == 1) {
|
||||
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
|
||||
if (items == null) {
|
||||
LOG.info("Eagerly loading image size for latest message");
|
||||
AttachmentHeader header = headers.get(0);
|
||||
try {
|
||||
Attachment a = attachmentRetriever
|
||||
.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);
|
||||
}
|
||||
|
||||
private void loadMessageAttachments(MessageId messageId,
|
||||
List<AttachmentHeader> headers) {
|
||||
private void loadMessageAttachments(PrivateMessageHeader h) {
|
||||
// TODO: Use placeholders for missing/invalid attachments
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||
attachmentRetriever.getMessageAttachments(headers);
|
||||
// TODO move getting the items off to IoExecutor, if size == 1
|
||||
List<AttachmentItem> items =
|
||||
attachmentRetriever.getAttachmentItems(attachments);
|
||||
displayMessageAttachments(messageId, items);
|
||||
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||
boolean needsSize = headers.size() == 1;
|
||||
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) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
@@ -553,7 +584,6 @@ public class ConversationActivity extends BriarActivity
|
||||
private void displayMessageAttachments(MessageId m,
|
||||
List<AttachmentItem> items) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
attachmentRetriever.cachePut(m, items);
|
||||
Pair<Integer, ConversationMessageItem> pair =
|
||||
adapter.getMessageItem(m);
|
||||
if (pair != null) {
|
||||
@@ -567,6 +597,13 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
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) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
@@ -617,6 +654,15 @@ public class ConversationActivity extends BriarActivity
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onAttachmentReceived(MessageId attachmentId) {
|
||||
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
|
||||
if (h != null) {
|
||||
LOG.info("Missing attachment received");
|
||||
loadMessageAttachments(h);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void onNewConversationMessage(ConversationMessageHeader h) {
|
||||
if (h instanceof ConversationRequest ||
|
||||
@@ -903,11 +949,11 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||
List<AttachmentHeader> headers) {
|
||||
List<AttachmentItem> attachments = attachmentRetriever.cacheGet(m);
|
||||
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
|
||||
List<AttachmentItem> attachments =
|
||||
attachmentRetriever.cacheGet(h.getId());
|
||||
if (attachments == null) {
|
||||
loadMessageAttachments(m, headers);
|
||||
loadMessageAttachments(h);
|
||||
return emptyList();
|
||||
}
|
||||
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.TransactionManager;
|
||||
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.settings.Settings;
|
||||
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.logException;
|
||||
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.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@@ -101,10 +99,13 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
@Inject
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
@IoExecutor Executor ioExecutor, TransactionManager db,
|
||||
MessagingManager messagingManager, ContactManager contactManager,
|
||||
TransactionManager db,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
PrivateMessageFactory privateMessageFactory) {
|
||||
PrivateMessageFactory privateMessageFactory,
|
||||
AttachmentRetriever attachmentRetriever,
|
||||
AttachmentCreator attachmentCreator) {
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.db = db;
|
||||
@@ -112,10 +113,8 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
this.privateMessageFactory = privateMessageFactory;
|
||||
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
||||
getAttachmentDimensions(application.getResources()));
|
||||
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
||||
ioExecutor, messagingManager, attachmentRetriever);
|
||||
this.attachmentRetriever = attachmentRetriever;
|
||||
this.attachmentCreator = attachmentCreator;
|
||||
messagingGroupId = Transformations
|
||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||
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.introduction.IntroductionRequest;
|
||||
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.privategroup.invitation.GroupInvitationRequest;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||
@@ -56,8 +55,7 @@ class ConversationVisitor implements
|
||||
if (h.getAttachmentHeaders().isEmpty()) {
|
||||
attachments = emptyList();
|
||||
} else {
|
||||
attachments = attachmentCache
|
||||
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
|
||||
attachments = attachmentCache.getAttachmentItems(h);
|
||||
}
|
||||
if (h.isLocal()) {
|
||||
item = new ConversationMessageItem(
|
||||
@@ -295,7 +293,6 @@ class ConversationVisitor implements
|
||||
}
|
||||
|
||||
interface AttachmentCache {
|
||||
List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||
List<AttachmentHeader> headers);
|
||||
List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
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.MessageId;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
@@ -135,10 +134,10 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
|
||||
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
|
||||
@Nullable Runnable afterCopy) {
|
||||
MessageId messageId = attachment.getMessageId();
|
||||
dbExecutor.execute(() -> {
|
||||
try {
|
||||
Attachment a = messagingManager.getAttachment(messageId);
|
||||
Attachment a =
|
||||
messagingManager.getAttachment(attachment.getHeader());
|
||||
copyImageFromDb(a, osp, afterCopy);
|
||||
} catch (DbException 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.DbException;
|
||||
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.api.messaging.Attachment;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
|
||||
import java.io.InputStream;
|
||||
@@ -50,11 +50,12 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
|
||||
@Override
|
||||
public void loadData(Priority priority,
|
||||
DataCallback<? super InputStream> callback) {
|
||||
MessageId id = attachment.getMessageId();
|
||||
dbExecutor.execute(() -> {
|
||||
if (cancel) return;
|
||||
try {
|
||||
inputStream = messagingManager.getAttachment(id).getStream();
|
||||
Attachment a =
|
||||
messagingManager.getAttachment(attachment.getHeader());
|
||||
inputStream = a.getStream();
|
||||
callback.onDataReady(inputStream);
|
||||
} catch (DbException e) {
|
||||
callback.onLoadFailed(e);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="@string/set_contact_alias_hint"
|
||||
android:inputType="textPersonName"
|
||||
android:inputType="text|textCapWords"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_medium"/>
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@
|
||||
<string name="message_hint">Idatzi mezua</string>
|
||||
<string name="image_caption_hint">Gehitu epigrafea (aukerakoa)</string>
|
||||
<string name="image_attach">Erantsi irudia</string>
|
||||
<string name="image_attach_error">Ezin izan da irudia erantsi</string>
|
||||
<string name="image_attach_error">Ezin izan dira irudiak erantsi</string>
|
||||
<string name="image_attach_error_too_big">Irudia handiegia da. Muga %d MB da.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Onartu gabeko irudi formatua: %s</string>
|
||||
<string name="set_contact_alias">Aldatu kontaktuaren izena</string>
|
||||
<string name="set_contact_alias_hint">Kontaktuaren izena</string>
|
||||
<string name="set_alias_button">Aldatu</string>
|
||||
@@ -138,6 +140,7 @@
|
||||
<string name="dialog_title_image_support">Orain irudiak bidali ahal dizkiozu eranskin gisa kontaktu honei</string>
|
||||
<string name="dialog_message_image_support">Sakatu ikono hau irudiak eransteko.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Gehitu inguruko kontaktua</string>
|
||||
<string name="face_to_face">Kontaktu gisa gehitu nahi duzun pertsonarekin aurrez aurre egon behar duzu.\n\nHonek etorkizunean inork zure itxurak egitea edo zure mezuak irakurtzea eragotziko du.</string>
|
||||
<string name="continue_button">Jarraitu</string>
|
||||
<string name="try_again_button">Saiatu berriro</string>
|
||||
@@ -155,14 +158,66 @@
|
||||
<string name="connection_error_explanation">Egiaztatu biak Wi-Fi sare berera konektatuta zaudetela.</string>
|
||||
<string name="connection_error_feedback">Arazoa mantentzen bada, mesedez <a href="feedback">bidali iruzkin bat</a> aplikazioa hobetzen laguntzeko.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Gehitu urruneko kontaktua</string>
|
||||
<string name="add_contact_nearby_title">Gehitu inguruko kontaktua</string>
|
||||
<string name="add_contact_remotely_title">Gehitu urruneko kontaktua</string>
|
||||
<string name="contact_name_hint">Eman ezizena kontaktuari</string>
|
||||
<string name="contact_link_intro">Sartu zure kontaktuaren ezizena hemen</string>
|
||||
<string name="contact_link_hint">Kontaktuaren esteka</string>
|
||||
<string name="paste_button">Itsatsi</string>
|
||||
<string name="add_contact_button">Gehitu kontaktua</string>
|
||||
<string name="copy_button">Kopiatu</string>
|
||||
<string name="share_button">Elkarbanatu</string>
|
||||
<string name="send_link_title">Partekatu estekak</string>
|
||||
<string name="add_contact_choose_nickname">Aukeratu ezizena</string>
|
||||
<string name="add_contact_choose_a_nickname">Sartu ezizen bat</string>
|
||||
<string name="nickname_intro">Eman ezizena zure kontaktuari. Zuk ikusi dezakezu, beste inork ez</string>
|
||||
<string name="your_link">Eman esteka hau gehitu nahi duzun kontaktuari</string>
|
||||
<string name="link_clip_label">Briar esteka</string>
|
||||
<string name="link_copied_toast">Esteka kopiatuta</string>
|
||||
<string name="adding_contact_error">Errore bat gertatu da kontaktua gehitzean.</string>
|
||||
<string name="pending_contact_requests_snackbar">Kontaktu eskaerak daude aztertzeke</string>
|
||||
<string name="pending_contact_requests">Kontaktu eskaerak aztertzeke</string>
|
||||
<string name="no_pending_contacts">Ez dago kontakturik aztertzeke</string>
|
||||
<string name="add_contact_remote_connecting">Elkartzen</string>
|
||||
<string name="waiting_for_contact_to_come_online">Kontaktua konektatu bitartean zain...</string>
|
||||
<string name="connecting">Elkartzen</string>
|
||||
<string name="adding_contact">Kontaktua gehitzen...</string>
|
||||
<string name="adding_contact_failed">Kontaktua gehitzeak huts egin du</string>
|
||||
<string name="dialog_title_remove_pending_contact">Berretsi kentzea</string>
|
||||
<string name="dialog_message_remove_pending_contact">Kontaktu hau gehitzen ari da oraindik. orain kentzen baduzu, ez da gehituko.</string>
|
||||
<string name="own_link_error">Sartu zure kontaktuaren esteka, ez zurea</string>
|
||||
<string name="nickname_missing">Sartu ezizen bat</string>
|
||||
<string name="invalid_link">Esteka baliogabea</string>
|
||||
<string name="unsupported_link">Esteka hau Briar-en bertsio berriago batetik dator. Eguneratu azken bertsiora eta saiatu berriro.</string>
|
||||
<string name="intent_own_link">Zure esteka ireki duzu. Erabili gehitu nahi duzun kontaktuarena!</string>
|
||||
<string name="missing_link">Sartu esteka bat</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">Kontaktu berria gehitu da</item>
|
||||
<item quantity="other">%d kontaktu berri gehitu dira.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">Kontaktu hau gehitzeak ohi baino denbora gehiago hartzen ari da</string>
|
||||
<string name="adding_contact_slow_title">Ezin da kontaktuarekin kontaktatu</string>
|
||||
<string name="adding_contact_slow_text">Kontaktu hau gehitzeak ohi baino denbora gehiago hartzen du.\n\nEgiaztatu kontaktua esteka jaso duela eta zu gehitu zaituela:</string>
|
||||
<string name="offline_state">Internet konexiorik ez</string>
|
||||
<string name="duplicate_link_dialog_title">Bikoiztutako esteka</string>
|
||||
<string name="duplicate_link_dialog_text_1">Baduzu kontaktu bat aztertzeke esteka honekin: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">Ziur %s eta %s pertsona bera direla?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Pertsona bera</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Pertsona desberdina</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s eta %s erabiltzaileek esteka bera bidali dizute.\n\nBietako batek agian zure kontaktuak zeintzuk diren asmatu nahi du.\n\nEz esan esteka bera jaso duzula beste norbaitengandik.</string>
|
||||
<string name="pending_contact_updated_toast">Aztertzeke zegoen kontaktua eguneratu da</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Aurkeztu zure kontaktuak</string>
|
||||
<string name="introduction_onboarding_text">Zure kontaktuak elkarren artean aurkez ditzakezu, Briar bidez konektatzeko aurrez aurre egon behar izan ez dezaten.</string>
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!--Setup-->
|
||||
<string name="setup_title">به Briar (برایر) خوش آمدید</string>
|
||||
<string name="setup_name_explanation">نام مستعارتان کنار هر مطلب شما قرار خواهد گرفت و بعد از ایجاد حساب کاربری امکان تغییر آن وجود ندارد.</string>
|
||||
<string name="setup_name_explanation">نام مستعار شما کنار هر مطلبی که پست کنید قرار خواهد گرفت. بعد از ایجاد حساب کاربری امکان تغییر آن وجود ندارد.</string>
|
||||
<string name="setup_next">بعدی</string>
|
||||
<string name="setup_password_intro">یک رمز عبور انتخاب کنید</string>
|
||||
<string name="setup_password_explanation">حساب کاربری Briar (برایر) شما به صورت رمزگذاری شده روی دستگاه شما به جای حافظه ابری ذخیره شده است. اگر شما رمز عبور خود را فراموش کنید یا Briar (برایر) را پاک کنید، راهی برای بازیابی حساب کاربری شما وجود نخواهد داشت.
|
||||
<string name="setup_password_intro">یک گذرواژه انتخاب کنید</string>
|
||||
<string name="setup_password_explanation">حساب کاربری Briar (برایر) شما به صورت رمزگذاری شده روی دستگاه شما به جای حافظه ابری ذخیره شده است. اگر گذرواژه خود را فراموش کنید یا Briar (برایر) را پاک کنید، راهی برای بازیابی حساب کاربری شما وجود نخواهد داشت.
|
||||
|
||||
یک رمز عبور طولانی انتخاب کنید که حدس آن سخت باشد، مثل چهار عبارت تصادفی یا ده لغت تصادفی با اعداد و نماد ها.</string>
|
||||
یک گذرواژه طولانی انتخاب کنید که حدس آن سخت باشد، مثل چهار عبارت تصادفی یا ده لغت تصادفی با اعداد و نماد ها.</string>
|
||||
<string name="setup_doze_title">اتصال های پس زمینه</string>
|
||||
<string name="setup_doze_intro">برای دریافت پیام، Briar (برایر) نیاز دارد تا در پس زمینه اتصال داشته باشد.</string>
|
||||
<string name="setup_doze_explanation">برای دریافت پیام، Briar (برایر) نیاز دارد تا در پس زمینه اتصال داشته باشد. لطفا بهینه سازی باتری را غیر فعال کنید تا Briar (برایر) بتواند به اتصال خود ادامه دهد.</string>
|
||||
<string name="setup_doze_button">دادن اجازه به اتصالات</string>
|
||||
<string name="setup_doze_button">دادن اجازه به ارتباطات</string>
|
||||
<string name="choose_nickname">نام مستعار خود را انتخاب کنید</string>
|
||||
<string name="choose_password">رمز عبور خود را انتخاب کنید</string>
|
||||
<string name="confirm_password">رمز عبور خود را تایید کنید</string>
|
||||
<string name="choose_password">گذرواژه خود را انتخاب کنید</string>
|
||||
<string name="confirm_password">گذرواژه خود را تایید کنید</string>
|
||||
<string name="name_too_long">نام بیش از حد طولانی می باشد</string>
|
||||
<string name="password_too_weak">رمز عبور ضعیف می باشد</string>
|
||||
<string name="password_too_weak">گذرواژه ضعیف می باشد</string>
|
||||
<string name="passwords_do_not_match">رمز های عبور مطابقت ندارند </string>
|
||||
<string name="create_account_button">ایجاد حساب کاربری</string>
|
||||
<string name="more_info">اطلاعات بیشتر</string>
|
||||
@@ -26,21 +26,21 @@
|
||||
<string name="setup_huawei_help">اگر Briar (برایر) به فهرست برنامه های محافظت شده اضافه نشده، نمی تواند در پس زمینه مشغول به کار باشد.</string>
|
||||
<string name="warning_dozed">ناتوانی %s برای اجراء در پس زمینه</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">رمز عبور</string>
|
||||
<string name="try_again">رمز عبور اشتباه است، لطفا دوباره سعی کنید</string>
|
||||
<string name="enter_password">گذرواژه</string>
|
||||
<string name="try_again">گذرواژه اشتباه است، لطفا دوباره سعی کنید</string>
|
||||
<string name="sign_in_button">ورود</string>
|
||||
<string name="forgotten_password">رمز عبور را فراموش کرده ام</string>
|
||||
<string name="dialog_title_lost_password">رمز عبور گمشده</string>
|
||||
<string name="dialog_message_lost_password">حساب کاربری Briar (برایر) شما به صورت رمزنگاری شده روی سیستم شما به جای حافظه ابری ذخیره شده است برای همین ما نمی توانیم رمز عبور شما را به صورت مجدد تنظیم کنیم. آیا مایل هستید تا حساب کاربری شما را پاک کنیم و دوباره از ابتدا شروع کنیم؟
|
||||
<string name="forgotten_password">گذرواژه خود را فراموش کرده ام</string>
|
||||
<string name="dialog_title_lost_password">گذرواژه گمشده</string>
|
||||
<string name="dialog_message_lost_password">حساب کاربری Briar (برایر) شما به صورت رمزنگاری شده روی سیستم شما به جای حافظه ابری ذخیره شده است برای همین ما نمی توانیم گذرواژه شما را به صورت مجدد تنظیم کنیم. آیا مایل هستید تا حساب کاربری شما را پاک کنیم و دوباره از ابتدا شروع کنیم؟
|
||||
|
||||
اخطار: هویت های شما، مخاطبان شما و پیام های شما برای همیشه از بین خواهند رفت.</string>
|
||||
<string name="startup_failed_notification_title">Briar (برایر) نمی تواند شروع به کار کند.</string>
|
||||
<string name="startup_failed_notification_text">برای اطلاعات بیشتر کلیک کنید</string>
|
||||
<string name="startup_failed_activity_title">خطا در شروع Briar (برایر)</string>
|
||||
<string name="startup_failed_db_error">به دلایلی، دیتابیس Briar (برایر) شما خراب شده و قابل اصلاح نیست. حساب کاربری شما، داده های شما و تمام مخاطبان شما از بین رفته اند. متاسفانه، شما یا باید Briar (برایر) را دوباره نصب کنید یا یک حساب کاربری جدید با انتخاب \'رمز عبور ام را فراموش کرده ام\' انتخاب کنید.</string>
|
||||
<string name="startup_failed_data_too_old_error">حساب کاربری شما با یک نسخه قدیمی از این برنامه ایجاد شده است و به همین خاطربا این نسخه نمی تواند باز شود. شما باید نسخه قدیمی را دوباره نصب کنید یا یک حساب کاربری جدید با \"رمز عبور ام را فراموش کرده ام\" در صفحه ی رمز عبور ایجاد کنید.</string>
|
||||
<string name="startup_failed_db_error">به دلایلی، پایگاه داده Briar (برایر) شما خراب شده و قابل اصلاح نیست. حساب کاربری شما، داده های شما و تمام مخاطبان شما از بین رفته اند. متاسفانه، شما یا باید Briar (برایر) را دوباره نصب کنید یا یک حساب کاربری جدید در هنگام خواست گذرواژه با انتخاب گذرواژه خود را فراموش کرده ام\' ایجاد کنید.</string>
|
||||
<string name="startup_failed_data_too_old_error">حساب کاربری شما با یک نسخه قدیمی از این برنامه ایجاد شده است و به همین خاطر با این نسخه نمی تواند باز شود. شما باید نسخه قدیمی را دوباره نصب کنید یا یک حساب کاربری جدید با \"گذرواژه خود را فراموش کرده ام\" در صفحه ی گذرواژه ایجاد کنید.</string>
|
||||
<string name="startup_failed_data_too_new_error">این نسخه از برنامه قدیمی می باشد. لطفا به آخرین نسخه از برنامه ارتقاء داده و دوباره سعی کنید.</string>
|
||||
<string name="startup_failed_service_error">Briar (برایر) نمی تواند یک پلاگین ضروری را اجراء کند. نصب دوباره Briar (برایر) معمولا این مشکل را حل میکند. هرچند، توجه داشته باشید که حساب کاربری و تمام داده های مرتبط با آن را از دست خواهید داد از آنجایی که Briar (برایر) از هیچ سرور مرکزی برای ذخیره داده های شما استفاده نمی کند.</string>
|
||||
<string name="startup_failed_service_error">Briar (برایر) نمی تواند یک افزونه ضروری را اجراء کند. نصب دوباره Briar (برایر) معمولا این مشکل را حل میکند. هرچند، توجه داشته باشید که حساب کاربری و تمام داده های مرتبط با آن را از دست خواهید داد از آنجایی که Briar (برایر) از هیچ سرور مرکزی برای ذخیره داده های شما استفاده نمی کند.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
|
||||
<item quantity="other">این یک نسخه آزمایشی از Briar (برایر) می باشد. حساب کاربری شما در %d روز آینده به پایان می رسد و امکان تمدید آن وجود نخواهد داشت.</item>
|
||||
@@ -50,15 +50,15 @@
|
||||
|
||||
بابت تست از شما سپاسگزاریم.</string>
|
||||
<string name="download_briar">برای اینکه به استفاده خود از Briar (برایر) ادامه دهید، لطفا نسخه ۱.۰ را دانلود کنید.</string>
|
||||
<string name="create_new_account">لازم است تا یک حساب کاربری جدید ایجاد کنید، می توانید از نام مستعار یکسان برای حساب های کاربری استفاده کنید.</string>
|
||||
<string name="create_new_account">لازم است تا یک حساب کاربری جدید ایجاد کنید، ولی می توانید از همان نام مستعار استفاده کنید.</string>
|
||||
<string name="download_briar_button">دانلود Briar (برایر) 1.0</string>
|
||||
<string name="startup_open_database">رمزگشایی سیستم ...</string>
|
||||
<string name="startup_migrate_database">به روز رسانی سیستم ...</string>
|
||||
<string name="startup_compact_database">درحال فشردهسازی پایگاه داده...</string>
|
||||
<string name="startup_open_database">در حال رمزگشایی سیستم ...</string>
|
||||
<string name="startup_migrate_database">در حال ارتقا سیستم ...</string>
|
||||
<string name="startup_compact_database">درحال فشرده سازی پایگاه داده...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">باز کردن منوی برنامه</string>
|
||||
<string name="nav_drawer_close_description">بستن منوی برنامه</string>
|
||||
<string name="contact_list_button">مخاطبین</string>
|
||||
<string name="contact_list_button">مخاطبان</string>
|
||||
<string name="groups_button">گروه های خصوصی</string>
|
||||
<string name="forums_button">تالار های گفتمان</string>
|
||||
<string name="blogs_button">بلاگ ها</string>
|
||||
@@ -72,7 +72,7 @@
|
||||
<string name="transport_lan">وای فای</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">از Briar (برایر) خارج شد</string>
|
||||
<string name="reminder_notification_text">برای وارد شدن دوباره کلیک کنید.</string>
|
||||
<string name="reminder_notification_text">برای وارد شدن دوباره ضربه بزنید.</string>
|
||||
<string name="reminder_notification_channel_title">یادآور ورود Briar (برایر)</string>
|
||||
<string name="reminder_notification_dismiss">رد کردن</string>
|
||||
<string name="ongoing_notification_title">وارد Briar (برایر) شد</string>
|
||||
@@ -115,16 +115,18 @@
|
||||
<string name="show_onboarding">نمایش پنجره راهنما</string>
|
||||
<string name="fix">اصلاح</string>
|
||||
<string name="help">راهنما</string>
|
||||
<string name="sorry">متاسفم</string>
|
||||
<string name="sorry">پوزش</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">هیچ مخاطبی برای نمایش وجود ندارد</string>
|
||||
<string name="no_contacts_action">برای افزودن مخاطب روی + کلیک کنید</string>
|
||||
<string name="date_no_private_messages">هیچ پیامی موجود نیست</string>
|
||||
<string name="no_private_messages">هیچ پیامی برای نشان دادن وجود ندارد</string>
|
||||
<string name="message_hint">نوشتن پیام</string>
|
||||
<string name="image_caption_hint">یک عنوان اضافه کنید (اختیاری)</string>
|
||||
<string name="image_attach">تصویر پیوست</string>
|
||||
<string name="image_attach_error">تصویر پیوست نشد</string>
|
||||
<string name="image_caption_hint">افزودن یک شرح (اختیاری)</string>
|
||||
<string name="image_attach">پیوست کردن تصویر</string>
|
||||
<string name="image_attach_error">پیوست کردن تصویر(ها) ممکن نمی باشد</string>
|
||||
<string name="image_attach_error_too_big">تصویر بیش از اندازه بزرگ است. محدودیت %d مگابایت می باشد.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">فرمت تصویر پشتیبانی نمیشود: %s</string>
|
||||
<string name="set_contact_alias">تغییر نام مخاطب</string>
|
||||
<string name="set_contact_alias_hint">نام مخاطب</string>
|
||||
<string name="set_alias_button">تغییر</string>
|
||||
@@ -136,15 +138,19 @@
|
||||
<string name="you">شما</string>
|
||||
<string name="save_image">ذخیره تصویر</string>
|
||||
<string name="dialog_title_save_image">ذخیره تصویر؟</string>
|
||||
<string name="dialog_message_save_image">ذخیره این تصویر به دیگر برنامهها اجازه میدهد تا به آن دسترسی داشته باشند.\n\n آیا مطمئن هستید که میخواهید ذخیره کنید؟</string>
|
||||
<string name="dialog_message_save_image">ذخیره این تصویر به دیگر برنامهها اجازه میدهد تا به آن دسترسی داشته باشند.
|
||||
|
||||
آیا از ذخیره کردن مطمئن می باشید؟</string>
|
||||
<string name="save_image_success">تصویر ذخیره شد</string>
|
||||
<string name="save_image_error">تصویر ذخیره نشد</string>
|
||||
<string name="dialog_title_no_image_support">تصاویر در دسترس نیست</string>
|
||||
<string name="dialog_message_no_image_support">مخاطب برایر شما هنوز از پیوستهای تصویر پشتیبانی نمیکند. هنگامی که آنها را ارتقا دهید یک آیکون دیگر خواهید دید.</string>
|
||||
<string name="dialog_title_image_support">شما هم اکنون میتوانید تصاویر را به این مخاطب ارسال کنید</string>
|
||||
<string name="dialog_message_image_support">برای پیوست تصاویر به روی آیکون ضربه بزنید</string>
|
||||
<string name="dialog_title_no_image_support">تصاویر در دسترس نمی باشند</string>
|
||||
<string name="dialog_message_no_image_support">Briar (برایر) مخاطب شما هنوز از پیوست های تصویری پشتیبانی نمیکند. هنگامی که آنها ارتقا دادند یک آیکون متفاوت خواهید دید.</string>
|
||||
<string name="dialog_title_image_support">شما هم اکنون میتوانید به این مخاطب تصاویر ارسال کنید</string>
|
||||
<string name="dialog_message_image_support">برای پیوست کردن تصاویر به روی آیکون ضربه بزنید.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">افزودن مخاطب از نزدیک</string>
|
||||
<string name="face_to_face">برای اضافه کردن فرد به عنوان مخاطب باید با او به صورت حضوری ملاقات کنید.
|
||||
|
||||
این از جعل هویت شما و یا از خوانده شدن پیام هایتان در آینده جلوگیری خواهد کرد.</string>
|
||||
<string name="continue_button">ادامه</string>
|
||||
<string name="try_again_button">دوباره سعی کنید</string>
|
||||
@@ -152,29 +158,86 @@
|
||||
<string name="exchanging_contact_details">تبادیل جزییات مخاطبu2026\</string>
|
||||
<string name="contact_added_toast">مخاطب اضافه شد: %s</string>
|
||||
<string name="contact_already_exists">مخاطب %s از قبل وجود دارد</string>
|
||||
<string name="qr_code_invalid">کد QR نامعتبر می باشد</string>
|
||||
<string name="qr_code_too_old">کد QR اسکن شده متعلق به یک نسخه قدیمی از %s است.
|
||||
<string name="qr_code_invalid">کد کیوآر نامعتبر می باشد</string>
|
||||
<string name="qr_code_too_old">کد کیوآر اسکن شده متعلق به یک نسخه قدیمی از %s است.
|
||||
|
||||
لطفا از مخاطبتان بخواهید تا به آخرین نسخه ارتقا داده و سپس دوباره تلاش کنید.</string>
|
||||
<string name="qr_code_too_new">کد QR که شما اسکن کردهاید متعلق به یک نسخه جدیدتر از %s است.
|
||||
لطفا از مخاطب خود بخواهید تا به آخرین نسخه ارتقا داده و سپس دوباره تلاش کنید.</string>
|
||||
<string name="qr_code_too_new">کد کیوآر اسکن شده توسط شما متعلق به یک نسخه جدیدتر از %s است.
|
||||
|
||||
لطفا به آخرین نسخه ارتقا داده و دوباره تلاش کنید.</string>
|
||||
<string name="camera_error">خطای دوربین</string>
|
||||
<string name="connecting_to_device">اتصال به دستگاهu2026\</string>
|
||||
<string name="authenticating_with_device">تصدیق سازی با دستگاه u2026\</string>
|
||||
<string name="connection_error_title">اتصال به مخاطب شما برقرار نشد</string>
|
||||
<string name="connection_error_explanation">لطفا مطمئن شوید که هردو شما به شبکه وای فای یکسان متصل هستید.</string>
|
||||
<string name="connection_error_feedback">اگر این مشکل ادامه پیدا کرد، لطفا <a href="feedback">بازخورد ارسال کنید</a> تا به ما در ارتقاء برنامه کمک کنید.</string>
|
||||
<string name="connection_error_explanation">لطفا مطمئن شوید که هر دو شما به شبکه وای فای یکسان متصل هستید.</string>
|
||||
<string name="connection_error_feedback">اگر این مشکل ادامه پیدا کرد، لطفا <a href="feedback">بازخورد ارسال کنید</a> تا به ما در بهبود برنامه کمک کنید.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">افزودن مخاطب از دور</string>
|
||||
<string name="add_contact_nearby_title">افزودن مخاطب از نزدیک</string>
|
||||
<string name="add_contact_remotely_title">افزودن مخاطب از راه دور</string>
|
||||
<string name="contact_name_hint">دادن یک نام مستعار به مخاطب</string>
|
||||
<string name="contact_link_intro">پیوند آمده از مخاطب خود را اینجا وارد کنید</string>
|
||||
<string name="contact_link_hint">پیوند مخاطب</string>
|
||||
<string name="paste_button">چسباندن</string>
|
||||
<string name="copy_button">تکثیر</string>
|
||||
<string name="share_button">همرسانی</string>
|
||||
<string name="add_contact_button">افزودن مخاطب</string>
|
||||
<string name="copy_button">کپی</string>
|
||||
<string name="share_button">اشتراک گذاری</string>
|
||||
<string name="send_link_title">تبادل پیوندها</string>
|
||||
<string name="add_contact_choose_nickname">انتخاب نام مستعار</string>
|
||||
<string name="add_contact_choose_a_nickname">یک نام مستعار وارد کنید</string>
|
||||
<string name="nickname_intro">به مخاطب خود یک نام مستعار بدهید. فقط شما قادر به دیدن آن می باشید.</string>
|
||||
<string name="your_link">برای افزودن مخاطب این پیوند را به او بدهید</string>
|
||||
<string name="link_clip_label">پیوند Briar (برایر)</string>
|
||||
<string name="link_copied_toast">پیوند کپی شد</string>
|
||||
<string name="adding_contact_error">یک خطا هنگام افزودن مخاطب رخ داد.</string>
|
||||
<string name="pending_contact_requests_snackbar">درخواست های مخاطب معلق وجود دارند</string>
|
||||
<string name="pending_contact_requests">درخواست های مخاطب معلق</string>
|
||||
<string name="no_pending_contacts">هیچ مخاطب معلقی وجود ندارد</string>
|
||||
<string name="add_contact_remote_connecting">در حال اتصال…</string>
|
||||
<string name="waiting_for_contact_to_come_online">انتظار برای آنلاین شدن مخاطب...</string>
|
||||
<string name="connecting">در حال اتصال…</string>
|
||||
<string name="adding_contact">در حال افزودن مخاطب...</string>
|
||||
<string name="adding_contact_failed">افزودن مخاطب ناموفق بود</string>
|
||||
<string name="dialog_title_remove_pending_contact">تایید حذف</string>
|
||||
<string name="dialog_message_remove_pending_contact">این مخاطب هنوز در حال اضافه شدن می باشد. اگر اکنون آن را حذف کنید، دیگر افزوده نخواهد شد.</string>
|
||||
<string name="own_link_error">به جای پیوند خود، پیوند مخاطب را وارد کنید</string>
|
||||
<string name="nickname_missing">لطفا یک نام مستعار وارد کنید</string>
|
||||
<string name="invalid_link">پیوند نامعتبر</string>
|
||||
<string name="unsupported_link">این پیوند از یک نسخه جدیدتر Briar (برایر) آمده است. لطفا به آخرین نسخه ارتقا دهید و دوباره امتحان کنید.</string>
|
||||
<string name="intent_own_link">شما پیوند خود را باز کردید. پیوند مخاطبی که میخواهید اضافه کنید را استفاده کنید!</string>
|
||||
<string name="missing_link">لطفا یک پیوند وارد کنید</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">۱</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">۲</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">مخاطب جدید افزوده شد.</item>
|
||||
<item quantity="other">%d مخاطب جدید افزوده شد.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">افزودن این مخاطب بیشتر از حد معمول وقت گرفته است</string>
|
||||
<string name="adding_contact_slow_title">عدم توانایی در اتصال به مخاطب</string>
|
||||
<string name="adding_contact_slow_text">افزودن این مخاطب بیش تر از معمول وقت گرفته است.
|
||||
|
||||
لطفا بررسی کنید که مخاطب شما پیوندتان را دریافت کرده و شما را افزوده است:</string>
|
||||
<string name="offline_state">ارتباط با اینترنت برقرار نیست</string>
|
||||
<string name="duplicate_link_dialog_title">پیوند تکراری</string>
|
||||
<string name="duplicate_link_dialog_text_1">شما هم اکنون یک مخاطب معلق با این پیوند دارید: %s</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">آیا %s و %s یک شخص می باشند؟</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">همان شخص</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">شخص متفاوت</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s و %s پیوند یکسانی را به شما ارسال کردند.
|
||||
|
||||
یکی از آن ها ممکن است سعی در شناسایی مخاطبان شما داشته باشد.
|
||||
|
||||
به آن ها نگویید که همان لینک را از یک شخص دیگر دریافت کرده اید.</string>
|
||||
<string name="pending_contact_updated_toast">مخاطب معلق به روز رسانی شد</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">معرفی مخاطبان</string>
|
||||
<string name="introduction_onboarding_text">شما می توانید مخاطبان خود را به یکدیگر معرفی کنید، در این صورت دیگر نیازی به دیدار حضوری برای اتصال روی Briar (برایر) نمی باشد.</string>
|
||||
@@ -367,11 +430,11 @@
|
||||
<string name="blogs_rss_feeds_manage_delete_error">خوراک نمی تواند پاک شود!</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">هیچ خوراک RSS برای نمایش وجود ندارد
|
||||
|
||||
برای وارد کردن خوراک روی آیکون + کلیک کنید</string>
|
||||
برای وارد کردن خوراک روی آیکون + ضربه بزنید</string>
|
||||
<string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string>
|
||||
<!--Settings Display-->
|
||||
<string name="pref_language_title">زبان و منطقه</string>
|
||||
<string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را ری استارت کنید.</string>
|
||||
<string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را دوباره راه اندازی کنید.</string>
|
||||
<string name="pref_language_default">پیش فرض سیستم</string>
|
||||
<string name="display_settings_title">نمایش</string>
|
||||
<string name="pref_theme_title">قالب</string>
|
||||
@@ -385,15 +448,15 @@
|
||||
<string name="bluetooth_setting_enabled">هر زمانی که مخاطبان نزدیک هستند</string>
|
||||
<string name="bluetooth_setting_disabled">فقط در هنگام افزودن مخاطبان</string>
|
||||
<string name="tor_network_setting">اتصال از طریق اینترنت (تور)</string>
|
||||
<string name="tor_network_setting_automatic">اتوماتیک مبتنی بر مکان</string>
|
||||
<string name="tor_network_setting_without_bridges">استفاده از تور بدون بریج</string>
|
||||
<string name="tor_network_setting_with_bridges">استفاده از تور با بریج</string>
|
||||
<string name="tor_network_setting_automatic">خودکار مبتنی بر موقعیت</string>
|
||||
<string name="tor_network_setting_without_bridges">استفاده از تور بدون پل ها</string>
|
||||
<string name="tor_network_setting_with_bridges">استفاده از تور با پل ها</string>
|
||||
<string name="tor_network_setting_never">وصل نشو</string>
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">خودکار: %1$s(در %2$s)</string>
|
||||
<string name="tor_mobile_data_title">استفاده از داده موبایل</string>
|
||||
<string name="tor_only_when_charging_title">اتصال از طریق اینترنت (تور) فقط در هنگام شارژ</string>
|
||||
<string name="tor_only_when_charging_summary">وقتی دستگاه با باتری در حال اجرا است، ارتباط به اینترنت را غیرفعال میکند</string>
|
||||
<string name="tor_only_when_charging_summary">ارتباط اینترنت را هنگامی که دستگاه در حال استفاده از باتری خود می باشد را غیرفعال می کند</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">امنیت</string>
|
||||
<string name="pref_lock_title">قفل برنامه</string>
|
||||
@@ -414,17 +477,17 @@
|
||||
<string name="pref_lock_timeout_60">۱ ساعت</string>
|
||||
<string name="pref_lock_timeout_never">هرگز</string>
|
||||
<string name="pref_lock_timeout_never_summary">هیچوقت Briar (برایر) را به صورت خودکار قفل نکن</string>
|
||||
<string name="change_password">تغییر رمز عبور</string>
|
||||
<string name="current_password">رمز عبور فعلی</string>
|
||||
<string name="choose_new_password">رمز عبور جدید</string>
|
||||
<string name="confirm_new_password">تائید رمز جدید</string>
|
||||
<string name="password_changed">رمز عبور تغییر کرده است</string>
|
||||
<string name="panic_setting">برپایی دکمه هراس</string>
|
||||
<string name="change_password">تغییر گذرواژه</string>
|
||||
<string name="current_password">گذرواژه فعلی</string>
|
||||
<string name="choose_new_password">گذرواژه جدید</string>
|
||||
<string name="confirm_new_password">تائید گذرواژه جدید</string>
|
||||
<string name="password_changed">گذرواژه تغییر کرده است</string>
|
||||
<string name="panic_setting">نصب دکمه هراس</string>
|
||||
<string name="panic_setting_title">دکمه هراس</string>
|
||||
<string name="panic_setting_hint">تنظیم نحوه واکنش Briar (برایر) هنگام فعال سازی دکمه هراس</string>
|
||||
<string name="panic_app_setting_title">برنامه دکمه هراس</string>
|
||||
<string name="unknown_app">یک برنامه ناشناخته</string>
|
||||
<string name="panic_app_setting_summary">هیچ برنامه تنظیم نشده است</string>
|
||||
<string name="panic_app_setting_summary">هیچ برنامه ای تنظیم نشده است</string>
|
||||
<string name="panic_app_setting_none">هیچکدام</string>
|
||||
<string name="dialog_title_connect_panic_app">تایید برنامه هراس</string>
|
||||
<string name="dialog_message_connect_panic_app">آیا مطمئن هستید که میخواهید به %1$s اجازه دهید تا باعث عملیات مخرب دکمه هراس بشود؟</string>
|
||||
@@ -432,7 +495,7 @@
|
||||
<string name="panic_setting_signout_title">خروج</string>
|
||||
<string name="panic_setting_signout_summary">در صورت کلیک بر روی کلید هراس از Briar (برایر) خارج شو</string>
|
||||
<string name="purge_setting_title">حذف حساب کاربری</string>
|
||||
<string name="purge_setting_summary">پاک کردن حساب کاربری Briar (برایر) شما در صورتی که دکمه هراس فشار داده شود. اخطار: این باعث پاک شدن دائمی تمام هویت ها، مخاطبان و پیام های شما خواهد شد</string>
|
||||
<string name="purge_setting_summary">در صورت فشار دادن دکمه هراس حساب کاربری Briar (برایر) شما حذف خواهد شد. اخطار: این باعث حذف دائمی تمام هویت ها، مخاطبان و پیام های شما خواهد شد</string>
|
||||
<string name="uninstall_setting_title">پاک کردن Briar (برایر)</string>
|
||||
<string name="uninstall_setting_summary">این نیازمند تایید دستی در موعد هراس میباشد</string>
|
||||
<!--Settings Notifications-->
|
||||
@@ -461,10 +524,10 @@
|
||||
<string name="feedback_settings_title">بازخورد</string>
|
||||
<string name="send_feedback">ارسال بازخورد</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">هشدار لینک</string>
|
||||
<string name="link_warning_title">هشدار پیوند</string>
|
||||
<string name="link_warning_intro">با باز کردن لینک زیر شما یک برنامه بیرونی را باز خواهید کرد.</string>
|
||||
<string name="link_warning_text">این میتواند برای شناسایی شما مورد استفاده قرار بگیرد. قبل از باز کردن آن به قابل اطمینان بودن شخصی که آن را برای شما ارسال کرده فکر کنید و بعد برای احتیاط آن را با مرورگر تور باز کنید.</string>
|
||||
<string name="link_warning_open_link">باز کردن لینک</string>
|
||||
<string name="link_warning_text">این میتواند برای شناسایی شما مورد استفاده قرار بگیرد. قبل از باز کردن این پیوند به قابل اطمینان بودن شخصی که آن را برای شما ارسال کرده فکر کنید و بعد برای احتیاط آن را با مرورگر تور باز کنید.</string>
|
||||
<string name="link_warning_open_link">باز کردن پیوند</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">گزارش خطای Briar (برایر)</string>
|
||||
<string name="briar_crashed">متاسفیم، Briar (برایر) از کار افتاده است.</string>
|
||||
@@ -493,21 +556,27 @@
|
||||
<string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی Briar (برایر) قرار بگیرند</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">دسترسی به دوربین</string>
|
||||
<string name="permission_camera_request_body">برای اسکن کردن کد QR دسترسی به دوربین لازم است.</string>
|
||||
<string name="permission_location_title">مجوز موقعیت محل</string>
|
||||
<string name="permission_location_request_body">برای پیدا کردن دستگاههای بلوتوث، برایر نیاز به اجازه دسترسی به موقعیت مکانی شما دارد.\n\n برایر موقعیت مکان شما را ذخیره نمیکند و آن را با کسی به اشتراک نمیگذارد.</string>
|
||||
<string name="permission_camera_request_body">برای اسکن کردن کد کیوآر دسترسی به دوربین لازم است.</string>
|
||||
<string name="permission_location_title">اجازه موقعیت</string>
|
||||
<string name="permission_location_request_body">برای پیدا کردن دستگاههای بلوتوث، Briar (برایر) نیاز به اجازه دسترسی به موقعیت شما دارد.
|
||||
|
||||
Briar (برایر) موقعیت شما را ذخیره نمیکند و آن را با کسی به اشتراک نمیگذارد.</string>
|
||||
<string name="permission_camera_location_title">دوربین و موقعیت</string>
|
||||
<string name="permission_camera_location_request_body">برای اسکن کد کیوآر، برایر نیاز به دسترسی به دوربین دارد.\n\n برای پیدا کردن دستگاههای بلوتوث، برایر نیاز به اجازه دسترسی به موقعیت مکانی شما دارد.\n\n برایر موقعیت مکان شما را ذخیره نمیکند و آن را با کسی به اشتراک نمیگذارد.</string>
|
||||
<string name="permission_camera_location_request_body">برای اسکن کد کیوآر، Briar (برایر) نیاز به دسترسی به دوربین دارد.
|
||||
|
||||
برای پیدا کردن دستگاههای بلوتوث، Briar (برایر) نیاز به اجازه دسترسی به موقعیت شما دارد.
|
||||
|
||||
Briar (برایر) موقعیت شما را ذخیره نمیکند و آن را با کسی به اشتراک نمیگذارد.</string>
|
||||
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
|
||||
|
||||
لطفا اجازه دسترسی را بدهید.</string>
|
||||
<string name="qr_code">کد QR</string>
|
||||
<string name="show_qr_code_fullscreen">نمایش کد QR به صورت فول اسکرین</string>
|
||||
<string name="qr_code">کد کیوآر</string>
|
||||
<string name="show_qr_code_fullscreen">نمایش کد کیوآر به صورت فول اسکرین</string>
|
||||
<!--App Locking-->
|
||||
<string name="lock_unlock">آنلاک کردن Briar (برایر)</string>
|
||||
<string name="lock_unlock_verbose">PIN، الگو یا رمز عبور دستگاه خود را برای آنلاک کردن Briar (برایر) وارد کنید</string>
|
||||
<string name="lock_unlock">باز کردن قفل Briar (برایر)</string>
|
||||
<string name="lock_unlock_verbose">پین، الگو یا گذرواژه دستگاه خود را برای باز کردن قفل Briar (برایر) وارد کنید</string>
|
||||
<string name="lock_unlock_fingerprint_description">برای ادامه سنسور اثر انگشت را با انگشت ثبت شده تان لمس کنید</string>
|
||||
<string name="lock_unlock_password">از رمز عبور استفاده کنید</string>
|
||||
<string name="lock_unlock_password">استفاده از گذرواژه</string>
|
||||
<string name="lock_is_locked">Briar (برایر) قفل می باشد</string>
|
||||
<string name="lock_tap_to_unlock">برای آنلاک کردن کلیک کنید</string>
|
||||
<!--Screenshots-->
|
||||
@@ -520,7 +589,7 @@
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">هی باب!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">هی آلیس! ممنون از اینکه درباره Briar به من گفتی!</string>
|
||||
<string name="screenshot_message_2">هی آلیس! ممنون از اینکه درباره Briar (برایر) به من گفتی!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">خواهش میکنم، امیدوارم ازش خوشت بیاد 😀</string>
|
||||
</resources>
|
||||
|
||||
@@ -125,6 +125,11 @@
|
||||
<string name="date_no_private_messages">Brak wiadomości.</string>
|
||||
<string name="no_private_messages">Brak wiadomości do pokazania</string>
|
||||
<string name="message_hint">Typ wiadomości</string>
|
||||
<string name="image_caption_hint">Dodaj podpis (opcjonalne)</string>
|
||||
<string name="image_attach">Załącz zdjęcie</string>
|
||||
<string name="image_attach_error_too_big">Zdjęcie jest zbyt duże. Limit wynosi %d MB.</string>
|
||||
<string name="image_attach_error_invalid_mime_type">Format zdjęcia jest nieobsługiwany: %s</string>
|
||||
<string name="set_contact_alias">Zmień imię kontaktu</string>
|
||||
<string name="set_contact_alias_hint">Nazwa kontaktu</string>
|
||||
<string name="set_alias_button">Zmień</string>
|
||||
<string name="delete_contact">Usuń kontakt</string>
|
||||
@@ -134,6 +139,13 @@
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">You</string>
|
||||
<string name="save_image">Zapisz obrazek</string>
|
||||
<string name="dialog_title_save_image">Zapisać Zdjęcie?</string>
|
||||
<string name="dialog_message_save_image">Zapisanie tego zdjęcia pozwoli innym aplikacjom na dostęp do niego.\n\nJesteś pewien, że chcesz go zapisać?</string>
|
||||
<string name="save_image_success">Zdjęcie zostało zapisane</string>
|
||||
<string name="save_image_error">Nie udało się zapisać zdjęcia</string>
|
||||
<string name="dialog_title_no_image_support">Zdjęcie Niedostępne</string>
|
||||
<string name="dialog_message_no_image_support">Briar twojego kontaktu nie obsługuje jeszcze załączników zdjęć. Jak wykonają aktualizację, zobaczysz inną ikonę.</string>
|
||||
<string name="dialog_title_image_support">Możesz teraz wysyłać zdjęcia do tego kontaktu</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="face_to_face">Musisz spotkać się z osobą którą chcesz dodać jako kontakt.\n\nTo uniemożliwi komukolwiek podszyć się pod Ciebie lub czytać wysyłane wiadomości.</string>
|
||||
<string name="continue_button">Kontynuuj</string>
|
||||
@@ -153,11 +165,21 @@
|
||||
<string name="paste_button">Wklej</string>
|
||||
<string name="copy_button">Kopiuj</string>
|
||||
<string name="share_button">Udostępnij</string>
|
||||
<string name="link_copied_toast">Link skopiowany</string>
|
||||
<string name="add_contact_remote_connecting">Trwa łączenie...</string>
|
||||
<string name="connecting">Trwa łączenie...</string>
|
||||
<string name="missing_link">Proszę, wprowadź link</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="offline_state">Brak połączenia z Internetem</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Udostępnij swoje kontakty.</string>
|
||||
<string name="introduction_onboarding_text">Możesz udostępniać kontakty, tak aby osoby nie musiały spotykać się osobiście.</string>
|
||||
@@ -361,10 +383,14 @@
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">Automatycznie: %1$s (za %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Używaj danych komórkowych</string>
|
||||
<string name="tor_only_when_charging_title">Łącz przez Internet (Tor) tylko podczas ładowania</string>
|
||||
<string name="tor_only_when_charging_summary">Wyłącza połączenie Internetowe, gdy urządzenie pracuje na baterii</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Bezpieczeństwo</string>
|
||||
<string name="pref_lock_title">Blokada aplikacji</string>
|
||||
<string name="pref_lock_summary">Użyj blokady ekranu urządzenia by chronić Briar gdy zalogowany</string>
|
||||
<string name="pref_lock_disabled_summary">Aby użyć tej funkcji skonfiguruj blokadę ekranu na swoim urządzeniu</string>
|
||||
<string name="pref_lock_timeout_title">Czas bezczynności aplikacji</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Gdy nie używam Briar, automatycznie zablokuj po %s</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
@@ -393,6 +419,7 @@
|
||||
<string name="panic_app_setting_none">Brak</string>
|
||||
<string name="dialog_title_connect_panic_app">Potwierdź Awaryjną Aplikację</string>
|
||||
<string name="dialog_message_connect_panic_app">Czy na pewno chcesz pozwolić %1$s aby działała jako przycisk wyjścia awaryjnego?</string>
|
||||
<string name="panic_setting_destructive_action">Działania Destrukcyjne</string>
|
||||
<string name="panic_setting_signout_title">Wyloguj się</string>
|
||||
<string name="panic_setting_signout_summary">Wyloguj się z Briar jeśli przycisk zostanie wciśnięty</string>
|
||||
<string name="purge_setting_title">Skasuj Konto</string>
|
||||
@@ -427,6 +454,7 @@
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Uważaj na link</string>
|
||||
<string name="link_warning_intro">Otwierasz link w zewnętrznej aplikacji.</string>
|
||||
<string name="link_warning_text">Może to posłużyć do twojej identyfikacji. Zastanów się czy ufasz osobie która wysłała Ci ten link i rozważ otwarcie go za pomocą Przeglądarki Tor.</string>
|
||||
<string name="link_warning_open_link">Otwórz Link</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Zgłoś błąd w Briar</string>
|
||||
@@ -454,6 +482,7 @@
|
||||
<string name="permission_camera_title">Dostęp do aparatu</string>
|
||||
<string name="permission_camera_request_body">Aby zeskanować kod QR, Briar potrzebuje mieć dostęp do aparatu.</string>
|
||||
<string name="permission_camera_location_title">Kamera i lokalizacja</string>
|
||||
<string name="permission_camera_location_request_body">Aby zeskanować kod QR, Briar potrzebuje dostępu do kamery.\n\nAby odkryć urządzenia Bluetooth, Briar potrzebuje zezwolenia na dostęp do Twojej lokalizacji.\n\nBriar nie przechowuje Twojej lokalizacji ani nie udostępnia jej nikomu.</string>
|
||||
<string name="permission_camera_denied_body">Odmówiłeś dostępu do kamery, lecz dodawanie kontaktów tego wymaga.\n\nProszę udzielić dostępu.</string>
|
||||
<string name="qr_code">Kod QR</string>
|
||||
<string name="show_qr_code_fullscreen">Pokaż QR na pełnym ekranie</string>
|
||||
@@ -466,9 +495,15 @@
|
||||
<string name="lock_tap_to_unlock">Dotknij by odblokować</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alicja</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_bob">Bob</string>
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_carol">Karol</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
|
||||
<string name="screenshot_message_1">Hej Bob!</string>
|
||||
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
|
||||
<string name="screenshot_message_2">Hej Alicja! Dzięki za opowiedzenie mi o Briarze!</string>
|
||||
<!--This is a message to be used in screenshots.-->
|
||||
<string name="screenshot_message_3">Nie ma problemu, mam nadzieję, że ci się podoba 😀</string>
|
||||
</resources>
|
||||
|
||||
@@ -116,6 +116,12 @@
|
||||
<string name="date_no_private_messages">Sem Mensagens</string>
|
||||
<string name="no_private_messages">Nenhuma mensagem para ser exibida</string>
|
||||
<string name="message_hint">Digite a mensagem</string>
|
||||
<string name="image_caption_hint">Adicione uma legenda (opcional)</string>
|
||||
<string name="image_attach">Anexar imagem</string>
|
||||
<string name="image_attach_error">Não foi possível anexar imagem(s)</string>
|
||||
<string name="image_attach_error_too_big">Imagem muito grande. O limite é de %dMB. </string>
|
||||
<string name="image_attach_error_invalid_mime_type">Formato da imagem não suportado: %s</string>
|
||||
<string name="set_contact_alias">Alterar nome do contato</string>
|
||||
<string name="set_contact_alias_hint">Nome de contato</string>
|
||||
<string name="set_alias_button">Trocar</string>
|
||||
<string name="delete_contact">Apagar contato</string>
|
||||
@@ -124,7 +130,17 @@
|
||||
<string name="contact_deleted_toast">Contato apagado</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Você</string>
|
||||
<string name="save_image">Salvar imagem</string>
|
||||
<string name="dialog_title_save_image">Salvar imagem?</string>
|
||||
<string name="dialog_message_save_image">Salvando essa imagem irá permitir que outros aplicativos a acessem.\n\nVocê tem certeza que quer salvar? </string>
|
||||
<string name="save_image_success">Imagem salva</string>
|
||||
<string name="save_image_error">Não foi possível salvar imagem</string>
|
||||
<string name="dialog_title_no_image_support">Imagens indisponíveis</string>
|
||||
<string name="dialog_message_no_image_support">Seu contato do Briar ainda não suporta anexo de imagens. Quando ele atualizar um você verá ícone diferente.</string>
|
||||
<string name="dialog_title_image_support">Você agora pode enviar imagens para esse contato</string>
|
||||
<string name="dialog_message_image_support">Toque nesse ícone para anexar imagens.</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Adicionar contato que está próximo</string>
|
||||
<string name="face_to_face">Você deve estar frente-a-frente com a pessoa que deseja adicionar como contato.\n\nIsso evita que alguém se passe por você ou leia suas mensagens no futuro.</string>
|
||||
<string name="continue_button">Continuar</string>
|
||||
<string name="try_again_button">Tente novamente</string>
|
||||
@@ -142,15 +158,66 @@
|
||||
<string name="connection_error_explanation">Por favor. certifique se ambos estão conectados na mesma rede Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Se o problema persistir, for favor <a href="feedback">enviar feedback</a> para nós ajudar a melhor o app.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Adicionar contato à distância</string>
|
||||
<string name="add_contact_nearby_title">Adicionar contato que está próximo</string>
|
||||
<string name="add_contact_remotely_title">Adicionar contato à distância</string>
|
||||
<string name="contact_name_hint">De um apelido ao contato</string>
|
||||
<string name="contact_link_intro">Insira o link do seu contato aqui</string>
|
||||
<string name="contact_link_hint">Link do contato</string>
|
||||
<string name="paste_button">Colar</string>
|
||||
<string name="add_contact_button">Adicionar contato</string>
|
||||
<string name="copy_button">Copiar</string>
|
||||
<string name="share_button">Compartilhar</string>
|
||||
<string name="send_link_title">Passem os links</string>
|
||||
<string name="add_contact_choose_nickname">Escolha um apelido</string>
|
||||
<string name="add_contact_choose_a_nickname">Insira um apelido</string>
|
||||
<string name="nickname_intro">De um apelido ao contato. Somente você pode vê-lo. </string>
|
||||
<string name="your_link">Passe esse link para o contato que você quer adicionar</string>
|
||||
<string name="link_clip_label">Link Briar</string>
|
||||
<string name="link_copied_toast">Link copiado</string>
|
||||
<string name="adding_contact_error">Ocorreu um erro ao adicionar o contato.</string>
|
||||
<string name="pending_contact_requests_snackbar">Existem solicitações de contato pendentes</string>
|
||||
<string name="pending_contact_requests">Solicitações de contatos pendentes</string>
|
||||
<string name="no_pending_contacts">Sem contatos pendentes</string>
|
||||
<string name="add_contact_remote_connecting">Conectando...</string>
|
||||
<string name="waiting_for_contact_to_come_online">Esperando pelo usuário estar online ...</string>
|
||||
<string name="connecting">Conectando...</string>
|
||||
<string name="adding_contact">Adicionando contato ...</string>
|
||||
<string name="adding_contact_failed">Adicionar o contato falhou</string>
|
||||
<string name="dialog_title_remove_pending_contact">Confirme a remoção </string>
|
||||
<string name="dialog_message_remove_pending_contact">Esse contato ainda está sendo adicionado. Se você removê-lo agora, o mesmo não será adicionado</string>
|
||||
<string name="own_link_error">Insira o link do seu contato, não seu próprio </string>
|
||||
<string name="nickname_missing">Por favor insira um apelido</string>
|
||||
<string name="invalid_link">Link invalido</string>
|
||||
<string name="unsupported_link">Esse link veio de uma versão mais recente do Briar. Por favor atualize para versão mais recente e tente novamente </string>
|
||||
<string name="intent_own_link">Você abriu seu próprio link. Utilize o link do contato que quer adicionar!</string>
|
||||
<string name="missing_link">Por favor insira um link</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="one">Novo contato adicionado.</item>
|
||||
<item quantity="other">%dnovos contados adicionados.</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">Adicionar este contato está demorando mais que o normal</string>
|
||||
<string name="adding_contact_slow_title">Não foi possível conectar ao contato </string>
|
||||
<string name="adding_contact_slow_text">Adicionar este contato está demorando mais que o normal.\n\nPor favor se certifique que o seu contato recebeu seu link e adicionou você: </string>
|
||||
<string name="offline_state">Sem conexão à Internet</string>
|
||||
<string name="duplicate_link_dialog_title">Link duplicado</string>
|
||||
<string name="duplicate_link_dialog_text_1">Você já tem um contato pendente com esse link: %s </string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">%s e %s são a mesma pessoa?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">Mesma pessoa</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">Pessoa diferente</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s e %s te enviaram o mesmo link.\n\nUm deles talvez esteja tentando descobrir quem são seus contatos.\n\nNão conte pra eles que recebeu o mesmo link de outra pessoa.</string>
|
||||
<string name="pending_contact_updated_toast">Contato pendente atualizado</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Apresente seus contatos</string>
|
||||
<string name="introduction_onboarding_text">Você pode apresentar seus contatos entre si, assim eles não precisam se encontrar pessoalmente para se comunicar no Briar.</string>
|
||||
@@ -346,6 +413,8 @@
|
||||
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
|
||||
<string name="tor_network_setting_summary">Automático: %1$s (em %2$s) </string>
|
||||
<string name="tor_mobile_data_title">Usar dados moveis</string>
|
||||
<string name="tor_only_when_charging_title">Conectar via Internet (Tor) apenas enquanto carregando</string>
|
||||
<string name="tor_only_when_charging_summary">Desabilita conexão à Internet quando seu dispositivo estiver somente na bateria</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Segurança</string>
|
||||
<string name="pref_lock_title">Bloqueio de app</string>
|
||||
@@ -415,6 +484,7 @@
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Aviso sobre Link</string>
|
||||
<string name="link_warning_intro">Você está prestes a abrir esse link em um Aplicativo Externo.</string>
|
||||
<string name="link_warning_text">Isso pode ser usado para identificar você. Pense sobre se quer ou não confiar na pessoa que te enviou esse link e considere abrindo o no Tor Browser</string>
|
||||
<string name="link_warning_open_link">Abrir Link</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Relatório de falhas do Briar</string>
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
<string name="image_caption_hint">添加一段描述(可选)</string>
|
||||
<string name="image_attach">附加图片</string>
|
||||
<string name="image_attach_error">无法附加图片</string>
|
||||
<string name="image_attach_error_too_big">图片过大。上限为 %d MB。</string>
|
||||
<string name="image_attach_error_invalid_mime_type">不支持的图片格式:%s</string>
|
||||
<string name="set_contact_alias">更改联系人姓名</string>
|
||||
<string name="set_contact_alias_hint">联系人姓名</string>
|
||||
<string name="set_alias_button">更改</string>
|
||||
@@ -133,6 +135,7 @@
|
||||
<string name="dialog_title_image_support">你现在可以向此联系人发送图片</string>
|
||||
<string name="dialog_message_image_support">轻按此图标即可附加图片。</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">添加附近的联系人</string>
|
||||
<string name="face_to_face">您必须面对面添加联系人。\n\n这样将防止未来他人冒充您的身份并查看您的信息。</string>
|
||||
<string name="continue_button">继续</string>
|
||||
<string name="try_again_button">重试</string>
|
||||
@@ -150,8 +153,65 @@
|
||||
<string name="connection_error_explanation">请确保你们连接到了同一个无线局域网中。</string>
|
||||
<string name="connection_error_feedback">如果该问题仍存在,请 <a href="feedback">发送反馈</a> 帮助我们改善应用。</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">添加远处的联系人</string>
|
||||
<string name="add_contact_nearby_title">添加附近的联系人</string>
|
||||
<string name="add_contact_remotely_title">添加远处的联系人</string>
|
||||
<string name="contact_name_hint">添加联系人昵称</string>
|
||||
<string name="contact_link_intro">在此输入您联系人的链接</string>
|
||||
<string name="contact_link_hint">联系人的链接</string>
|
||||
<string name="paste_button">粘贴</string>
|
||||
<string name="add_contact_button">添加联系人</string>
|
||||
<string name="copy_button">复制</string>
|
||||
<string name="share_button">分享</string>
|
||||
<string name="send_link_title">交换链接</string>
|
||||
<string name="add_contact_choose_nickname">选择昵称</string>
|
||||
<string name="add_contact_choose_a_nickname">输入昵称</string>
|
||||
<string name="nickname_intro">为联系人添加一个昵称。仅您可见。</string>
|
||||
<string name="your_link">将此链接发送给您希望添加的联系人</string>
|
||||
<string name="link_clip_label">Briar 链接</string>
|
||||
<string name="link_copied_toast">链接已复制</string>
|
||||
<string name="adding_contact_error">添加联系人时发生错误。</string>
|
||||
<string name="pending_contact_requests_snackbar">有待处理的联系人请求</string>
|
||||
<string name="pending_contact_requests">待处理的联系人请求</string>
|
||||
<string name="no_pending_contacts">没有待处理的联系人</string>
|
||||
<string name="add_contact_remote_connecting">连接中……</string>
|
||||
<string name="waiting_for_contact_to_come_online">正在等待联系人上线……</string>
|
||||
<string name="connecting">连接中……</string>
|
||||
<string name="adding_contact">正在添加联系人……</string>
|
||||
<string name="adding_contact_failed">添加联系人失败</string>
|
||||
<string name="dialog_title_remove_pending_contact">确认移除</string>
|
||||
<string name="dialog_message_remove_pending_contact">这个联系人还在添加中。如果您现在移除,则此联系人不会被添加。</string>
|
||||
<string name="own_link_error">输入您联系人的链接,不要输入您自己的</string>
|
||||
<string name="nickname_missing">请输入一个昵称</string>
|
||||
<string name="invalid_link">无效的链接</string>
|
||||
<string name="unsupported_link">这条链接来自更新版本的 Briar。请升级到最新版本后并重试。</string>
|
||||
<string name="intent_own_link">您打开了自己的链接。请使用您希望添加的联系人的链接!</string>
|
||||
<string name="missing_link">请输入一个链接</string>
|
||||
<!--This is a numeral indicating the first step in a series of screens-->
|
||||
<string name="step_1">1</string>
|
||||
<!--This is a numeral indicating the second step in a series of screens-->
|
||||
<string name="step_2">2</string>
|
||||
<plurals name="contact_added_notification_text">
|
||||
<item quantity="other">%d 新联系人已添加。</item>
|
||||
</plurals>
|
||||
<string name="adding_contact_slow_warning">添加此联系人比通常花费了更多时间</string>
|
||||
<string name="adding_contact_slow_title">无法连接到联系人</string>
|
||||
<string name="adding_contact_slow_text">添加此联系人比通常花费了更多时间。\n\n请检查您的联系人是否收到您的链接并已添加您:</string>
|
||||
<string name="offline_state">无网络连接</string>
|
||||
<string name="duplicate_link_dialog_title">重复的链接</string>
|
||||
<string name="duplicate_link_dialog_text_1">您已经有此链接的待处理联系人:</string>
|
||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||
<string name="duplicate_link_dialog_text_2">%s 和 %s 是同一个人吗?</string>
|
||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
|
||||
<string name="same_person_button">同一个人</string>
|
||||
<!--This is a button for answering that two nicknames refer to different people. This string
|
||||
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||
please use "No" instead, and use "Yes" for the "Same Person" button-->
|
||||
<string name="different_person_button">不同的人</string>
|
||||
<string name="duplicate_link_dialog_text_3">%s 和 %s 给你发送了相同的链接。\n\n其中一个人可能企图找出谁是你的联系人。\n\n不要告诉他们你从其他人那里收到了相同的链接。</string>
|
||||
<string name="pending_contact_updated_toast">待处理联系人已更新</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">介绍您的联系人</string>
|
||||
<string name="introduction_onboarding_text">您可以互相介绍您的联系人,这样他们可以直接在 Briar 上建立联系而不必亲自见面。</string>
|
||||
|
||||
@@ -2,14 +2,13 @@ package org.briarproject.briar.android.attachment;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
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.AttachmentHeader;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -25,32 +24,30 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||
100, 50, 200, 75, 300
|
||||
);
|
||||
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 AttachmentRetriever controller =
|
||||
new AttachmentRetriever(
|
||||
messagingManager,
|
||||
dimensions,
|
||||
imageHelper
|
||||
);
|
||||
private final ImageSizeCalculator imageSizeCalculator;
|
||||
private final AttachmentRetriever retriever;
|
||||
|
||||
public AttachmentRetrieverTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
MessagingManager messagingManager =
|
||||
context.mock(MessagingManager.class);
|
||||
imageSizeCalculator = context.mock(ImageSizeCalculator.class);
|
||||
retriever = new AttachmentRetrieverImpl(messagingManager, dimensions,
|
||||
imageHelper, imageSizeCalculator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSize() {
|
||||
String mimeType = "image/jpeg";
|
||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
||||
Attachment attachment = getAttachment(mimeType);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||
will(returnValue("jpg"));
|
||||
}});
|
||||
|
||||
AttachmentItem item =
|
||||
controller.getAttachmentItem(h, attachment, false);
|
||||
AttachmentItem item = retriever.getAttachmentItem(attachment, false);
|
||||
assertEquals(mimeType, item.getMimeType());
|
||||
assertEquals("jpg", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
@@ -59,31 +56,31 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testNoSizeWrongMimeTypeProducesError() {
|
||||
String mimeType = "application/octet-stream";
|
||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
||||
Attachment attachment = getAttachment(mimeType);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
AttachmentItem item =
|
||||
controller.getAttachmentItem(h, attachment, false);
|
||||
AttachmentItem item = retriever.getAttachmentItem(attachment, false);
|
||||
assertTrue(item.hasError());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallJpegImage() {
|
||||
String mimeType = "image/jpeg";
|
||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
||||
Attachment attachment = getAttachment(mimeType);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
||||
will(returnValue(new DecodeResult(160, 240, mimeType)));
|
||||
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||
with(mimeType));
|
||||
will(returnValue(new Size(160, 240, mimeType)));
|
||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||
will(returnValue("jpg"));
|
||||
}});
|
||||
|
||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(160, item.getWidth());
|
||||
assertEquals(240, item.getHeight());
|
||||
@@ -97,16 +94,17 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testBigJpegImage() {
|
||||
String mimeType = "image/jpeg";
|
||||
AttachmentHeader h = getAttachmentHeader(mimeType);
|
||||
Attachment attachment = getAttachment(mimeType);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
||||
will(returnValue(new DecodeResult(1728, 2592, mimeType)));
|
||||
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||
with(mimeType));
|
||||
will(returnValue(new Size(1728, 2592, mimeType)));
|
||||
oneOf(imageHelper).getExtensionFromMimeType(mimeType);
|
||||
will(returnValue("jpg"));
|
||||
}});
|
||||
|
||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||
assertEquals(1728, item.getWidth());
|
||||
assertEquals(2592, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
@@ -116,21 +114,24 @@ public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||
|
||||
@Test
|
||||
public void testMalformedError() {
|
||||
AttachmentHeader h = getAttachmentHeader("image/jpeg");
|
||||
String mimeType = "image/jpeg";
|
||||
Attachment attachment = getAttachment(mimeType);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
||||
will(returnValue(new DecodeResult(0, 0, "")));
|
||||
oneOf(imageSizeCalculator).getSize(with(any(InputStream.class)),
|
||||
with(mimeType));
|
||||
will(returnValue(new Size()));
|
||||
oneOf(imageHelper).getExtensionFromMimeType("");
|
||||
will(returnValue(null));
|
||||
}});
|
||||
|
||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
||||
AttachmentItem item = retriever.getAttachmentItem(attachment, true);
|
||||
assertTrue(item.hasError());
|
||||
}
|
||||
|
||||
private AttachmentHeader getAttachmentHeader(String contentType) {
|
||||
return new AttachmentHeader(msgId, contentType);
|
||||
private Attachment getAttachment(String 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;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class Attachment {
|
||||
|
||||
private final AttachmentHeader header;
|
||||
private final InputStream stream;
|
||||
|
||||
public Attachment(InputStream stream) {
|
||||
public Attachment(AttachmentHeader header, InputStream stream) {
|
||||
this.header = header;
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public AttachmentHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public InputStream getStream() {
|
||||
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.
|
||||
*/
|
||||
int MINOR_VERSION = 1;
|
||||
int MINOR_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Stores a local private message.
|
||||
@@ -40,7 +40,7 @@ public interface MessagingManager extends ConversationClient {
|
||||
/**
|
||||
* Stores a local attachment message.
|
||||
*
|
||||
* @throws FileTooBigException
|
||||
* @throws FileTooBigException If the attachment is too big
|
||||
*/
|
||||
AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
|
||||
String contentType, InputStream is) throws DbException, IOException;
|
||||
@@ -68,9 +68,13 @@ public interface MessagingManager extends ConversationClient {
|
||||
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
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.briarproject.briar.api.conversation.ConversationMessageHeader;
|
||||
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.InvalidAttachmentException;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
@@ -374,13 +375,20 @@ class MessagingManagerImpl implements MessagingManager, IncomingMessageHook,
|
||||
}
|
||||
|
||||
@Override
|
||||
public Attachment getAttachment(MessageId m) throws DbException {
|
||||
public Attachment getAttachment(AttachmentHeader h) throws DbException {
|
||||
// TODO: Support large messages
|
||||
MessageId m = h.getMessageId();
|
||||
byte[] body = clientHelper.getMessage(m).getBody();
|
||||
try {
|
||||
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();
|
||||
return new Attachment(new ByteArrayInputStream(body, offset,
|
||||
return new Attachment(h, new ByteArrayInputStream(body, offset,
|
||||
body.length - offset));
|
||||
} catch (FormatException e) {
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class MessagingModule {
|
||||
conversationManager.registerConversationClient(messagingManager);
|
||||
// Advertise the current or previous minor version depending on the
|
||||
// feature flag
|
||||
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 1 : 0;
|
||||
int minorVersion = featureFlags.shouldEnableImageAttachments() ? 2 : 0;
|
||||
clientVersioningManager.registerClient(CLIENT_ID, MAJOR_VERSION,
|
||||
minorVersion, messagingManager);
|
||||
return messagingManager;
|
||||
|
||||
Reference in New Issue
Block a user