Merge branch '214-user-avatars' into 'master'

Merge user avatars feature branch

See merge request briar/briar!1334
This commit is contained in:
akwizgran
2021-01-25 15:15:54 +00:00
225 changed files with 4256 additions and 1036 deletions

View File

@@ -13,6 +13,7 @@
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method>

View File

@@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-api" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -6,4 +6,7 @@ package org.briarproject.bramble.api;
public interface FeatureFlags {
boolean shouldEnableImageAttachments();
boolean shouldEnableProfilePictures();
}

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.db.PendingContactExistsException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -220,16 +219,6 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(AuthorId a) throws DbException;
/**
* Returns the {@link AuthorInfo} for the given author.
*/
AuthorInfo getAuthorInfo(Transaction txn, AuthorId a) throws DbException;
interface ContactHook {
/**

View File

@@ -29,4 +29,12 @@ public class NullSafety {
public static void requireNull(@Nullable Object o) {
if (o != null) throw new AssertionError();
}
/**
* Stand-in for {@code Objects.equals()}.
*/
public static boolean equals(@Nullable Object a, @Nullable Object b) {
return (a == b) || (a != null && a.equals(b));
}
}

View File

@@ -1,42 +0,0 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class AuthorInfoTest extends BrambleTestCase {
@Test
public void testEquals() {
assertEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, null)
);
assertEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(VERIFIED)
);
assertNotEquals(
new AuthorInfo(NONE, "test"),
new AuthorInfo(NONE)
);
assertNotEquals(
new AuthorInfo(NONE),
new AuthorInfo(NONE, "test")
);
assertNotEquals(
new AuthorInfo(NONE, "a"),
new AuthorInfo(NONE, "b")
);
}
}

View File

@@ -20,9 +20,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager;
@@ -40,10 +38,6 @@ import javax.inject.Inject;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe
@@ -261,25 +255,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
db.removeContact(txn, c);
}
@Override
public AuthorInfo getAuthorInfo(AuthorId a) throws DbException {
return db.transactionWithResult(true, txn -> getAuthorInfo(txn, a));
}
@Override
public AuthorInfo getAuthorInfo(Transaction txn, AuthorId authorId)
throws DbException {
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId))
return new AuthorInfo(OURSELVES);
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
if (contacts.size() > 1) throw new AssertionError();
Contact c = contacts.iterator().next();
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
else return new AuthorInfo(UNVERIFIED, c.getAlias());
}
@Override
public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) {

View File

@@ -15,6 +15,9 @@ import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_TRANSPORT_ID;
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MSG_KEY_VERSION;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -43,9 +46,9 @@ class TransportPropertyValidator extends BdfMessageValidator {
clientHelper.parseAndValidateTransportProperties(dictionary);
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("transportId", transportId);
meta.put("version", version);
meta.put("local", false);
meta.put(MSG_KEY_TRANSPORT_ID, transportId);
meta.put(MSG_KEY_VERSION, version);
meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta);
}
}

View File

@@ -8,18 +8,15 @@ import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
@@ -31,10 +28,6 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
@@ -46,7 +39,6 @@ import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase {
@@ -212,75 +204,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local));
}
@Test
public void testGetAuthorInfo() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(singletonList(contact)));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
}
@Test
public void testGetAuthorInfoTransaction() throws DbException {
Transaction txn = new Transaction(null, true);
// check unknown author
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, remote.getId());
will(returnValue(emptyList()));
}});
AuthorInfo authorInfo =
contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNKNOWN, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
// check unverified contact
checkAuthorInfoContext(txn, remote.getId(), singletonList(contact));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(UNVERIFIED, authorInfo.getStatus());
assertEquals(contact.getAlias(), authorInfo.getAlias());
// check verified contact
Contact verified = getContact(remote, local, true);
checkAuthorInfoContext(txn, remote.getId(), singletonList(verified));
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
assertEquals(VERIFIED, authorInfo.getStatus());
assertEquals(verified.getAlias(), authorInfo.getAlias());
// check ourselves
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
never(db).getContactsByAuthorId(txn, remote.getId());
}});
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
assertEquals(OURSELVES, authorInfo.getStatus());
assertNull(authorInfo.getAlias());
}
private void checkAuthorInfoContext(Transaction txn, AuthorId authorId,
Collection<Contact> contacts) throws DbException {
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(localAuthor));
oneOf(db).getContactsByAuthorId(txn, authorId);
will(returnValue(contacts));
}});
}
@Test
public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true);

View File

@@ -22,6 +22,17 @@ public class BrambleCoreIntegrationTestModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> true;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return true;
}
@Override
public boolean shouldEnableProfilePictures() {
return true;
}
};
}
}

View File

@@ -108,7 +108,7 @@ dependencies {
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
def glideVersion = '4.10.0'
def glideVersion = '4.11.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -5,6 +5,7 @@ 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.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import javax.inject.Singleton;
@@ -15,6 +16,7 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,

View File

@@ -1,41 +0,0 @@
package org.briarproject.briar.android.attachment;
import android.content.res.AssetManager;
import org.junit.Before;
import java.io.IOException;
import java.io.InputStream;
import static androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
abstract class AbstractAttachmentCreationTaskTest {
private final ImageHelper imageHelper = new ImageHelperImpl();
private final ImageSizeCalculator imageSizeCalculator =
new ImageSizeCalculator(imageHelper);
private AttachmentCreationTask task;
@Before
@SuppressWarnings("ConstantConditions") // add real objects when needed
public void setUp() {
task = new AttachmentCreationTask(null,
getApplicationContext().getContentResolver(), null,
imageSizeCalculator, null, null, true);
}
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
task.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.briar.android.attachment.media.MediaModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractAttachmentRetrieverComponent {
void inject(AttachmentRetrieverIntegrationTest test);
}

View File

@@ -1,15 +1,20 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.GroupId;
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.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.InputStream;
import java.util.Random;
import javax.inject.Inject;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -24,16 +29,28 @@ public class AttachmentRetrieverIntegrationTest {
private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300
);
private final GroupId groupId = new GroupId(getRandomId());
private final MessageId msgId = new MessageId(getRandomId());
private final ImageHelper imageHelper = new ImageHelperImpl();
private final AttachmentRetriever retriever =
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
new ImageSizeCalculator(imageHelper));
@Inject
ImageHelper imageHelper;
@Inject
ImageSizeCalculator imageSizeCalculator;
private final AttachmentRetriever retriever;
public AttachmentRetrieverIntegrationTest() {
AbstractAttachmentRetrieverComponent component =
DaggerAbstractAttachmentRetrieverComponent.builder().build();
component.inject(this);
retriever = new AttachmentRetrieverImpl(null, null, dimensions,
imageHelper, imageSizeCalculator);
}
@Test
public void testSmallJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -49,7 +66,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testBigJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -65,7 +82,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallPngImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -81,7 +98,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testUberGif() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -96,7 +113,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLottaPixels() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -111,7 +128,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testImageIoCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/png");
InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -126,7 +143,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testGimpCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -141,7 +158,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testOptiPngAfl() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -156,7 +173,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testLibrawError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -165,7 +182,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifMaxDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -180,7 +197,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallAnimatedGifHugeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -195,7 +212,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testSmallGifLargeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -210,7 +227,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testHighError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -225,7 +242,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test
public void testWideError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
AttachmentHeader h = new AttachmentHeader(groupId, msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageCompressorComponent {
void inject(AbstractImageCompressorTest test);
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
public abstract class AbstractImageCompressorTest {
@Inject
ImageCompressor imageCompressor;
public AbstractImageCompressorTest() {
AbstractImageCompressorComponent component =
DaggerAbstractImageCompressorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageCompressorComponent component);
void testCompress(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
imageCompressor.compressImage(is, contentType);
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.attachment.media;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
MediaModule.class
})
interface AbstractImageSizeCalculatorComponent {
void inject(AbstractImageSizeCalculatorTest test);
}

View File

@@ -0,0 +1,51 @@
package org.briarproject.briar.android.attachment.media;
import android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
import javax.inject.Inject;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public abstract class AbstractImageSizeCalculatorTest {
@Inject
ImageSizeCalculator imageSizeCalculator;
public AbstractImageSizeCalculatorTest() {
AbstractImageSizeCalculatorComponent component =
DaggerAbstractImageSizeCalculatorComponent.builder().build();
component.inject(this);
}
protected abstract void inject(
AbstractImageSizeCalculatorComponent component);
void testCanCalculateSize(String filename, String contentType, int width,
int height) throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertFalse(size.hasError());
assertEquals(width, size.getWidth());
assertEquals(height, size.getHeight());
}
void testCannotCalculateSize(String filename, String contentType)
throws IOException {
InputStream is = getAssetManager().open(filename);
Size size = imageSizeCalculator.getSize(is, contentType);
assertTrue(size.hasError());
}
static AssetManager getAssetManager() {
// pm.getResourcesForApplication(packageName).getAssets() did not work
//noinspection deprecation
return getContext().getAssets();
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class AttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
public class ImageCompressorTest
extends AbstractImageCompressorTest {
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Test
public void testCompressSmallPng() throws Exception {

View File

@@ -0,0 +1,80 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class ImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Test
public void testCalculateSizeKittenSmall() throws Exception {
testCanCalculateSize("kitten_small.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeKittenSmallNoExif() throws Exception {
testCanCalculateSize("kitten_small_noexif.jpg", "image/jpeg", 160, 240);
}
@Test
public void testCalculateSizeSmallPng() throws Exception {
testCanCalculateSize("red-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeMediumPng() throws Exception {
testCanCalculateSize("blue-500x500.png", "image/png", 500, 500);
}
@Test
public void testCalculateSizeLargePng() throws Exception {
testCanCalculateSize("green-1000x2000.png", "image/png", 1000, 2000);
}
@Test
public void testCalculateSizeTransparentPng() throws Exception {
testCanCalculateSize("transparent-100x100.png", "image/png", 100, 100);
}
@Test
public void testCalculateSizeVeryHighJpg() throws Exception {
testCanCalculateSize("error_high.jpg", "image/jpeg", 1, 10000);
}
@Test
public void testCalculateSizeVeryWideJpg() throws Exception {
testCanCalculateSize("error_wide.jpg", "image/jpeg", 1920, 1);
}
@Test
public void testCalculateSizeAnimatedGif1() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated.gif", "image/gif", 65535, 65535);
}
@Test
public void testCalculateSizeAnimatedGif2() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("animated2.gif", "image/gif", 10000, 10000);
}
@Test
public void testCalculateSizeVeryLargeGif() throws Exception {
// TODO: Remove this assumption when we support large messages
assumeTrue(SDK_INT >= 24);
testCanCalculateSize("error_large.gif", "image/gif", 16384, 16384);
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteAttachmentCreationTaskTest
extends AbstractAttachmentCreationTaskTest {
public class PngSuiteImageCompressorTest
extends AbstractImageCompressorTest {
private static final Logger LOG =
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName());
getLogger(PngSuiteImageCompressorTest.class.getName());
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Parameters
public static Iterable<String> data() throws IOException {
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
private final String filename;
public PngSuiteAttachmentCreationTaskTest(String filename) {
public PngSuiteImageCompressorTest(String filename) {
this.filename = filename;
}
@Test
public void testPngSuiteCompress() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteAttachmentCreationTaskTest.class));
PngSuiteImageCompressorTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x")) {
try {

View File

@@ -0,0 +1,80 @@
package org.briarproject.briar.android.attachment.media;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public class PngSuiteImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
private static final Logger LOG =
getLogger(PngSuiteImageSizeCalculatorTest.class.getName());
@Override
protected void inject(AbstractImageSizeCalculatorComponent component) {
component.inject(this);
}
@Parameters
public static Iterable<String> data() throws IOException {
List<String> data = new ArrayList<>();
String[] files = requireNonNull(getAssetManager().list("PngSuite"));
for (String filename : files)
if (filename.endsWith(".png")) data.add(filename);
return data;
}
private final String filename;
public PngSuiteImageSizeCalculatorTest(String filename) {
this.filename = filename;
}
// some files have sizes other than 32x32
private Map<String, Size> customSizes = new HashMap<>();
{
customSizes.put("cdfn2c08.png", new Size(8, 32, "image/png"));
customSizes.put("cdhn2c08.png", new Size(32, 8, "image/png"));
customSizes.put("cdsn2c08.png", new Size(8, 8, "image/png"));
customSizes.put("PngSuite.png", new Size(256, 256, "image/png"));
}
@Test
public void testPngSuiteCalculateSizes() throws Exception {
assumeTrue(isOptionalTestEnabled(
PngSuiteImageSizeCalculatorTest.class));
LOG.info("Testing " + filename);
if (filename.startsWith("x") && !filename.equals("xcsn0g01.png")) {
testCannotCalculateSize("PngSuite/" + filename, "image/png");
} else if (filename.startsWith("s")) {
int size = Integer.parseInt(filename.substring(1, 3));
testCanCalculateSize("PngSuite/" + filename, "image/png", size,
size);
} else {
int width = 32;
int height = 32;
if (customSizes.containsKey(filename)) {
Size size = customSizes.get(filename);
width = size.getWidth();
height = size.getHeight();
}
testCanCalculateSize("PngSuite/" + filename, "image/png", width,
height);
}
}
}

View File

@@ -5,6 +5,7 @@ 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.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
@@ -16,6 +17,7 @@ import dagger.Component;
@Component(modules = {
AppModule.class,
AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class,
BrambleAndroidModule.class,
BriarAccountModule.class,
@@ -26,6 +28,7 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupDataTest test);
void inject(ConversationActivityScreenshotTest test);
void inject(SettingsActivityScreenshotTest test);
}

View File

@@ -116,7 +116,7 @@ public class SetupDataTest extends ScreenshotTest {
throws DbException {
Context ctx = getApplicationContext();
String bobName = ctx.getString(R.string.screenshot_bob);
Contact bob = testDataCreator.addContact(bobName);
Contact bob = testDataCreator.addContact(bobName, true);
// TODO add messages

View File

@@ -0,0 +1,105 @@
/*
* Some code was taken from:
*
* RIG Random Image Generator
* https://github.com/stedi-akk/RandomImageGenerator
* licenced under Apache2 license.
*/
package org.briarproject.briar.android.test;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import javax.annotation.Nullable;
import javax.inject.Inject;
public class TestAvatarCreatorImpl implements TestAvatarCreator {
private final int WIDTH = 800;
private final int HEIGHT = 640;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final float[] hsv = new float[3];
private final Random random = new Random();
private final ImageCompressor imageCompressor;
@Inject
TestAvatarCreatorImpl(ImageCompressor imageCompressor) {
this.imageCompressor = imageCompressor;
}
@Nullable
@Override
public InputStream getAvatarInputStream() throws IOException {
Bitmap bitmap = generateBitmap();
return imageCompressor.compressImage(bitmap);
}
private Bitmap generateBitmap() {
// one pattern is boring, let's at least use two
if (random.nextBoolean()) {
return generateColoredPixels();
} else {
return generateColoredCircles();
}
}
private Bitmap generateColoredPixels() {
Bitmap bitmap = getBitmapWithRandomBackground();
Canvas canvas = new Canvas(bitmap);
Rect pixel = new Rect();
int pixelMultiplier = random.nextInt(500) + 1;
for (int x = 0; x < WIDTH; x += pixelMultiplier) {
for (int y = 0; y < HEIGHT; y += pixelMultiplier) {
pixel.set(x, y, x + pixelMultiplier, y + pixelMultiplier);
paint.setColor(getRandomColor());
canvas.drawRect(pixel, paint);
}
}
return bitmap;
}
private Bitmap generateColoredCircles() {
Bitmap bitmap = getBitmapWithRandomBackground();
int biggestSide = Math.max(WIDTH, HEIGHT);
int selectedCount = random.nextInt(10) + 2;
Canvas canvas = new Canvas(bitmap);
float radiusFrom = biggestSide / 12f;
float radiusTo = biggestSide / 4f;
for (int i = 0; i < selectedCount; i++) {
float cx = random.nextInt(WIDTH);
float cy = random.nextInt(HEIGHT);
float radius =
random.nextInt((int) (radiusTo - radiusFrom)) + radiusFrom;
paint.setColor(getRandomColor());
canvas.drawCircle(cx, cy, radius, paint);
}
return bitmap;
}
private Bitmap getBitmapWithRandomBackground() {
Bitmap bitmap =
Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(getRandomColor());
return bitmap;
}
private int getRandomColor() {
hsv[0] = random.nextInt(360);
hsv[1] = random.nextFloat();
hsv[2] = 1f;
return Color.HSVToColor(hsv);
}
}

View File

@@ -32,6 +32,7 @@ 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.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.view.EmojiTextInputView;
@@ -39,6 +40,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -47,6 +49,7 @@ import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -71,7 +74,8 @@ import dagger.Component;
BrambleAndroidModule.class,
BriarAccountModule.class,
AppModule.class,
AttachmentModule.class
AttachmentModule.class,
MediaModule.class
})
public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
@@ -94,6 +98,10 @@ public interface AndroidComponent
IdentityManager identityManager();
AttachmentReader attachmentReader();
AuthorManager authorManager();
PluginManager pluginManager();
EventBus eventBus();

View File

@@ -35,13 +35,16 @@ import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File;
import java.security.GeneralSecurityException;
@@ -72,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class,
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory
@@ -188,6 +192,12 @@ public class AppModule {
return devConfig;
}
@Provides
TestAvatarCreator provideTestAvatarCreator(
TestAvatarCreatorImpl testAvatarCreator) {
return testAvatarCreator;
}
@Provides
SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences()
@@ -251,6 +261,17 @@ public class AppModule {
@Provides
FeatureFlags provideFeatureFlags() {
return () -> IS_DEBUG_BUILD;
return new FeatureFlags() {
@Override
public boolean shouldEnableImageAttachments() {
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return IS_DEBUG_BUILD;
}
};
}
}

View File

@@ -14,6 +14,8 @@ import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import com.bumptech.glide.Glide;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -246,10 +248,13 @@ public class BriarService extends Service {
LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
Glide.get(getApplicationContext()).clearMemory();
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
// TODO investigate if we can clear Glide cache here as well
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.warning("Trim memory: running critically low");
// TODO investigate if we can clear Glide cache here as well
// If we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi();
} else if (LOG.isLoggable(INFO)) {

View File

@@ -67,6 +67,7 @@ import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
import org.briarproject.briar.android.settings.ConfirmAvatarDialogFragment;
import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -239,4 +240,6 @@ public interface ActivityComponent {
void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
}

View File

@@ -16,5 +16,6 @@ public interface RequestCodes {
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
int REQUEST_AVATAR_IMAGE = 15;
}

View File

@@ -1,33 +1,24 @@
package org.briarproject.briar.android.attachment;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri;
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.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.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 androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import static android.graphics.Bitmap.CompressFormat.JPEG;
import static android.graphics.BitmapFactory.decodeStream;
import static java.util.Arrays.asList;
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.AndroidUtils.getSupportedImageContentTypes;
@@ -35,19 +26,16 @@ 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;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreationTask {
private static Logger LOG =
private static final 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 ImageCompressor imageCompressor;
private final GroupId groupId;
private final Collection<Uri> uris;
private final boolean needsSize;
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver,
AttachmentCreator attachmentCreator,
ImageSizeCalculator imageSizeCalculator,
ImageCompressor imageCompressor,
GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager;
this.contentResolver = contentResolver;
this.imageSizeCalculator = imageSizeCalculator;
this.imageCompressor = imageCompressor;
this.groupId = groupId;
this.uris = uris;
this.needsSize = needsSize;
@@ -110,66 +98,19 @@ class AttachmentCreationTask {
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
String uriString = uri.toString();
throw new UnsupportedMimeTypeException("", contentType, uriString);
throw new UnsupportedMimeTypeException(contentType, uri);
}
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
is = compressImage(is, contentType);
contentType = "image/jpeg";
is = imageCompressor
.compressImage(is, contentType);
long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
.addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start);
return h;
}
@VisibleForTesting
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;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -6,7 +6,7 @@ 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.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -10,11 +10,11 @@ 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.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException;
import java.util.ArrayList;
@@ -36,12 +36,12 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
@NotNullByDefault
class AttachmentCreatorImpl implements AttachmentCreator {
private static Logger LOG =
private static final Logger LOG =
getLogger(AttachmentCreatorImpl.class.getName());
private final Application app;
@@ -49,7 +49,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor;
private final MessagingManager messagingManager;
private final AttachmentRetriever retriever;
private final ImageSizeCalculator imageSizeCalculator;
private final ImageCompressor imageCompressor;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
@@ -64,12 +64,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever,
ImageSizeCalculator imageSizeCalculator) {
ImageCompressor imageCompressor) {
this.app = app;
this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager;
this.retriever = retriever;
this.imageSizeCalculator = imageSizeCalculator;
this.imageCompressor = imageCompressor;
}
@Override
@@ -89,7 +89,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, imageSizeCalculator, id,
app.getContentResolver(), this, imageCompressor, id,
uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments());
});

View File

@@ -4,8 +4,9 @@ import android.os.Parcel;
import android.os.Parcelable;
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.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import javax.annotation.concurrent.Immutable;
@@ -78,6 +79,9 @@ public class AttachmentItem implements Parcelable {
}
protected AttachmentItem(Parcel in) {
byte[] groupIdByte = new byte[GroupId.LENGTH];
in.readByteArray(groupIdByte);
GroupId groupId = new GroupId(groupIdByte);
byte[] messageIdByte = new byte[MessageId.LENGTH];
in.readByteArray(messageIdByte);
MessageId messageId = new MessageId(messageIdByte);
@@ -88,7 +92,7 @@ public class AttachmentItem implements Parcelable {
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
state = State.valueOf(requireNonNull(in.readString()));
header = new AttachmentHeader(messageId, mimeType);
header = new AttachmentHeader(groupId, messageId, mimeType);
}
public AttachmentHeader getHeader() {
@@ -142,6 +146,7 @@ public class AttachmentItem implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(header.getGroupId().getBytes());
dest.writeByteArray(header.getMessageId().getBytes());
dest.writeInt(width);
dest.writeInt(height);

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.attachment;
import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -12,16 +12,6 @@ import static org.briarproject.briar.android.attachment.AttachmentDimensions.get
@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());

View File

@@ -4,8 +4,8 @@ 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.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -49,10 +49,10 @@ public interface AttachmentRetriever {
* Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}.
*
* <p>
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData.
*
* <p>
* It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives.

View File

@@ -6,9 +6,12 @@ import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State;
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.android.attachment.media.ImageHelper;
import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.android.attachment.media.Size;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.BufferedInputStream;
@@ -43,7 +46,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
private final Executor dbExecutor;
private final MessagingManager messagingManager;
private final AttachmentReader attachmentReader;
private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator;
private final int defaultSize;
@@ -57,11 +60,10 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
MessagingManager messagingManager,
AttachmentDimensions dimensions, ImageHelper imageHelper,
ImageSizeCalculator imageSizeCalculator) {
AttachmentReader attachmentReader, AttachmentDimensions dimensions,
ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor;
this.messagingManager = messagingManager;
this.attachmentReader = attachmentReader;
this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator;
defaultSize = dimensions.defaultSize;
@@ -75,7 +77,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor
public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException {
return messagingManager.getAttachment(h);
return attachmentReader.getAttachment(h);
}
@Override
@@ -86,13 +88,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) {
// try cache for existing item live data
MutableLiveData<AttachmentItem> liveData;
if (needsSize) liveData = itemsWithSize.get(h.getMessageId());
else {
// try items with size first, as they work as well
liveData = itemsWithSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
MutableLiveData<AttachmentItem> liveData =
itemsWithSize.get(h.getMessageId());
if (!needsSize && liveData == null) {
// check cache for items that don't need the size
liveData = itemsWithoutSize.get(h.getMessageId());
}
// create new live data with LOADING item if cache miss
@@ -131,7 +131,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
// If a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return;
try {
Attachment a = messagingManager.getAttachment(h);
Attachment a = attachmentReader.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item);
@@ -173,7 +173,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
Attachment a;
AttachmentItem item;
try {
a = messagingManager.getAttachment(h);
a = attachmentReader.getAttachment(h);
item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
@@ -210,26 +210,30 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
// calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
if (!size.error) {
Size thumbnailSize =
new Size(defaultSize, defaultSize, size.getMimeType());
if (!size.hasError()) {
thumbnailSize =
getThumbnailSize(size.width, size.height, size.mimeType);
getThumbnailSize(size.getWidth(), size.getHeight(),
size.getMimeType());
}
// get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
boolean hasError = extension == null || size.error;
if (!h.getContentType().equals(size.mimeType)) {
String extension =
imageHelper.getExtensionFromMimeType(size.getMimeType());
boolean hasError = extension == null || size.hasError();
if (!h.getContentType().equals(size.getMimeType())) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Header has different mime type (" +
h.getContentType() + ") than image (" + size.mimeType +
").");
h.getContentType() + ") than image (" +
size.getMimeType() + ").");
}
hasError = true;
}
if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.width, size.height,
extension, thumbnailSize.width, thumbnailSize.height, state);
return new AttachmentItem(h, size.getWidth(), size.getHeight(),
extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
state);
}
private Size getThumbnailSize(int width, int height, String mimeType) {

View File

@@ -1,23 +0,0 @@
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;
}
}

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.android.attachment;
import android.net.Uri;
import java.io.IOException;
public class UnsupportedMimeTypeException extends IOException {
private final String mimeType;
private final Uri uri;
public UnsupportedMimeTypeException(String mimeType, Uri uri) {
this.mimeType = mimeType;
this.uri = uri;
}
public String getMimeType() {
return mimeType;
}
public Uri getUri() {
return uri;
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
public interface ImageCompressor {
/**
* The MIME type of compressed images
*/
String MIME_TYPE = "image/jpeg";
/**
* Load an image from {@code is}, compress it and return an InputStream
* from which the resulting image can be read. The image will be compressed
* as a JPEG image such that it fits into a message.
*
* @param is the stream to read the source image from
* @param contentType the mimetype of the source image such as "image/jpeg"
* as obtained by {@link android.content.ContentResolver#getType(Uri)}
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(InputStream is, String contentType)
throws IOException;
/**
* Compress an image and return an InputStream from which the resulting
* image can be read. The image will be compressed as a JPEG image such that
* it fits into a message.
*
* @param bitmap the source image
* @return a stream from which the resulting image can be read
*/
InputStream compressImage(Bitmap bitmap) throws IOException;
}

View File

@@ -0,0 +1,92 @@
package org.briarproject.briar.android.attachment.media;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import javax.inject.Inject;
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;
import static org.briarproject.briar.api.attachment.MediaConstants.MAX_IMAGE_SIZE;
class ImageCompressorImpl implements ImageCompressor {
private static final Logger LOG =
getLogger(ImageCompressorImpl.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final ImageSizeCalculator imageSizeCalculator;
@Inject
ImageCompressorImpl(ImageSizeCalculator imageSizeCalculator) {
this.imageSizeCalculator = imageSizeCalculator;
}
@Override
public InputStream compressImage(InputStream is, String contentType)
throws IOException {
try {
Bitmap bitmap =
createBitmap(is, contentType, MAX_ATTACHMENT_DIMENSION);
return compressImage(bitmap);
} finally {
tryToClose(is, LOG, WARNING);
}
}
@Override
public InputStream compressImage(Bitmap bitmap) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
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();
}
private Bitmap createBitmap(InputStream is, String contentType, int maxSize)
throws IOException {
is = new BufferedInputStream(is);
Size size = imageSizeCalculator.getSize(is, contentType);
if (size.hasError()) throw new IOException();
if (LOG.isLoggable(INFO))
LOG.info("Original image size: " + size.getWidth() + "x" +
size.getHeight());
int dimension = Math.max(size.getWidth(), size.getHeight());
int inSampleSize = 1;
while (dimension > maxSize) {
inSampleSize *= 2;
dimension /= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("Scaling attachment by factor of " + inSampleSize);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
if (contentType.equals("image/png"))
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = decodeStream(is, null, options);
if (bitmap == null) throw new IOException();
return bitmap;
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import android.graphics.BitmapFactory;
import android.webkit.MimeTypeMap;

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
@NotNullByDefault
public interface ImageSizeCalculator {
/**
* Determine the size of the image that can be read from {@code is}.
*
* @param contentType the mime type of the image. If "image/jpeg" is passed,
* the implementation will try to determine the size from the Exif header
*/
Size getSize(InputStream is, String contentType);
}

View File

@@ -1,9 +1,9 @@
package org.briarproject.briar.android.attachment;
package org.briarproject.briar.android.attachment.media;
import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
import org.briarproject.briar.android.attachment.media.ImageHelper.DecodeResult;
import java.io.IOException;
import java.io.InputStream;
@@ -23,20 +23,21 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class ImageSizeCalculator {
class ImageSizeCalculatorImpl implements ImageSizeCalculator {
private static final Logger LOG =
getLogger(ImageSizeCalculator.class.getName());
getLogger(ImageSizeCalculatorImpl.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final ImageHelper imageHelper;
ImageSizeCalculator(ImageHelper imageHelper) {
ImageSizeCalculatorImpl(ImageHelper imageHelper) {
this.imageHelper = imageHelper;
}
Size getSize(InputStream is, String contentType) {
@Override
public Size getSize(InputStream is, String contentType) {
Size size = new Size();
is = new MarkEnforcingInputStream(is);
is.mark(READ_LIMIT);
@@ -49,7 +50,7 @@ class ImageSizeCalculator {
logException(LOG, WARNING, e);
}
}
if (size.error) {
if (size.hasError()) {
// need to mark again to re-add read limit
is.mark(READ_LIMIT);
try {

View File

@@ -0,0 +1,24 @@
package org.briarproject.briar.android.attachment.media;
import dagger.Module;
import dagger.Provides;
@Module
public class MediaModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculatorImpl(imageHelper);
}
@Provides
ImageCompressor provideImageCompressor(
ImageCompressorImpl imageCompressor) {
return imageCompressor;
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.briar.android.attachment.media;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class Size {
private final int width;
private final int height;
private final String mimeType;
private final boolean error;
public Size(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
public Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getMimeType() {
return mimeType;
}
public boolean hasError() {
return error;
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader;

View File

@@ -20,7 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List;
@@ -156,16 +156,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// do nothing
}
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
@Override
public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here
}
}, getFragmentManager());
input = v.findViewById(R.id.inputText);
}
}

View File

@@ -19,10 +19,10 @@ import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException;
import java.util.List;

View File

@@ -3,14 +3,12 @@ package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable;
import androidx.annotation.NonNull;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
@@ -47,15 +45,6 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
return true;
}
int findItemPosition(ContactId c) {
for (int i = 0; i < getItemCount(); i++) {
I item = getItemAt(i);
if (item != null && item.getContact().getId().equals(c))
return i;
}
return INVALID_POSITION; // Not found
}
public interface OnContactClickListener<I> {
void onItemClick(View view, I item);
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable;
@@ -10,14 +11,17 @@ import javax.annotation.concurrent.Immutable;
public class ContactItem {
private final Contact contact;
private final AuthorInfo authorInfo;
private final boolean connected;
public ContactItem(Contact contact) {
this(contact, false);
public ContactItem(Contact contact, AuthorInfo authorInfo) {
this(contact, authorInfo, false);
}
public ContactItem(Contact contact, boolean connected) {
public ContactItem(Contact contact, AuthorInfo authorInfo,
boolean connected) {
this.contact = contact;
this.authorInfo = authorInfo;
this.connected = connected;
}
@@ -25,6 +29,10 @@ public class ContactItem {
return contact;
}
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() {
return connected;
}

View File

@@ -5,7 +5,6 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -14,9 +13,9 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
@UiThread
@NotNullByDefault
@@ -40,9 +39,7 @@ public class ContactItemViewHolder<I extends ContactItem>
}
protected void bind(I item, @Nullable OnContactClickListener<I> listener) {
Author author = item.getContact().getAuthor();
avatar.setImageDrawable(
new IdenticonDrawable(author.getId().getBytes()));
setAvatar(avatar, item);
name.setText(getContactDisplayName(item.getContact()));
if (bulb != null) {

View File

@@ -5,6 +5,7 @@ import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -48,7 +49,11 @@ public class ContactListAdapter extends
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
if (c1.isConnected() != c2.isConnected()) {
return false;
}
return NullSafety.equals(c1.getAuthorInfo().getAvatarHeader(),
c2.getAuthorInfo().getAvatarHeader());
}
}

View File

@@ -2,8 +2,10 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable;
@@ -16,33 +18,44 @@ public class ContactListItem extends ContactItem
private final long timestamp;
private final int unread;
public ContactListItem(Contact contact, boolean connected,
GroupCount count) {
super(contact, connected);
public ContactListItem(Contact contact, AuthorInfo authorInfo,
boolean connected, GroupCount count) {
super(contact, authorInfo, connected);
this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime();
}
private ContactListItem(Contact contact, boolean connected, boolean empty,
int unread, long timestamp) {
super(contact, connected);
private ContactListItem(Contact contact, AuthorInfo authorInfo,
boolean connected, boolean empty, int unread, long timestamp) {
super(contact, authorInfo, connected);
this.empty = empty;
this.timestamp = timestamp;
this.unread = unread;
}
ContactListItem(ContactListItem item, boolean connected) {
this(item.getContact(), connected, item.empty, item.unread,
item.timestamp);
this(item.getContact(), item.getAuthorInfo(), connected, item.empty,
item.unread, item.timestamp);
}
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
this(item.getContact(), item.isConnected(), false,
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp));
}
/**
* Creates a new copy of the given item with a new avatar
* referenced by the given attachment header.
*/
ContactListItem(ContactListItem item,
AttachmentHeader attachmentHeader) {
this(item.getContact(), new AuthorInfo(item.getAuthorInfo().getStatus(),
item.getAuthorInfo().getAlias(), attachmentHeader),
item.isConnected(), item.empty, item.unread, item.timestamp);
}
boolean isEmpty() {
return empty;
}

View File

@@ -25,10 +25,13 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -55,6 +58,7 @@ class ContactListViewModel extends DbViewModel implements EventListener {
getLogger(ContactListViewModel.class.getName());
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final ConversationManager conversationManager;
private final ConnectionRegistry connectionRegistry;
private final EventBus eventBus;
@@ -71,11 +75,13 @@ class ContactListViewModel extends DbViewModel implements EventListener {
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, TransactionManager db,
AndroidExecutor androidExecutor, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager,
ConnectionRegistry connectionRegistry, EventBus eventBus,
AndroidNotificationManager notificationManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.contactManager = contactManager;
this.authorManager = authorManager;
this.conversationManager = conversationManager;
this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus;
@@ -99,10 +105,11 @@ class ContactListViewModel extends DbViewModel implements EventListener {
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts(txn)) {
ContactId id = c.getId();
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
MessageTracker.GroupCount count =
conversationManager.getGroupCount(txn, id);
boolean connected = connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
contacts.add(new ContactListItem(c, authorInfo, connected, count));
}
Collections.sort(contacts);
logDuration(LOG, "Full load", start);
@@ -133,6 +140,10 @@ class ContactListViewModel extends DbViewModel implements EventListener {
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
} else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
updateItem(a.getContactId(), item -> new ContactListItem(item,
a.getAttachmentHeader()), false);
}
}

View File

@@ -10,6 +10,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.ArrayList;
import java.util.Collection;
@@ -31,11 +33,14 @@ public abstract class ContactSelectorControllerImpl
Logger.getLogger(ContactSelectorControllerImpl.class.getName());
private final ContactManager contactManager;
private final AuthorManager authorManager;
public ContactSelectorControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager) {
LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager) {
super(dbExecutor, lifecycleManager);
this.contactManager = contactManager;
this.authorManager = authorManager;
}
@Override
@@ -45,12 +50,13 @@ public abstract class ContactSelectorControllerImpl
try {
Collection<SelectableContactItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts()) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
// was this contact already selected?
boolean selected = selection.contains(c.getId());
// can this contact be selected?
boolean disabled = isDisabled(g, c);
contacts.add(new SelectableContactItem(c, selected,
disabled));
contacts.add(new SelectableContactItem(c, authorInfo,
selected, disabled));
}
handler.onResult(contacts);
} catch (DbException e) {

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.contactselection;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.NotThreadSafe;
@@ -10,11 +11,12 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotNullByDefault
public class SelectableContactItem extends ContactItem {
private boolean selected, disabled;
private boolean selected;
private final boolean disabled;
public SelectableContactItem(Contact contact, boolean selected,
boolean disabled) {
super(contact);
public SelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected, boolean disabled) {
super(contact, authorInfo);
this.selected = selected;
this.disabled = disabled;
}

View File

@@ -21,7 +21,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static java.util.Objects.requireNonNull;
@@ -59,7 +58,7 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
}
@@ -72,7 +71,8 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
aliasEditLayout = v.findViewById(R.id.aliasEditLayout);
aliasEditText = v.findViewById(R.id.aliasEditText);
Contact contact = requireNonNull(viewModel.getContact().getValue());
Contact contact = requireNonNull(viewModel.getContactItem().getValue())
.getContact();
String alias = contact.getAlias();
aliasEditText.setText(alias);
if (alias != null) aliasEditText.setSelection(alias.length());

View File

@@ -59,6 +59,7 @@ import org.briarproject.briar.android.view.TextAttachmentController.AttachmentLi
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
@@ -71,7 +72,6 @@ import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
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.privategroup.invitation.GroupInvitationManager;
@@ -106,7 +106,6 @@ import androidx.recyclerview.selection.StorageStrategy;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.os.Build.VERSION.SDK_INT;
@@ -134,6 +133,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
@@ -233,10 +233,9 @@ public class ConversationActivity extends BriarActivity
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
observeOnce(viewModel.getContactAuthorId(), this, authorId -> {
requireNonNull(authorId);
toolbarAvatar.setImageDrawable(
new IdenticonDrawable(authorId.getBytes()));
viewModel.getContactItem().observe(this, contactItem -> {
requireNonNull(contactItem);
setAvatar(toolbarAvatar, contactItem);
});
viewModel.getContactDisplayName().observe(this, contactName -> {
requireNonNull(contactName);
@@ -366,7 +365,7 @@ public class ConversationActivity extends BriarActivity
}
});
// enable alias action if available
observeOnce(viewModel.getContact(), this, contact ->
observeOnce(viewModel.getContactItem(), this, contact ->
menu.findItem(R.id.action_set_alias).setEnabled(true));
return super.onCreateOptionsMenu(menu);

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
@@ -27,11 +26,15 @@ import org.briarproject.briar.android.attachment.AttachmentCreator;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -49,8 +52,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import static androidx.lifecycle.Transformations.map;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
@@ -76,6 +79,7 @@ public class ConversationViewModel extends DbViewModel
private final EventBus eventBus;
private final MessagingManager messagingManager;
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever;
@@ -83,11 +87,10 @@ public class ConversationViewModel extends DbViewModel
@Nullable
private ContactId contactId = null;
private final MutableLiveData<Contact> contact = new MutableLiveData<>();
private final LiveData<AuthorId> contactAuthorId =
Transformations.map(contact, c -> c.getAuthor().getId());
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<ContactItem> contactItem =
new MutableLiveData<>();
private final LiveData<String> contactName = map(contactItem, c ->
UiUtils.getContactDisplayName(c.getContact()));
private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>();
@@ -111,6 +114,7 @@ public class ConversationViewModel extends DbViewModel
EventBus eventBus,
MessagingManager messagingManager,
ContactManager contactManager,
AuthorManager authorManager,
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever,
@@ -120,12 +124,13 @@ public class ConversationViewModel extends DbViewModel
this.eventBus = eventBus;
this.messagingManager = messagingManager;
this.contactManager = contactManager;
this.authorManager = authorManager;
this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator;
messagingGroupId = Transformations
.map(contact, c -> messagingManager.getContactGroup(c).getId());
messagingGroupId = map(contactItem, c ->
messagingManager.getContactGroup(c.getContact()).getId());
contactDeleted.setValue(false);
eventBus.addListener(this);
@@ -147,9 +152,33 @@ public class ConversationViewModel extends DbViewModel
runOnDbThread(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId()));
}
} else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Avatar updated");
updateAvatar(a);
}
}
}
@UiThread
private void updateAvatar(AvatarUpdatedEvent a) {
// Make sure that contactItem has been set by the task initiated
// by loadContact() before we update the avatar.
observeForeverOnce(contactItem, oldContactItem -> {
requireNonNull(oldContactItem);
AuthorInfo oldAuthorInfo = oldContactItem.getAuthorInfo();
AuthorInfo newAuthorInfo = new AuthorInfo(oldAuthorInfo.getStatus(),
oldAuthorInfo.getAlias(), a.getAttachmentHeader());
ContactItem newContactItem =
new ContactItem(oldContactItem.getContact(), newAuthorInfo);
contactItem.setValue(newContactItem);
});
}
/**
* Setting the {@link ContactId} automatically triggers loading of other
* data.
@@ -168,7 +197,8 @@ public class ConversationViewModel extends DbViewModel
try {
long start = now();
Contact c = contactManager.getContact(contactId);
contact.postValue(c);
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
contactItem.postValue(new ContactItem(c, authorInfo));
logDuration(LOG, "Loading contact", start);
start = now();
checkFeaturesAndOnboarding(contactId);
@@ -319,12 +349,8 @@ public class ConversationViewModel extends DbViewModel
return attachmentRetriever;
}
LiveData<Contact> getContact() {
return contact;
}
LiveData<AuthorId> getContactAuthorId() {
return contactAuthorId;
LiveData<ContactItem> getContactItem() {
return contactItem;
}
LiveData<String> getContactDisplayName() {

View File

@@ -121,7 +121,7 @@ public class ImageFragment extends Fragment
private void loadImage() {
GlideApp.with(this)
.load(attachment)
.load(attachment.getHeader())
// TODO allow if size < maxTextureSize ?
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE)

View File

@@ -77,7 +77,7 @@ class ImageViewHolder extends ViewHolder {
private void loadImage(AttachmentItem a, Radii r) {
Transformation<Bitmap> transformation = new BriarImageTransformation(r);
GlideApp.with(imageView)
.load(a)
.load(a.getHeader())
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.transform(transformation)

View File

@@ -20,8 +20,8 @@ import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.io.File;
@@ -56,7 +56,7 @@ public class ImageViewModel extends DbViewModel implements EventListener {
private static final Logger LOG = getLogger(ImageViewModel.class.getName());
private final MessagingManager messagingManager;
private final AttachmentReader attachmentReader;
private final EventBus eventBus;
@IoExecutor
private final Executor ioExecutor;
@@ -75,16 +75,14 @@ public class ImageViewModel extends DbViewModel implements EventListener {
private int toolbarTop, toolbarBottom;
@Inject
ImageViewModel(Application application,
MessagingManager messagingManager,
EventBus eventBus,
@DatabaseExecutor Executor dbExecutor,
ImageViewModel(Application application, AttachmentReader attachmentReader,
EventBus eventBus, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.messagingManager = messagingManager;
this.attachmentReader = attachmentReader;
this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
@@ -202,7 +200,7 @@ public class ImageViewModel extends DbViewModel implements EventListener {
runOnDbThread(() -> {
try {
Attachment a =
messagingManager.getAttachment(attachment.getHeader());
attachmentReader.getAttachment(attachment.getHeader());
copyImageFromDb(a, osp, afterCopy);
} catch (DbException e) {
logException(LOG, WARNING, e);

View File

@@ -7,9 +7,9 @@ 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.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentReader;
import java.io.InputStream;
import java.util.concurrent.Executor;
@@ -30,21 +30,22 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
private final static Logger LOG =
getLogger(BriarDataFetcher.class.getName());
private final MessagingManager messagingManager;
private final AttachmentReader attachmentReader;
@DatabaseExecutor
private final Executor dbExecutor;
private final AttachmentItem attachment;
private final AttachmentHeader attachmentHeader;
@Nullable
private volatile InputStream inputStream;
private volatile boolean cancel = false;
@Inject
BriarDataFetcher(MessagingManager messagingManager,
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) {
this.messagingManager = messagingManager;
BriarDataFetcher(AttachmentReader attachmentReader,
@DatabaseExecutor Executor dbExecutor,
AttachmentHeader attachmentHeader) {
this.attachmentReader = attachmentReader;
this.dbExecutor = dbExecutor;
this.attachment = attachment;
this.attachmentHeader = attachmentHeader;
}
@Override
@@ -53,8 +54,7 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
dbExecutor.execute(() -> {
if (cancel) return;
try {
Attachment a =
messagingManager.getAttachment(attachment.getHeader());
Attachment a = attachmentReader.getAttachment(attachmentHeader);
inputStream = a.getStream();
callback.onDataReady(inputStream);
} catch (DbException e) {

View File

@@ -2,8 +2,8 @@ package org.briarproject.briar.android.conversation.glide;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentReader;
import java.util.concurrent.Executor;
@@ -12,19 +12,19 @@ import javax.inject.Inject;
@NotNullByDefault
public class BriarDataFetcherFactory {
private final MessagingManager messagingManager;
private final AttachmentReader attachmentReader;
@DatabaseExecutor
private final Executor dbExecutor;
@Inject
public BriarDataFetcherFactory(MessagingManager messagingManager,
public BriarDataFetcherFactory(AttachmentReader attachmentReader,
@DatabaseExecutor Executor dbExecutor) {
this.messagingManager = messagingManager;
this.attachmentReader = attachmentReader;
this.dbExecutor = dbExecutor;
}
BriarDataFetcher createBriarDataFetcher(AttachmentItem model) {
return new BriarDataFetcher(messagingManager, dbExecutor, model);
BriarDataFetcher createBriarDataFetcher(AttachmentHeader model) {
return new BriarDataFetcher(attachmentReader, dbExecutor, model);
}
}

View File

@@ -10,7 +10,7 @@ import com.bumptech.glide.module.AppGlideModule;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream;
@@ -28,7 +28,7 @@ public final class BriarGlideModule extends AppGlideModule {
BriarApplication app =
(BriarApplication) context.getApplicationContext();
BriarModelLoaderFactory factory = new BriarModelLoaderFactory(app);
registry.prepend(AttachmentItem.class, InputStream.class, factory);
registry.prepend(AttachmentHeader.class, InputStream.class, factory);
}
@Override

View File

@@ -8,7 +8,7 @@ import com.bumptech.glide.signature.ObjectKey;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream;
@@ -17,7 +17,7 @@ import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public final class BriarModelLoader
implements ModelLoader<AttachmentItem, InputStream> {
implements ModelLoader<AttachmentHeader, InputStream> {
@Inject
BriarDataFetcherFactory dataFetcherFactory;
@@ -27,8 +27,8 @@ public final class BriarModelLoader
}
@Override
public LoadData<InputStream> buildLoadData(AttachmentItem model, int width,
int height, Options options) {
public LoadData<InputStream> buildLoadData(AttachmentHeader model,
int width, int height, Options options) {
ObjectKey key = new ObjectKey(model.getMessageId());
BriarDataFetcher dataFetcher =
dataFetcherFactory.createBriarDataFetcher(model);
@@ -36,7 +36,7 @@ public final class BriarModelLoader
}
@Override
public boolean handles(AttachmentItem model) {
public boolean handles(AttachmentHeader model) {
return true;
}
}

View File

@@ -6,13 +6,13 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream;
@NotNullByDefault
class BriarModelLoaderFactory
implements ModelLoaderFactory<AttachmentItem, InputStream> {
implements ModelLoaderFactory<AttachmentHeader, InputStream> {
private final BriarApplication app;
@@ -21,7 +21,7 @@ class BriarModelLoaderFactory
}
@Override
public ModelLoader<AttachmentItem, InputStream> build(
public ModelLoader<AttachmentHeader, InputStream> build(
MultiModelLoaderFactory multiFactory) {
return new BriarModelLoader(app);
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.forum;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.threaded.ThreadItem;
import org.briarproject.briar.api.forum.ForumPostHeader;

View File

@@ -21,6 +21,8 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.ArrayList;
import java.util.List;
@@ -49,10 +51,12 @@ public class ContactChooserFragment extends BaseFragment {
private ContactId contactId;
// Fields that are accessed from background threads must be volatile
volatile Contact c1;
private volatile Contact c1;
@Inject
volatile ContactManager contactManager;
@Inject
volatile AuthorManager authorManager;
@Inject
volatile ConversationManager conversationManager;
@Inject
volatile ConnectionRegistry connectionRegistry;
@@ -123,12 +127,14 @@ public class ContactChooserFragment extends BaseFragment {
if (c.getId().equals(contactId)) {
c1 = c;
} else {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
contacts.add(new ContactListItem(c, authorInfo,
connected, count));
}
}
displayContacts(contacts);

View File

@@ -18,12 +18,15 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List;
import java.util.logging.Logger;
@@ -33,7 +36,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
@@ -43,6 +45,7 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
@MethodsNotNullByDefault
@@ -65,6 +68,8 @@ public class IntroductionMessageFragment extends BaseFragment
@Inject
protected volatile ContactManager contactManager;
@Inject
protected volatile AuthorManager authorManager;
@Inject
protected volatile IntroductionManager introductionManager;
public static IntroductionMessageFragment newInstance(int contactId1,
@@ -137,11 +142,16 @@ public class IntroductionMessageFragment extends BaseFragment
private void prepareToSetUpViews(int contactId1, int contactId2) {
introductionActivity.runOnDbThread(() -> {
try {
Contact c1 = contactManager.getContact(
new ContactId(contactId1));
Contact c2 = contactManager.getContact(
new ContactId(contactId2));
boolean possible = introductionManager.canIntroduce(c1, c2);
Contact contact1 =
contactManager.getContact(new ContactId(contactId1));
Contact contact2 =
contactManager.getContact(new ContactId(contactId2));
AuthorInfo a1 = authorManager.getAuthorInfo(contact1);
AuthorInfo a2 = authorManager.getAuthorInfo(contact2);
boolean possible =
introductionManager.canIntroduce(contact1, contact2);
ContactItem c1 = new ContactItem(contact1, a1);
ContactItem c2 = new ContactItem(contact2, a2);
setUpViews(c1, c2, possible);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -149,20 +159,18 @@ public class IntroductionMessageFragment extends BaseFragment
});
}
private void setUpViews(Contact c1, Contact c2, boolean possible) {
private void setUpViews(ContactItem c1, ContactItem c2, boolean possible) {
introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
contact1 = c1;
contact2 = c2;
contact1 = c1.getContact();
contact2 = c2.getContact();
// set avatars
ui.avatar1.setImageDrawable(new IdenticonDrawable(
c1.getAuthor().getId().getBytes()));
ui.avatar2.setImageDrawable(new IdenticonDrawable(
c2.getAuthor().getId().getBytes()));
setAvatar(ui.avatar1, c1);
setAvatar(ui.avatar2, c2);
// set contact names
ui.contactName1.setText(getContactDisplayName(c1));
ui.contactName2.setText(getContactDisplayName(c2));
ui.contactName1.setText(getContactDisplayName(c1.getContact()));
ui.contactName2.setText(getContactDisplayName(c2.getContact()));
// hide progress bar
ui.progressBar.setVisibility(GONE);

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.privategroup.conversation;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;

View File

@@ -10,7 +10,7 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListe
import androidx.annotation.UiThread;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.OURSELVES;
@UiThread
@NotNullByDefault

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
@@ -59,12 +60,13 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
IdentityManager identityManager, PrivateGroupFactory groupFactory,
AuthorManager authorManager, IdentityManager identityManager,
PrivateGroupFactory groupFactory,
GroupMessageFactory groupMessageFactory,
PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.cryptoExecutor = cryptoExecutor;
this.contactManager = contactManager;
this.identityManager = identityManager;
@@ -176,7 +178,6 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
// Continue
}
}
//noinspection ConstantConditions
handler.onResult(null);
} catch (DbException e) {
logException(LOG, WARNING, e);

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.privategroup.list;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -24,6 +23,8 @@ import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
@@ -64,7 +65,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager;
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final AndroidNotificationManager notificationManager;
private final EventBus eventBus;
@@ -81,12 +82,12 @@ class GroupListViewModel extends DbViewModel implements EventListener {
AndroidExecutor androidExecutor,
PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager,
ContactManager contactManager,
AuthorManager authorManager,
AndroidNotificationManager notificationManager, EventBus eventBus) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager;
this.authorManager = authorManager;
this.notificationManager = notificationManager;
this.eventBus = eventBus;
this.eventBus.addListener(this);
@@ -157,7 +158,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
if (authorInfos.containsKey(authorId)) {
authorInfo = requireNonNull(authorInfos.get(authorId));
} else {
authorInfo = contactManager.getAuthorInfo(txn, authorId);
authorInfo = authorManager.getAuthorInfo(txn, authorId);
authorInfos.put(authorId, authorInfo);
}
GroupCount count = groupManager.getGroupCount(txn, id);

View File

@@ -2,8 +2,8 @@ package org.briarproject.briar.android.privategroup.memberlist;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo.Status;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.privategroup.GroupMember;

View File

@@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread

View File

@@ -14,6 +14,8 @@ import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
@@ -45,17 +47,20 @@ class RevealContactsControllerImpl extends DbControllerImpl
private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager;
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final SettingsManager settingsManager;
@Inject
RevealContactsControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager,
ContactManager contactManager, SettingsManager settingsManager) {
ContactManager contactManager, AuthorManager authorManager,
SettingsManager settingsManager) {
super(dbExecutor, lifecycleManager);
this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager;
this.authorManager = authorManager;
this.settingsManager = settingsManager;
}
@@ -82,11 +87,12 @@ class RevealContactsControllerImpl extends DbControllerImpl
for (GroupMember m : members) {
for (Contact c : contacts) {
if (m.getAuthor().equals(c.getAuthor())) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
boolean disabled = m.getVisibility() != INVISIBLE;
boolean selected =
disabled || selection.contains(c.getId());
items.add(new RevealableContactItem(c, selected, disabled,
m.getVisibility()));
items.add(new RevealableContactItem(c, authorInfo, selected,
disabled, m.getVisibility()));
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.privategroup.reveal;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.contactselection.SelectableContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.privategroup.Visibility;
import javax.annotation.concurrent.NotThreadSafe;
@@ -13,9 +14,9 @@ class RevealableContactItem extends SelectableContactItem {
private final Visibility visibility;
RevealableContactItem(Contact contact, boolean selected,
boolean disabled, Visibility visibility) {
super(contact, selected, disabled);
RevealableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected, boolean disabled, Visibility visibility) {
super(contact, authorInfo, selected, disabled);
this.visibility = visibility;
}

View File

@@ -0,0 +1,91 @@
package org.briarproject.briar.android.settings;
import android.app.Dialog;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConfirmAvatarDialogFragment extends DialogFragment {
final static String TAG = ConfirmAvatarDialogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
private static final String ARG_URI = "uri";
private Uri uri;
static ConfirmAvatarDialogFragment newInstance(Uri uri) {
ConfirmAvatarDialogFragment f = new ConfirmAvatarDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_URI, uri.toString());
f.setArguments(args);
return f;
}
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
}
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
String argUri = requireNonNull(args.getString(ARG_URI));
uri = Uri.parse(argUri);
FragmentActivity activity = requireActivity();
ViewModelProvider provider =
new ViewModelProvider(activity, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
LayoutInflater inflater = LayoutInflater.from(getContext());
final View view =
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
builder.setView(view);
builder.setTitle(R.string.dialog_confirm_profile_picture_title);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.change,
(dialog, id) -> settingsViewModel.setAvatar(uri));
ImageView imageView = view.findViewById(R.id.image);
imageView.setImageURI(uri);
TextView textViewUserName = view.findViewById(R.id.username);
settingsViewModel.getOwnIdentityInfo().observe(activity,
us -> textViewUserName.setText(us.getLocalAuthor().getName()));
return builder.create();
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.android.settings;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class OwnIdentityInfo {
private final LocalAuthor localAuthor;
private final AuthorInfo authorInfo;
OwnIdentityInfo(LocalAuthor localAuthor, AuthorInfo authorInfo) {
this.localAuthor = localAuthor;
this.authorInfo = authorInfo;
}
LocalAuthor getLocalAuthor() {
return localAuthor;
}
AuthorInfo getAuthorInfo() {
return authorInfo;
}
}

View File

@@ -1,16 +1,39 @@
package org.briarproject.briar.android.settings;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.AuthorView;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.ViewModelProvider;
import de.hdodenhof.circleimageview.CircleImageView;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE;
public class SettingsActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
@Inject
FeatureFlags featureFlags;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
@@ -22,6 +45,37 @@ public class SettingsActivity extends BriarActivity {
}
setContentView(R.layout.activity_settings);
if (featureFlags.shouldEnableProfilePictures()) {
ViewModelProvider provider =
new ViewModelProvider(this, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
TextView textViewUserName = findViewById(R.id.username);
CircleImageView imageViewAvatar =
findViewById(R.id.avatarImage);
settingsViewModel.getOwnIdentityInfo().observe(this, us -> {
textViewUserName.setText(us.getLocalAuthor().getName());
AuthorView.setAvatar(imageViewAvatar,
us.getLocalAuthor().getId(), us.getAuthorInfo());
});
settingsViewModel.getSetAvatarFailed()
.observeEvent(this, failed -> {
if (failed) {
Toast.makeText(this,
R.string.change_profile_picture_failed_message,
LENGTH_LONG).show();
}
});
View avatarGroup = findViewById(R.id.avatarGroup);
avatarGroup.setOnClickListener(e -> selectAvatarImage());
} else {
View view = findViewById(R.id.avatarGroup);
view.setVisibility(View.GONE);
}
}
@Override
@@ -37,4 +91,31 @@ public class SettingsActivity extends BriarActivity {
}
return false;
}
private void selectAvatarImage() {
Intent intent = UiUtils.createSelectImageIntent(false);
startActivityForResult(intent, REQUEST_AVATAR_IMAGE);
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_AVATAR_IMAGE && result == RESULT_OK) {
onAvatarImageReceived(data);
}
}
private void onAvatarImageReceived(@Nullable Intent resultData) {
if (resultData == null) return;
Uri uri = resultData.getData();
if (uri == null) return;
ConfirmAvatarDialogFragment dialog =
ConfirmAvatarDialogFragment.newInstance(uri);
dialog.show(getSupportFragmentManager(),
ConfirmAvatarDialogFragment.TAG);
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.settings;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class SettingsModule {
@Binds
@IntoMap
@ViewModelKey(SettingsViewModel.class)
abstract ViewModel bindSettingsViewModel(
SettingsViewModel settingsViewModel);
}

View File

@@ -0,0 +1,134 @@
package org.briarproject.briar.android.settings;
import android.app.Application;
import android.content.ContentResolver;
import android.net.Uri;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.UnsupportedMimeTypeException;
import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.avatar.AvatarManager;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Arrays.asList;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class SettingsViewModel extends AndroidViewModel {
private final static Logger LOG =
getLogger(SettingsViewModel.class.getName());
private final IdentityManager identityManager;
private final AvatarManager avatarManager;
private final AuthorManager authorManager;
private final ImageCompressor imageCompressor;
@IoExecutor
private final Executor ioExecutor;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<OwnIdentityInfo> ownIdentityInfo =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> setAvatarFailed =
new MutableLiveEvent<>();
@Inject
SettingsViewModel(Application application,
IdentityManager identityManager,
AvatarManager avatarManager,
AuthorManager authorManager,
ImageCompressor imageCompressor,
@IoExecutor Executor ioExecutor,
@DatabaseExecutor Executor dbExecutor) {
super(application);
this.identityManager = identityManager;
this.imageCompressor = imageCompressor;
this.avatarManager = avatarManager;
this.authorManager = authorManager;
this.ioExecutor = ioExecutor;
this.dbExecutor = dbExecutor;
loadOwnIdentityInfo();
}
LiveData<OwnIdentityInfo> getOwnIdentityInfo() {
return ownIdentityInfo;
}
public LiveEvent<Boolean> getSetAvatarFailed() {
return setAvatarFailed;
}
private void loadOwnIdentityInfo() {
dbExecutor.execute(() -> {
try {
LocalAuthor localAuthor = identityManager.getLocalAuthor();
AuthorInfo authorInfo = authorManager.getMyAuthorInfo();
ownIdentityInfo.postValue(
new OwnIdentityInfo(localAuthor, authorInfo));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setAvatar(Uri uri) {
ioExecutor.execute(() -> {
try {
trySetAvatar(uri);
} catch (IOException e) {
logException(LOG, WARNING, e);
setAvatarFailed.postEvent(true);
}
});
}
private void trySetAvatar(Uri uri) throws IOException {
ContentResolver contentResolver =
getApplication().getContentResolver();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
throw new UnsupportedMimeTypeException(contentType, uri);
}
InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException(
"ContentResolver returned null when opening InputStream");
InputStream compressed = imageCompressor.compressImage(is, contentType);
dbExecutor.execute(() -> {
try {
avatarManager.addAvatar(ImageCompressor.MIME_TYPE, compressed);
loadOwnIdentityInfo();
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
setAvatarFailed.postEvent(true);
}
});
}
}

View File

@@ -15,7 +15,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.LargeTextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List;
@@ -62,6 +62,7 @@ public abstract class BaseMessageFragment extends BaseFragment
@StringRes
protected abstract int getButtonText();
@StringRes
protected abstract int getHintText();

View File

@@ -15,6 +15,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -42,9 +43,10 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
@Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager;
this.clock = clock;

View File

@@ -15,6 +15,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -42,9 +43,10 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
@Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager;
this.clock = clock;

View File

@@ -20,6 +20,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
import java.util.ArrayList;
@@ -42,8 +44,12 @@ import static org.briarproject.bramble.util.LogUtils.logException;
abstract class SharingStatusActivity extends BriarActivity
implements EventListener {
// objects accessed from background thread need to be volatile
@Inject
ConnectionRegistry connectionRegistry;
volatile AuthorManager authorManager;
@Inject
volatile ConnectionRegistry connectionRegistry;
@Inject
EventBus eventBus;
@@ -134,8 +140,9 @@ abstract class SharingStatusActivity extends BriarActivity
try {
List<ContactItem> contactItems = new ArrayList<>();
for (Contact c : getSharedWith()) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
boolean online = connectionRegistry.isConnected(c.getId());
ContactItem item = new ContactItem(c, online);
ContactItem item = new ContactItem(c, authorInfo, online);
contactItems.add(item);
}
displaySharedWith(contactItems);

View File

@@ -24,20 +24,9 @@ public class TestDataActivity extends BriarActivity {
@Inject
TestDataCreator testDataCreator;
private TextView contactsTextView;
private SeekBar contactsSeekBar;
private TextView messagesTextView;
private SeekBar messagesSeekBar;
private TextView blogPostsTextView;
private SeekBar blogPostsSeekBar;
private TextView forumsTextView;
private SeekBar forumsSeekBar;
private TextView forumPostsTextView;
private SeekBar forumPostsSeekBar;
private TextView contactsTextView, forumsTextView;
private SeekBar contactsSeekBar, messagesSeekBar, avatarsSeekBar,
blogPostsSeekBar, forumsSeekBar, forumPostsSeekBar;
@Override
public void onCreate(Bundle bundle) {
@@ -51,12 +40,15 @@ public class TestDataActivity extends BriarActivity {
setContentView(R.layout.activity_test_data);
contactsTextView = findViewById(R.id.textViewContactsSb);
messagesTextView = findViewById(R.id.textViewMessagesSb);
blogPostsTextView = findViewById(R.id.TextViewBlogPostsSb);
TextView messagesTextView = findViewById(R.id.textViewMessagesSb);
TextView avatarsTextView = findViewById(R.id.textViewAvatarsSb);
TextView blogPostsTextView = findViewById(R.id.TextViewBlogPostsSb);
forumsTextView = findViewById(R.id.TextViewForumsSb);
forumPostsTextView = findViewById(R.id.TextViewForumMessagesSb);
TextView forumPostsTextView =
findViewById(R.id.TextViewForumMessagesSb);
contactsSeekBar = findViewById(R.id.seekBarContacts);
messagesSeekBar = findViewById(R.id.seekBarMessages);
avatarsSeekBar = findViewById(R.id.seekBarAvatars);
blogPostsSeekBar = findViewById(R.id.seekBarBlogPosts);
forumsSeekBar = findViewById(R.id.seekBarForums);
forumPostsSeekBar = findViewById(R.id.seekBarForumMessages);
@@ -78,40 +70,12 @@ public class TestDataActivity extends BriarActivity {
}
});
messagesSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar,
int progress, boolean fromUser) {
messagesTextView.setText(String.valueOf(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
blogPostsSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar,
int progress, boolean fromUser) {
blogPostsTextView.setText(String.valueOf(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
messagesSeekBar.setOnSeekBarChangeListener(
new OnSeekBarChangeUpdateProgress(messagesTextView));
avatarsSeekBar.setOnSeekBarChangeListener(
new OnSeekBarChangeUpdateProgress(avatarsTextView));
blogPostsSeekBar.setOnSeekBarChangeListener(
new OnSeekBarChangeUpdateProgress(blogPostsTextView));
forumsSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
@@ -129,23 +93,8 @@ public class TestDataActivity extends BriarActivity {
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
forumPostsSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar,
int progress, boolean fromUser) {
forumPostsTextView.setText(String.valueOf(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
forumPostsSeekBar.setOnSeekBarChangeListener(
new OnSeekBarChangeUpdateProgress(forumPostsTextView));
findViewById(R.id.buttonCreateTestData).setOnClickListener(
v -> createTestData());
@@ -153,8 +102,9 @@ public class TestDataActivity extends BriarActivity {
private void createTestData() {
testDataCreator.createTestData(contactsSeekBar.getProgress() + 1,
messagesSeekBar.getProgress(), blogPostsSeekBar.getProgress(),
forumsSeekBar.getProgress(), forumPostsSeekBar.getProgress());
messagesSeekBar.getProgress(), avatarsSeekBar.getProgress(),
blogPostsSeekBar.getProgress(), forumsSeekBar.getProgress(),
forumPostsSeekBar.getProgress());
Intent intent = new Intent(this, ENTRY_ACTIVITY);
intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
@@ -174,4 +124,28 @@ public class TestDataActivity extends BriarActivity {
}
return false;
}
private static class OnSeekBarChangeUpdateProgress
implements OnSeekBarChangeListener {
private final TextView textView;
private OnSeekBarChangeUpdateProgress(TextView textView) {
this.textView = textView;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
textView.setText(String.valueOf(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
}
}

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.MessageTree.MessageNode;

View File

@@ -27,8 +27,8 @@ import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.UnreadMessageButton;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.client.NamedGroup;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection;
import java.util.List;

View File

@@ -57,7 +57,12 @@ import androidx.lifecycle.Observer;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT;
@@ -89,6 +94,7 @@ import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
@@ -250,6 +256,18 @@ public class UiUtils {
};
}
public static Intent createSelectImageIntent(boolean allowMultiple) {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)
intent.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
if (allowMultiple && SDK_INT >= 18)
intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
return intent;
}
public static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text)

View File

@@ -8,10 +8,15 @@ import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.Nullable;
@@ -24,10 +29,10 @@ import im.delight.android.identicons.IdenticonDrawable;
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.graphics.Typeface.BOLD;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.NONE;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.OURSELVES;
@UiThread
public class AuthorView extends ConstraintLayout {
@@ -74,8 +79,7 @@ public class AuthorView extends ConstraintLayout {
public void setAuthor(Author author, AuthorInfo authorInfo) {
authorName
.setText(getContactDisplayName(author, authorInfo.getAlias()));
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
avatar.setImageDrawable(d);
setAvatar(avatar, author.getId(), authorInfo);
if (authorInfo.getStatus() != NONE) {
trustIndicator.setTrustLevel(authorInfo.getStatus());
@@ -94,6 +98,27 @@ public class AuthorView extends ConstraintLayout {
requestLayout();
}
public static void setAvatar(ImageView v, AuthorId id, AuthorInfo info) {
IdenticonDrawable identicon = new IdenticonDrawable(id.getBytes());
if (info.getAvatarHeader() == null) {
GlideApp.with(v)
.clear(v);
v.setImageDrawable(identicon);
} else {
GlideApp.with(v)
.load(info.getAvatarHeader())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(identicon)
.into(v)
.waitForLayout();
}
}
public static void setAvatar(ImageView v, ContactItem contactItem) {
AuthorId authorId = contactItem.getContact().getAuthor().getId();
setAvatar(v, authorId, contactItem.getAuthorInfo());
}
public void setDate(long date) {
this.date.setText(UiUtils.formatDate(getContext(), date));
@@ -117,10 +142,10 @@ public class AuthorView extends ConstraintLayout {
/**
* Styles this view for a different persona.
*
* <p>
* Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar
* and override the one set by
* {@link AuthorView#setAuthor(Author, AuthorInfo)}.
* and override the one set by
* {@link AuthorView#setAuthor(Author, AuthorInfo)}.
*/
public void setPersona(int persona) {
switch (persona) {

View File

@@ -46,7 +46,7 @@ class ImagePreviewViewHolder extends ViewHolder {
.clear(imageView);
} else {
GlideApp.with(imageView)
.load(item.getItem())
.load(item.getItem().getHeader())
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.downsample(FIT_CENTER)

View File

@@ -14,6 +14,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import java.util.ArrayList;
@@ -29,18 +30,12 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.customview.view.AbsSavedState.EMPTY_STATE;
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
@@ -120,26 +115,15 @@ public class TextAttachmentController extends TextSendController
builder.show();
return;
}
Intent intent = getAttachFileIntent();
Intent intent = UiUtils.createSelectImageIntent(true);
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
attachmentListener.onAttachImage(intent);
}
}
private Intent getAttachFileIntent() {
Intent intent = new Intent(SDK_INT >= 19 ?
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
intent.setType("image/*");
intent.addCategory(CATEGORY_OPENABLE);
if (SDK_INT >= 19)
intent.putExtra(EXTRA_MIME_TYPES, getSupportedImageContentTypes());
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
return intent;
}
/**
* This is called with the result Intent
* returned by the Activity started with {@link #getAttachFileIntent()}.
* This is called with the result Intent returned by the Activity started
* with {@link UiUtils#createSelectImageIntent(boolean)}.
* <p>
* This method must be called at most once per call to
* {@link AttachmentListener#onAttachImage(Intent)}.

Some files were not shown because too many files have changed in this diff Show More