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-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-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 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-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method> </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 { public interface FeatureFlags {
boolean shouldEnableImageAttachments(); 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.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; 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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -220,16 +219,6 @@ public interface ContactManager {
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId) boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException; 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 { interface ContactHook {
/** /**

View File

@@ -29,4 +29,12 @@ public class NullSafety {
public static void requireNull(@Nullable Object o) { public static void requireNull(@Nullable Object o) {
if (o != null) throw new AssertionError(); 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.event.EventListener;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; 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.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.KeyManager; 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.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.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; import static org.briarproject.bramble.util.StringUtils.toUtf8;
@ThreadSafe @ThreadSafe
@@ -261,25 +255,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
db.removeContact(txn, c); 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 @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof PendingContactStateChangedEvent) { if (e instanceof PendingContactStateChangedEvent) {

View File

@@ -15,6 +15,9 @@ import org.briarproject.bramble.api.system.Clock;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH; 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.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -43,9 +46,9 @@ class TransportPropertyValidator extends BdfMessageValidator {
clientHelper.parseAndValidateTransportProperties(dictionary); clientHelper.parseAndValidateTransportProperties(dictionary);
// Return the metadata // Return the metadata
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put("transportId", transportId); meta.put(MSG_KEY_TRANSPORT_ID, transportId);
meta.put("version", version); meta.put(MSG_KEY_VERSION, version);
meta.put("local", false); meta.put(MSG_KEY_LOCAL, false);
return new BdfMessageContext(meta); 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.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; 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.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; 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.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.HandshakeLinkConstants.BASE32_LINK_BYTES;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; 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.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.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
import static org.briarproject.bramble.test.TestUtils.getAuthor; 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.getRandomBase32String;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ContactManagerImplTest extends BrambleMockTestCase { public class ContactManagerImplTest extends BrambleMockTestCase {
@@ -212,75 +204,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
assertTrue(contactManager.contactExists(remote.getId(), local)); 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 @Test
public void testGetHandshakeLink() throws Exception { public void testGetHandshakeLink() throws Exception {
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);

View File

@@ -22,6 +22,17 @@ public class BrambleCoreIntegrationTestModule {
@Provides @Provides
FeatureFlags provideFeatureFlags() { 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.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0' 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") { implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support' exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it 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.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest; import org.briarproject.briar.android.navdrawer.NavDrawerActivityTest;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -15,6 +16,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.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; package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.api.messaging.AttachmentHeader; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.InputStream; import java.io.InputStream;
import java.util.Random; import java.util.Random;
import javax.inject.Inject;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext; import static androidx.test.InstrumentationRegistry.getContext;
@@ -24,16 +29,28 @@ public class AttachmentRetrieverIntegrationTest {
private final AttachmentDimensions dimensions = new AttachmentDimensions( private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300 100, 50, 200, 75, 300
); );
private final GroupId groupId = new GroupId(getRandomId());
private final MessageId msgId = new MessageId(getRandomId()); private final MessageId msgId = new MessageId(getRandomId());
private final ImageHelper imageHelper = new ImageHelperImpl(); @Inject
private final AttachmentRetriever retriever = ImageHelper imageHelper;
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper, @Inject
new ImageSizeCalculator(imageHelper)); 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 @Test
public void testSmallJpegImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -49,7 +66,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testBigJpegImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -65,7 +82,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallPngImage() throws Exception { 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"); InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -81,7 +98,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testUberGif() throws Exception { 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"); InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -96,7 +113,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testLottaPixels() throws Exception { 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"); InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -111,7 +128,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testImageIoCrash() throws Exception { 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"); InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -126,7 +143,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testGimpCrash() throws Exception { 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"); InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -141,7 +158,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testOptiPngAfl() throws Exception { 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"); InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -156,7 +173,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testLibrawError() throws Exception { 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"); InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -165,7 +182,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallAnimatedGifMaxDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -180,7 +197,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallAnimatedGifHugeDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -195,7 +212,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testSmallGifLargeDimensions() throws Exception { 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"); InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -210,7 +227,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testHighError() throws Exception { 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"); InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); AttachmentItem item = retriever.createAttachmentItem(a, true);
@@ -225,7 +242,7 @@ public class AttachmentRetrieverIntegrationTest {
@Test @Test
public void testWideError() throws Exception { 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"); InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is); Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true); 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -9,8 +9,13 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class AttachmentCreationTaskTest public class ImageCompressorTest
extends AbstractAttachmentCreationTaskTest { extends AbstractImageCompressorTest {
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Test @Test
public void testCompressSmallPng() throws Exception { 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -17,11 +17,16 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class PngSuiteAttachmentCreationTaskTest public class PngSuiteImageCompressorTest
extends AbstractAttachmentCreationTaskTest { extends AbstractImageCompressorTest {
private static final Logger LOG = private static final Logger LOG =
getLogger(PngSuiteAttachmentCreationTaskTest.class.getName()); getLogger(PngSuiteImageCompressorTest.class.getName());
@Override
protected void inject(AbstractImageCompressorComponent component) {
component.inject(this);
}
@Parameters @Parameters
public static Iterable<String> data() throws IOException { public static Iterable<String> data() throws IOException {
@@ -34,14 +39,14 @@ public class PngSuiteAttachmentCreationTaskTest
private final String filename; private final String filename;
public PngSuiteAttachmentCreationTaskTest(String filename) { public PngSuiteImageCompressorTest(String filename) {
this.filename = filename; this.filename = filename;
} }
@Test @Test
public void testPngSuiteCompress() throws Exception { public void testPngSuiteCompress() throws Exception {
assumeTrue(isOptionalTestEnabled( assumeTrue(isOptionalTestEnabled(
PngSuiteAttachmentCreationTaskTest.class)); PngSuiteImageCompressorTest.class));
LOG.info("Testing " + filename); LOG.info("Testing " + filename);
if (filename.startsWith("x")) { if (filename.startsWith("x")) {
try { 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.bramble.account.BriarAccountModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest; import org.briarproject.briar.android.conversation.ConversationActivityScreenshotTest;
import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest; import org.briarproject.briar.android.settings.SettingsActivityScreenshotTest;
@@ -16,6 +17,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,
@@ -26,6 +28,7 @@ public interface BriarUiTestComponent extends AndroidComponent {
void inject(SetupDataTest test); void inject(SetupDataTest test);
void inject(ConversationActivityScreenshotTest test); void inject(ConversationActivityScreenshotTest test);
void inject(SettingsActivityScreenshotTest test); void inject(SettingsActivityScreenshotTest test);
} }

View File

@@ -116,7 +116,7 @@ public class SetupDataTest extends ScreenshotTest {
throws DbException { throws DbException {
Context ctx = getApplicationContext(); Context ctx = getApplicationContext();
String bobName = ctx.getString(R.string.screenshot_bob); String bobName = ctx.getString(R.string.screenshot_bob);
Contact bob = testDataCreator.addContact(bobName); Contact bob = testDataCreator.addContact(bobName, true);
// TODO add messages // 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.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; 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.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.view.EmojiTextInputView; 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.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor; 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.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager; 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.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager; 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.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -71,7 +74,8 @@ import dagger.Component;
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,
AppModule.class, AppModule.class,
AttachmentModule.class AttachmentModule.class,
MediaModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons, extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
@@ -94,6 +98,10 @@ public interface AndroidComponent
IdentityManager identityManager(); IdentityManager identityManager();
AttachmentReader attachmentReader();
AuthorManager authorManager();
PluginManager pluginManager(); PluginManager pluginManager();
EventBus eventBus(); 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.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule; 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.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule; 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.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager; import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import java.io.File; import java.io.File;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@@ -72,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
LoginModule.class, LoginModule.class,
NavDrawerModule.class, NavDrawerModule.class,
ViewModelModule.class, ViewModelModule.class,
SettingsModule.class,
DevReportModule.class, DevReportModule.class,
ContactListModule.class, ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory // below need to be within same scope as ViewModelProvider.Factory
@@ -188,6 +192,12 @@ public class AppModule {
return devConfig; return devConfig;
} }
@Provides
TestAvatarCreator provideTestAvatarCreator(
TestAvatarCreatorImpl testAvatarCreator) {
return testAvatarCreator;
}
@Provides @Provides
SharedPreferences provideSharedPreferences(Application app) { SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences() // FIXME unify this with getDefaultSharedPreferences()
@@ -251,6 +261,17 @@ public class AppModule {
@Provides @Provides
FeatureFlags provideFeatureFlags() { 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.Binder;
import android.os.IBinder; import android.os.IBinder;
import com.bumptech.glide.Glide;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; 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"); LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) { } else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low"); LOG.info("Trim memory: running moderately low");
Glide.get(getApplicationContext()).clearMemory();
} else if (level == TRIM_MEMORY_RUNNING_LOW) { } else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("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) { } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.warning("Trim memory: running critically low"); 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 we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi(); if (app.isRunningInBackground()) hideUi();
} else if (LOG.isLoggable(INFO)) { } 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.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity; import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment; 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.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity; import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -239,4 +240,6 @@ public interface ActivityComponent {
void inject(CrashFragment crashFragment); void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
} }

View File

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

View File

@@ -1,33 +1,24 @@
package org.briarproject.briar.android.attachment; package org.briarproject.briar.android.attachment;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;
import android.net.Uri; import android.net.Uri;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; 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.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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.Nullable; 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.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; 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.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@NotNullByDefault @NotNullByDefault
class AttachmentCreationTask { class AttachmentCreationTask {
private static Logger LOG = private static final Logger LOG =
getLogger(AttachmentCreationTask.class.getName()); getLogger(AttachmentCreationTask.class.getName());
private static final int MAX_ATTACHMENT_DIMENSION = 1000;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
private final ImageSizeCalculator imageSizeCalculator; private final ImageCompressor imageCompressor;
private final GroupId groupId; private final GroupId groupId;
private final Collection<Uri> uris; private final Collection<Uri> uris;
private final boolean needsSize; private final boolean needsSize;
@@ -59,11 +47,11 @@ class AttachmentCreationTask {
AttachmentCreationTask(MessagingManager messagingManager, AttachmentCreationTask(MessagingManager messagingManager,
ContentResolver contentResolver, ContentResolver contentResolver,
AttachmentCreator attachmentCreator, AttachmentCreator attachmentCreator,
ImageSizeCalculator imageSizeCalculator, ImageCompressor imageCompressor,
GroupId groupId, Collection<Uri> uris, boolean needsSize) { GroupId groupId, Collection<Uri> uris, boolean needsSize) {
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contentResolver = contentResolver; this.contentResolver = contentResolver;
this.imageSizeCalculator = imageSizeCalculator; this.imageCompressor = imageCompressor;
this.groupId = groupId; this.groupId = groupId;
this.uris = uris; this.uris = uris;
this.needsSize = needsSize; this.needsSize = needsSize;
@@ -110,66 +98,19 @@ class AttachmentCreationTask {
String contentType = contentResolver.getType(uri); String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type"); if (contentType == null) throw new IOException("null content type");
if (!asList(getSupportedImageContentTypes()).contains(contentType)) { if (!asList(getSupportedImageContentTypes()).contains(contentType)) {
String uriString = uri.toString(); throw new UnsupportedMimeTypeException(contentType, uri);
throw new UnsupportedMimeTypeException("", contentType, uriString);
} }
InputStream is = contentResolver.openInputStream(uri); InputStream is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException(); if (is == null) throw new IOException();
is = compressImage(is, contentType); is = imageCompressor
contentType = "image/jpeg"; .compressImage(is, contentType);
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
AttachmentHeader h = messagingManager AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is); .addLocalAttachment(groupId, timestamp,
ImageCompressor.MIME_TYPE, is);
tryToClose(is, LOG, WARNING); tryToClose(is, LOG, WARNING);
logDuration(LOG, "Storing attachment", start); logDuration(LOG, "Storing attachment", start);
return h; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageCompressor;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.FileTooBigException; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.attachment.FileTooBigException;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.jsoup.UnsupportedMimeTypeException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; 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.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR; import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; 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 @NotNullByDefault
class AttachmentCreatorImpl implements AttachmentCreator { class AttachmentCreatorImpl implements AttachmentCreator {
private static Logger LOG = private static final Logger LOG =
getLogger(AttachmentCreatorImpl.class.getName()); getLogger(AttachmentCreatorImpl.class.getName());
private final Application app; private final Application app;
@@ -49,7 +49,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
private final Executor ioExecutor; private final Executor ioExecutor;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final AttachmentRetriever retriever; private final AttachmentRetriever retriever;
private final ImageSizeCalculator imageSizeCalculator; private final ImageCompressor imageCompressor;
private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<Uri> uris = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<AttachmentItemResult> itemResults = private final CopyOnWriteArrayList<AttachmentItemResult> itemResults =
@@ -64,12 +64,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Inject @Inject
AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor, AttachmentCreatorImpl(Application app, @IoExecutor Executor ioExecutor,
MessagingManager messagingManager, AttachmentRetriever retriever, MessagingManager messagingManager, AttachmentRetriever retriever,
ImageSizeCalculator imageSizeCalculator) { ImageCompressor imageCompressor) {
this.app = app; this.app = app;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.retriever = retriever; this.retriever = retriever;
this.imageSizeCalculator = imageSizeCalculator; this.imageCompressor = imageCompressor;
} }
@Override @Override
@@ -89,7 +89,7 @@ class AttachmentCreatorImpl implements AttachmentCreator {
if (id == null) throw new IllegalStateException(); if (id == null) throw new IllegalStateException();
boolean needsSize = uris.size() == 1; boolean needsSize = uris.size() == 1;
task = new AttachmentCreationTask(messagingManager, task = new AttachmentCreationTask(messagingManager,
app.getContentResolver(), this, imageSizeCalculator, id, app.getContentResolver(), this, imageCompressor, id,
uris, needsSize); uris, needsSize);
ioExecutor.execute(() -> task.storeAttachments()); ioExecutor.execute(() -> task.storeAttachments());
}); });

View File

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

View File

@@ -3,7 +3,7 @@ package org.briarproject.briar.android.attachment;
import android.net.Uri; import android.net.Uri;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.Collection;
import java.util.List; import java.util.List;

View File

@@ -12,16 +12,6 @@ import static org.briarproject.briar.android.attachment.AttachmentDimensions.get
@Module @Module
public class AttachmentModule { public class AttachmentModule {
@Provides
ImageHelper provideImageHelper(ImageHelperImpl imageHelper) {
return imageHelper;
}
@Provides
ImageSizeCalculator provideImageSizeCalculator(ImageHelper imageHelper) {
return new ImageSizeCalculator(imageHelper);
}
@Provides @Provides
AttachmentDimensions provideAttachmentDimensions(Application app) { AttachmentDimensions provideAttachmentDimensions(Application app) {
return getAttachmentDimensions(app.getResources()); 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.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -49,10 +49,10 @@ public interface AttachmentRetriever {
* Loads an {@link AttachmentItem} * Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent} * that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}. * and notifies the associated {@link LiveData}.
* * <p>
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)} * Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData. * first to get the LiveData.
* * <p>
* It is possible that no LiveData is available, * It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet. * because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives. * 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State; import org.briarproject.briar.android.attachment.AttachmentItem.State;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.android.attachment.media.ImageHelper;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.android.attachment.media.ImageSizeCalculator;
import org.briarproject.briar.api.messaging.MessagingManager; 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 org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
@@ -43,7 +46,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
private final MessagingManager messagingManager; private final AttachmentReader attachmentReader;
private final ImageHelper imageHelper; private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator; private final ImageSizeCalculator imageSizeCalculator;
private final int defaultSize; private final int defaultSize;
@@ -57,11 +60,10 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@Inject @Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor, AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
MessagingManager messagingManager, AttachmentReader attachmentReader, AttachmentDimensions dimensions,
AttachmentDimensions dimensions, ImageHelper imageHelper, ImageHelper imageHelper, ImageSizeCalculator imageSizeCalculator) {
ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.messagingManager = messagingManager; this.attachmentReader = attachmentReader;
this.imageHelper = imageHelper; this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator; this.imageSizeCalculator = imageSizeCalculator;
defaultSize = dimensions.defaultSize; defaultSize = dimensions.defaultSize;
@@ -75,7 +77,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
@DatabaseExecutor @DatabaseExecutor
public Attachment getMessageAttachment(AttachmentHeader h) public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException { throws DbException {
return messagingManager.getAttachment(h); return attachmentReader.getAttachment(h);
} }
@Override @Override
@@ -86,13 +88,11 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
boolean needsSize = headers.size() == 1; boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) { for (AttachmentHeader h : headers) {
// try cache for existing item live data // try cache for existing item live data
MutableLiveData<AttachmentItem> liveData; MutableLiveData<AttachmentItem> liveData =
if (needsSize) liveData = itemsWithSize.get(h.getMessageId()); itemsWithSize.get(h.getMessageId());
else { if (!needsSize && liveData == null) {
// try items with size first, as they work as well // check cache for items that don't need the size
liveData = itemsWithSize.get(h.getMessageId()); liveData = itemsWithoutSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
} }
// create new live data with LOADING item if cache miss // 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 a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return; if (itemsWithSize.containsKey(h.getMessageId())) return;
try { try {
Attachment a = messagingManager.getAttachment(h); Attachment a = attachmentReader.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true); AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData = MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item); new MutableLiveData<>(item);
@@ -173,7 +173,7 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
Attachment a; Attachment a;
AttachmentItem item; AttachmentItem item;
try { try {
a = messagingManager.getAttachment(h); a = attachmentReader.getAttachment(h);
item = createAttachmentItem(a, needsSize); item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) { } catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet"); LOG.info("Attachment not received yet");
@@ -210,26 +210,30 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) { private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
// calculate thumbnail size // calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType); Size thumbnailSize =
if (!size.error) { new Size(defaultSize, defaultSize, size.getMimeType());
if (!size.hasError()) {
thumbnailSize = thumbnailSize =
getThumbnailSize(size.width, size.height, size.mimeType); getThumbnailSize(size.getWidth(), size.getHeight(),
size.getMimeType());
} }
// get file extension // get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType); String extension =
boolean hasError = extension == null || size.error; imageHelper.getExtensionFromMimeType(size.getMimeType());
if (!h.getContentType().equals(size.mimeType)) { boolean hasError = extension == null || size.hasError();
if (!h.getContentType().equals(size.getMimeType())) {
if (LOG.isLoggable(WARNING)) { if (LOG.isLoggable(WARNING)) {
LOG.warning("Header has different mime type (" + LOG.warning("Header has different mime type (" +
h.getContentType() + ") than image (" + size.mimeType + h.getContentType() + ") than image (" +
")."); size.getMimeType() + ").");
} }
hasError = true; hasError = true;
} }
if (extension == null) extension = ""; if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE; State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.width, size.height, return new AttachmentItem(h, size.getWidth(), size.getHeight(),
extension, thumbnailSize.width, thumbnailSize.height, state); extension, thumbnailSize.getWidth(), thumbnailSize.getHeight(),
state);
} }
private Size getThumbnailSize(int width, int height, String mimeType) { 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; 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.graphics.BitmapFactory;
import android.webkit.MimeTypeMap; 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 com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -23,20 +23,21 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class ImageSizeCalculator { class ImageSizeCalculatorImpl implements ImageSizeCalculator {
private static final Logger LOG = private static final Logger LOG =
getLogger(ImageSizeCalculator.class.getName()); getLogger(ImageSizeCalculatorImpl.class.getName());
private static final int READ_LIMIT = 1024 * 8192; private static final int READ_LIMIT = 1024 * 8192;
private final ImageHelper imageHelper; private final ImageHelper imageHelper;
ImageSizeCalculator(ImageHelper imageHelper) { ImageSizeCalculatorImpl(ImageHelper imageHelper) {
this.imageHelper = imageHelper; this.imageHelper = imageHelper;
} }
Size getSize(InputStream is, String contentType) { @Override
public Size getSize(InputStream is, String contentType) {
Size size = new Size(); Size size = new Size();
is = new MarkEnforcingInputStream(is); is = new MarkEnforcingInputStream(is);
is.mark(READ_LIMIT); is.mark(READ_LIMIT);
@@ -49,7 +50,7 @@ class ImageSizeCalculator {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
} }
if (size.error) { if (size.hasError()) {
// need to mark again to re-add read limit // need to mark again to re-add read limit
is.mark(READ_LIMIT); is.mark(READ_LIMIT);
try { 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; package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author; 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.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader; 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.TextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; 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; import java.util.List;
@@ -156,16 +156,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
progressBar = v.findViewById(R.id.progressBar); progressBar = v.findViewById(R.id.progressBar);
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout), post = new BlogPostViewHolder(v.findViewById(R.id.postLayout),
true, new OnBlogPostClickListener() { true, new OnBlogPostClickListener() {
@Override @Override
public void onBlogPostClick(BlogPostItem post) { public void onBlogPostClick(BlogPostItem post) {
// do nothing // do nothing
} }
@Override @Override
public void onAuthorClick(BlogPostItem post) { public void onAuthorClick(BlogPostItem post) {
// probably don't want to allow author clicks here // probably don't want to allow author clicks here
} }
}, getFragmentManager()); }, getFragmentManager());
input = v.findViewById(R.id.inputText); 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;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.BlogManager;
import org.briarproject.briar.api.blog.BlogPost; import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.List; import java.util.List;

View File

@@ -3,14 +3,12 @@ package org.briarproject.briar.android.contact;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.BriarAdapter;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>> 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; 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> { public interface OnContactClickListener<I> {
void onItemClick(View view, I item); 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.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -10,14 +11,17 @@ import javax.annotation.concurrent.Immutable;
public class ContactItem { public class ContactItem {
private final Contact contact; private final Contact contact;
private final AuthorInfo authorInfo;
private final boolean connected; private final boolean connected;
public ContactItem(Contact contact) { public ContactItem(Contact contact, AuthorInfo authorInfo) {
this(contact, false); this(contact, authorInfo, false);
} }
public ContactItem(Contact contact, boolean connected) { public ContactItem(Contact contact, AuthorInfo authorInfo,
boolean connected) {
this.contact = contact; this.contact = contact;
this.authorInfo = authorInfo;
this.connected = connected; this.connected = connected;
} }
@@ -25,6 +29,10 @@ public class ContactItem {
return contact; return contact;
} }
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() { boolean isConnected() {
return connected; return connected;
} }

View File

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

View File

@@ -5,6 +5,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NullSafety;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -48,7 +49,11 @@ public class ContactListAdapter extends
if (c1.getTimestamp() != c2.getTimestamp()) { if (c1.getTimestamp() != c2.getTimestamp()) {
return false; 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.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -16,33 +18,44 @@ public class ContactListItem extends ContactItem
private final long timestamp; private final long timestamp;
private final int unread; private final int unread;
public ContactListItem(Contact contact, boolean connected, public ContactListItem(Contact contact, AuthorInfo authorInfo,
GroupCount count) { boolean connected, GroupCount count) {
super(contact, connected); super(contact, authorInfo, connected);
this.empty = count.getMsgCount() == 0; this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount(); this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime(); this.timestamp = count.getLatestMsgTime();
} }
private ContactListItem(Contact contact, boolean connected, boolean empty, private ContactListItem(Contact contact, AuthorInfo authorInfo,
int unread, long timestamp) { boolean connected, boolean empty, int unread, long timestamp) {
super(contact, connected); super(contact, authorInfo, connected);
this.empty = empty; this.empty = empty;
this.timestamp = timestamp; this.timestamp = timestamp;
this.unread = unread; this.unread = unread;
} }
ContactListItem(ContactListItem item, boolean connected) { ContactListItem(ContactListItem item, boolean connected) {
this(item.getContact(), connected, item.empty, item.unread, this(item.getContact(), item.getAuthorInfo(), connected, item.empty,
item.timestamp); item.unread, item.timestamp);
} }
ContactListItem(ContactListItem item, ConversationMessageHeader h) { 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, h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp)); 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() { boolean isEmpty() {
return empty; 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.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader; import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -55,6 +58,7 @@ class ContactListViewModel extends DbViewModel implements EventListener {
getLogger(ContactListViewModel.class.getName()); getLogger(ContactListViewModel.class.getName());
private final ContactManager contactManager; private final ContactManager contactManager;
private final AuthorManager authorManager;
private final ConversationManager conversationManager; private final ConversationManager conversationManager;
private final ConnectionRegistry connectionRegistry; private final ConnectionRegistry connectionRegistry;
private final EventBus eventBus; private final EventBus eventBus;
@@ -71,11 +75,13 @@ class ContactListViewModel extends DbViewModel implements EventListener {
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, TransactionManager db, LifecycleManager lifecycleManager, TransactionManager db,
AndroidExecutor androidExecutor, ContactManager contactManager, AndroidExecutor androidExecutor, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager, ConversationManager conversationManager,
ConnectionRegistry connectionRegistry, EventBus eventBus, ConnectionRegistry connectionRegistry, EventBus eventBus,
AndroidNotificationManager notificationManager) { AndroidNotificationManager notificationManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.contactManager = contactManager; this.contactManager = contactManager;
this.authorManager = authorManager;
this.conversationManager = conversationManager; this.conversationManager = conversationManager;
this.connectionRegistry = connectionRegistry; this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -99,10 +105,11 @@ class ContactListViewModel extends DbViewModel implements EventListener {
List<ContactListItem> contacts = new ArrayList<>(); List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts(txn)) { for (Contact c : contactManager.getContacts(txn)) {
ContactId id = c.getId(); ContactId id = c.getId();
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
MessageTracker.GroupCount count = MessageTracker.GroupCount count =
conversationManager.getGroupCount(txn, id); conversationManager.getGroupCount(txn, id);
boolean connected = connectionRegistry.isConnected(c.getId()); boolean connected = connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count)); contacts.add(new ContactListItem(c, authorInfo, connected, count));
} }
Collections.sort(contacts); Collections.sort(contacts);
logDuration(LOG, "Full load", start); logDuration(LOG, "Full load", start);
@@ -133,6 +140,10 @@ class ContactListViewModel extends DbViewModel implements EventListener {
} else if (e instanceof PendingContactAddedEvent || } else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) { e instanceof PendingContactRemovedEvent) {
checkForPendingContacts(); 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.bramble.api.sync.GroupId;
import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; 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.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -31,11 +33,14 @@ public abstract class ContactSelectorControllerImpl
Logger.getLogger(ContactSelectorControllerImpl.class.getName()); Logger.getLogger(ContactSelectorControllerImpl.class.getName());
private final ContactManager contactManager; private final ContactManager contactManager;
private final AuthorManager authorManager;
public ContactSelectorControllerImpl(@DatabaseExecutor Executor dbExecutor, public ContactSelectorControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager) { LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager) {
super(dbExecutor, lifecycleManager); super(dbExecutor, lifecycleManager);
this.contactManager = contactManager; this.contactManager = contactManager;
this.authorManager = authorManager;
} }
@Override @Override
@@ -45,12 +50,13 @@ public abstract class ContactSelectorControllerImpl
try { try {
Collection<SelectableContactItem> contacts = new ArrayList<>(); Collection<SelectableContactItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts()) { for (Contact c : contactManager.getContacts()) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
// was this contact already selected? // was this contact already selected?
boolean selected = selection.contains(c.getId()); boolean selected = selection.contains(c.getId());
// can this contact be selected? // can this contact be selected?
boolean disabled = isDisabled(g, c); boolean disabled = isDisabled(g, c);
contacts.add(new SelectableContactItem(c, selected, contacts.add(new SelectableContactItem(c, authorInfo,
disabled)); selected, disabled));
} }
handler.onResult(contacts); handler.onResult(contacts);
} catch (DbException e) { } 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.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.contact.ContactItem; import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@@ -10,11 +11,12 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotNullByDefault @NotNullByDefault
public class SelectableContactItem extends ContactItem { public class SelectableContactItem extends ContactItem {
private boolean selected, disabled; private boolean selected;
private final boolean disabled;
public SelectableContactItem(Contact contact, boolean selected, public SelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean disabled) { boolean selected, boolean disabled) {
super(contact); super(contact, authorInfo);
this.selected = selected; this.selected = selected;
this.disabled = disabled; this.disabled = disabled;
} }

View File

@@ -21,7 +21,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDialogFragment; import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@@ -59,7 +58,7 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme); setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory) viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class); .get(ConversationViewModel.class);
} }
@@ -72,7 +71,8 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
aliasEditLayout = v.findViewById(R.id.aliasEditLayout); aliasEditLayout = v.findViewById(R.id.aliasEditLayout);
aliasEditText = v.findViewById(R.id.aliasEditText); aliasEditText = v.findViewById(R.id.aliasEditText);
Contact contact = requireNonNull(viewModel.getContact().getValue()); Contact contact = requireNonNull(viewModel.getContactItem().getValue())
.getContact();
String alias = contact.getAlias(); String alias = contact.getAlias();
aliasEditText.setText(alias); aliasEditText.setText(alias);
if (alias != null) aliasEditText.setSelection(alias.length()); 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.TextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.api.android.AndroidNotificationManager; 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.blog.BlogSharingManager;
import org.briarproject.briar.api.client.ProtocolStateException; import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId; 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.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager; 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.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; 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.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.os.Build.VERSION.SDK_INT; 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.ITEM_ID;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME; import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; 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_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH; 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); toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName); toolbarTitle = toolbar.findViewById(R.id.contactName);
observeOnce(viewModel.getContactAuthorId(), this, authorId -> { viewModel.getContactItem().observe(this, contactItem -> {
requireNonNull(authorId); requireNonNull(contactItem);
toolbarAvatar.setImageDrawable( setAvatar(toolbarAvatar, contactItem);
new IdenticonDrawable(authorId.getBytes()));
}); });
viewModel.getContactDisplayName().observe(this, contactName -> { viewModel.getContactDisplayName().observe(this, contactName -> {
requireNonNull(contactName); requireNonNull(contactName);
@@ -366,7 +365,7 @@ public class ConversationActivity extends BriarActivity
} }
}); });
// enable alias action if available // enable alias action if available
observeOnce(viewModel.getContact(), this, contact -> observeOnce(viewModel.getContactItem(), this, contact ->
menu.findItem(R.id.action_set_alias).setEnabled(true)); menu.findItem(R.id.action_set_alias).setEnabled(true));
return super.onCreateOptionsMenu(menu); 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.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -27,11 +26,15 @@ import org.briarproject.briar.android.attachment.AttachmentCreator;
import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentRetriever; 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.util.UiUtils;
import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
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.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -49,8 +52,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import static androidx.lifecycle.Transformations.map;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -76,6 +79,7 @@ public class ConversationViewModel extends DbViewModel
private final EventBus eventBus; private final EventBus eventBus;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final AuthorManager authorManager;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory; private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever; private final AttachmentRetriever attachmentRetriever;
@@ -83,11 +87,10 @@ public class ConversationViewModel extends DbViewModel
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
private final MutableLiveData<Contact> contact = new MutableLiveData<>(); private final MutableLiveData<ContactItem> contactItem =
private final LiveData<AuthorId> contactAuthorId = new MutableLiveData<>();
Transformations.map(contact, c -> c.getAuthor().getId()); private final LiveData<String> contactName = map(contactItem, c ->
private final LiveData<String> contactName = UiUtils.getContactDisplayName(c.getContact()));
Transformations.map(contact, UiUtils::getContactDisplayName);
private final LiveData<GroupId> messagingGroupId; private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<Boolean> imageSupport = private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>(); new MutableLiveData<>();
@@ -111,6 +114,7 @@ public class ConversationViewModel extends DbViewModel
EventBus eventBus, EventBus eventBus,
MessagingManager messagingManager, MessagingManager messagingManager,
ContactManager contactManager, ContactManager contactManager,
AuthorManager authorManager,
SettingsManager settingsManager, SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory, PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
@@ -120,12 +124,13 @@ public class ConversationViewModel extends DbViewModel
this.eventBus = eventBus; this.eventBus = eventBus;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.authorManager = authorManager;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory; this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever; this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
messagingGroupId = Transformations messagingGroupId = map(contactItem, c ->
.map(contact, c -> messagingManager.getContactGroup(c).getId()); messagingManager.getContactGroup(c.getContact()).getId());
contactDeleted.setValue(false); contactDeleted.setValue(false);
eventBus.addListener(this); eventBus.addListener(this);
@@ -147,9 +152,33 @@ public class ConversationViewModel extends DbViewModel
runOnDbThread(() -> attachmentRetriever runOnDbThread(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId())); .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 * Setting the {@link ContactId} automatically triggers loading of other
* data. * data.
@@ -168,7 +197,8 @@ public class ConversationViewModel extends DbViewModel
try { try {
long start = now(); long start = now();
Contact c = contactManager.getContact(contactId); Contact c = contactManager.getContact(contactId);
contact.postValue(c); AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
contactItem.postValue(new ContactItem(c, authorInfo));
logDuration(LOG, "Loading contact", start); logDuration(LOG, "Loading contact", start);
start = now(); start = now();
checkFeaturesAndOnboarding(contactId); checkFeaturesAndOnboarding(contactId);
@@ -319,12 +349,8 @@ public class ConversationViewModel extends DbViewModel
return attachmentRetriever; return attachmentRetriever;
} }
LiveData<Contact> getContact() { LiveData<ContactItem> getContactItem() {
return contact; return contactItem;
}
LiveData<AuthorId> getContactAuthorId() {
return contactAuthorId;
} }
LiveData<String> getContactDisplayName() { LiveData<String> getContactDisplayName() {

View File

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

View File

@@ -77,7 +77,7 @@ class ImageViewHolder extends ViewHolder {
private void loadImage(AttachmentItem a, Radii r) { private void loadImage(AttachmentItem a, Radii r) {
Transformation<Bitmap> transformation = new BriarImageTransformation(r); Transformation<Bitmap> transformation = new BriarImageTransformation(r);
GlideApp.with(imageView) GlideApp.with(imageView)
.load(a) .load(a.getHeader())
.diskCacheStrategy(NONE) .diskCacheStrategy(NONE)
.error(ERROR_RES) .error(ERROR_RES)
.transform(transformation) .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.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent; import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.io.File; 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 static final Logger LOG = getLogger(ImageViewModel.class.getName());
private final MessagingManager messagingManager; private final AttachmentReader attachmentReader;
private final EventBus eventBus; private final EventBus eventBus;
@IoExecutor @IoExecutor
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -75,16 +75,14 @@ public class ImageViewModel extends DbViewModel implements EventListener {
private int toolbarTop, toolbarBottom; private int toolbarTop, toolbarBottom;
@Inject @Inject
ImageViewModel(Application application, ImageViewModel(Application application, AttachmentReader attachmentReader,
MessagingManager messagingManager, EventBus eventBus, @DatabaseExecutor Executor dbExecutor,
EventBus eventBus,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
TransactionManager db, TransactionManager db,
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor) { @IoExecutor Executor ioExecutor) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.messagingManager = messagingManager; this.attachmentReader = attachmentReader;
this.eventBus = eventBus; this.eventBus = eventBus;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -202,7 +200,7 @@ public class ImageViewModel extends DbViewModel implements EventListener {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
Attachment a = Attachment a =
messagingManager.getAttachment(attachment.getHeader()); attachmentReader.getAttachment(attachment.getHeader());
copyImageFromDb(a, osp, afterCopy); copyImageFromDb(a, osp, afterCopy);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);

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.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.attachment.Attachment;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.attachment.AttachmentReader;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -30,21 +30,22 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
private final static Logger LOG = private final static Logger LOG =
getLogger(BriarDataFetcher.class.getName()); getLogger(BriarDataFetcher.class.getName());
private final MessagingManager messagingManager; private final AttachmentReader attachmentReader;
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
private final AttachmentItem attachment; private final AttachmentHeader attachmentHeader;
@Nullable @Nullable
private volatile InputStream inputStream; private volatile InputStream inputStream;
private volatile boolean cancel = false; private volatile boolean cancel = false;
@Inject @Inject
BriarDataFetcher(MessagingManager messagingManager, BriarDataFetcher(AttachmentReader attachmentReader,
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) { @DatabaseExecutor Executor dbExecutor,
this.messagingManager = messagingManager; AttachmentHeader attachmentHeader) {
this.attachmentReader = attachmentReader;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.attachment = attachment; this.attachmentHeader = attachmentHeader;
} }
@Override @Override
@@ -53,8 +54,7 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
if (cancel) return; if (cancel) return;
try { try {
Attachment a = Attachment a = attachmentReader.getAttachment(attachmentHeader);
messagingManager.getAttachment(attachment.getHeader());
inputStream = a.getStream(); inputStream = a.getStream();
callback.onDataReady(inputStream); callback.onDataReady(inputStream);
} catch (DbException e) { } 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.db.DatabaseExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.attachment.AttachmentReader;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -12,19 +12,19 @@ import javax.inject.Inject;
@NotNullByDefault @NotNullByDefault
public class BriarDataFetcherFactory { public class BriarDataFetcherFactory {
private final MessagingManager messagingManager; private final AttachmentReader attachmentReader;
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
@Inject @Inject
public BriarDataFetcherFactory(MessagingManager messagingManager, public BriarDataFetcherFactory(AttachmentReader attachmentReader,
@DatabaseExecutor Executor dbExecutor) { @DatabaseExecutor Executor dbExecutor) {
this.messagingManager = messagingManager; this.attachmentReader = attachmentReader;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
} }
BriarDataFetcher createBriarDataFetcher(AttachmentItem model) { BriarDataFetcher createBriarDataFetcher(AttachmentHeader model) {
return new BriarDataFetcher(messagingManager, dbExecutor, 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.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream; import java.io.InputStream;
@@ -28,7 +28,7 @@ public final class BriarGlideModule extends AppGlideModule {
BriarApplication app = BriarApplication app =
(BriarApplication) context.getApplicationContext(); (BriarApplication) context.getApplicationContext();
BriarModelLoaderFactory factory = new BriarModelLoaderFactory(app); BriarModelLoaderFactory factory = new BriarModelLoaderFactory(app);
registry.prepend(AttachmentItem.class, InputStream.class, factory); registry.prepend(AttachmentHeader.class, InputStream.class, factory);
} }
@Override @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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream; import java.io.InputStream;
@@ -17,7 +17,7 @@ import javax.inject.Inject;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public final class BriarModelLoader public final class BriarModelLoader
implements ModelLoader<AttachmentItem, InputStream> { implements ModelLoader<AttachmentHeader, InputStream> {
@Inject @Inject
BriarDataFetcherFactory dataFetcherFactory; BriarDataFetcherFactory dataFetcherFactory;
@@ -27,8 +27,8 @@ public final class BriarModelLoader
} }
@Override @Override
public LoadData<InputStream> buildLoadData(AttachmentItem model, int width, public LoadData<InputStream> buildLoadData(AttachmentHeader model,
int height, Options options) { int width, int height, Options options) {
ObjectKey key = new ObjectKey(model.getMessageId()); ObjectKey key = new ObjectKey(model.getMessageId());
BriarDataFetcher dataFetcher = BriarDataFetcher dataFetcher =
dataFetcherFactory.createBriarDataFetcher(model); dataFetcherFactory.createBriarDataFetcher(model);
@@ -36,7 +36,7 @@ public final class BriarModelLoader
} }
@Override @Override
public boolean handles(AttachmentItem model) { public boolean handles(AttachmentHeader model) {
return true; 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.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.io.InputStream; import java.io.InputStream;
@NotNullByDefault @NotNullByDefault
class BriarModelLoaderFactory class BriarModelLoaderFactory
implements ModelLoaderFactory<AttachmentItem, InputStream> { implements ModelLoaderFactory<AttachmentHeader, InputStream> {
private final BriarApplication app; private final BriarApplication app;
@@ -21,7 +21,7 @@ class BriarModelLoaderFactory
} }
@Override @Override
public ModelLoader<AttachmentItem, InputStream> build( public ModelLoader<AttachmentHeader, InputStream> build(
MultiModelLoaderFactory multiFactory) { MultiModelLoaderFactory multiFactory) {
return new BriarModelLoader(app); return new BriarModelLoader(app);
} }

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.forum; package org.briarproject.briar.android.forum;
import org.briarproject.bramble.api.identity.Author; 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.bramble.api.sync.MessageId;
import org.briarproject.briar.android.threaded.ThreadItem; import org.briarproject.briar.android.threaded.ThreadItem;
import org.briarproject.briar.api.forum.ForumPostHeader; 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.android.view.BriarRecyclerView;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager; 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.ArrayList;
import java.util.List; import java.util.List;
@@ -49,10 +51,12 @@ public class ContactChooserFragment extends BaseFragment {
private ContactId contactId; private ContactId contactId;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
volatile Contact c1; private volatile Contact c1;
@Inject @Inject
volatile ContactManager contactManager; volatile ContactManager contactManager;
@Inject @Inject
volatile AuthorManager authorManager;
@Inject
volatile ConversationManager conversationManager; volatile ConversationManager conversationManager;
@Inject @Inject
volatile ConnectionRegistry connectionRegistry; volatile ConnectionRegistry connectionRegistry;
@@ -123,12 +127,14 @@ public class ContactChooserFragment extends BaseFragment {
if (c.getId().equals(contactId)) { if (c.getId().equals(contactId)) {
c1 = c; c1 = c;
} else { } else {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
ContactId id = c.getId(); ContactId id = c.getId();
GroupCount count = GroupCount count =
conversationManager.getGroupCount(id); conversationManager.getGroupCount(id);
boolean connected = boolean connected =
connectionRegistry.isConnected(c.getId()); connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count)); contacts.add(new ContactListItem(c, authorInfo,
connected, count));
} }
} }
displayContacts(contacts); 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.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; 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.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; 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.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -33,7 +36,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE; 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.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard; 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; import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -65,6 +68,8 @@ public class IntroductionMessageFragment extends BaseFragment
@Inject @Inject
protected volatile ContactManager contactManager; protected volatile ContactManager contactManager;
@Inject @Inject
protected volatile AuthorManager authorManager;
@Inject
protected volatile IntroductionManager introductionManager; protected volatile IntroductionManager introductionManager;
public static IntroductionMessageFragment newInstance(int contactId1, public static IntroductionMessageFragment newInstance(int contactId1,
@@ -137,11 +142,16 @@ public class IntroductionMessageFragment extends BaseFragment
private void prepareToSetUpViews(int contactId1, int contactId2) { private void prepareToSetUpViews(int contactId1, int contactId2) {
introductionActivity.runOnDbThread(() -> { introductionActivity.runOnDbThread(() -> {
try { try {
Contact c1 = contactManager.getContact( Contact contact1 =
new ContactId(contactId1)); contactManager.getContact(new ContactId(contactId1));
Contact c2 = contactManager.getContact( Contact contact2 =
new ContactId(contactId2)); contactManager.getContact(new ContactId(contactId2));
boolean possible = introductionManager.canIntroduce(c1, c2); 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); setUpViews(c1, c2, possible);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, 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(() -> { introductionActivity.runOnUiThreadUnlessDestroyed(() -> {
contact1 = c1; contact1 = c1.getContact();
contact2 = c2; contact2 = c2.getContact();
// set avatars // set avatars
ui.avatar1.setImageDrawable(new IdenticonDrawable( setAvatar(ui.avatar1, c1);
c1.getAuthor().getId().getBytes())); setAvatar(ui.avatar2, c2);
ui.avatar2.setImageDrawable(new IdenticonDrawable(
c2.getAuthor().getId().getBytes()));
// set contact names // set contact names
ui.contactName1.setText(getContactDisplayName(c1)); ui.contactName1.setText(getContactDisplayName(c1.getContact()));
ui.contactName2.setText(getContactDisplayName(c2)); ui.contactName2.setText(getContactDisplayName(c2.getContact()));
// hide progress bar // hide progress bar
ui.progressBar.setVisibility(GONE); ui.progressBar.setVisibility(GONE);

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.privategroup.conversation; package org.briarproject.briar.android.privategroup.conversation;
import org.briarproject.bramble.api.identity.Author; 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.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;

View File

@@ -10,7 +10,7 @@ import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListe
import androidx.annotation.UiThread; 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 @UiThread
@NotNullByDefault @NotNullByDefault

View File

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

View File

@@ -1,7 +1,7 @@
package org.briarproject.briar.android.privategroup.list; package org.briarproject.briar.android.privategroup.list;
import org.briarproject.bramble.api.identity.Author; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; 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.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId; 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.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -24,6 +23,8 @@ import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; 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.GroupMessageHeader;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
@@ -64,7 +65,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
private final PrivateGroupManager groupManager; private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager; private final GroupInvitationManager groupInvitationManager;
private final ContactManager contactManager; private final AuthorManager authorManager;
private final AndroidNotificationManager notificationManager; private final AndroidNotificationManager notificationManager;
private final EventBus eventBus; private final EventBus eventBus;
@@ -81,12 +82,12 @@ class GroupListViewModel extends DbViewModel implements EventListener {
AndroidExecutor androidExecutor, AndroidExecutor androidExecutor,
PrivateGroupManager groupManager, PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager, GroupInvitationManager groupInvitationManager,
ContactManager contactManager, AuthorManager authorManager,
AndroidNotificationManager notificationManager, EventBus eventBus) { AndroidNotificationManager notificationManager, EventBus eventBus) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.groupManager = groupManager; this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager; this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager; this.authorManager = authorManager;
this.notificationManager = notificationManager; this.notificationManager = notificationManager;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventBus.addListener(this); this.eventBus.addListener(this);
@@ -157,7 +158,7 @@ class GroupListViewModel extends DbViewModel implements EventListener {
if (authorInfos.containsKey(authorId)) { if (authorInfos.containsKey(authorId)) {
authorInfo = requireNonNull(authorInfos.get(authorId)); authorInfo = requireNonNull(authorInfos.get(authorId));
} else { } else {
authorInfo = contactManager.getAuthorInfo(txn, authorId); authorInfo = authorManager.getAuthorInfo(txn, authorId);
authorInfos.put(authorId, authorInfo); authorInfos.put(authorId, authorInfo);
} }
GroupCount count = groupManager.getGroupCount(txn, id); 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.contact.ContactId;
import org.briarproject.bramble.api.identity.Author; 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.identity.AuthorInfo.Status; import org.briarproject.briar.api.identity.AuthorInfo.Status;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.privategroup.GroupMember; 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.GONE;
import static android.view.View.VISIBLE; 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; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread @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.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.client.ProtocolStateException; 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.GroupMember;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
@@ -45,17 +47,20 @@ class RevealContactsControllerImpl extends DbControllerImpl
private final PrivateGroupManager groupManager; private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager; private final GroupInvitationManager groupInvitationManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final AuthorManager authorManager;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
@Inject @Inject
RevealContactsControllerImpl(@DatabaseExecutor Executor dbExecutor, RevealContactsControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, PrivateGroupManager groupManager, LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager, GroupInvitationManager groupInvitationManager,
ContactManager contactManager, SettingsManager settingsManager) { ContactManager contactManager, AuthorManager authorManager,
SettingsManager settingsManager) {
super(dbExecutor, lifecycleManager); super(dbExecutor, lifecycleManager);
this.groupManager = groupManager; this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager; this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.authorManager = authorManager;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
} }
@@ -82,11 +87,12 @@ class RevealContactsControllerImpl extends DbControllerImpl
for (GroupMember m : members) { for (GroupMember m : members) {
for (Contact c : contacts) { for (Contact c : contacts) {
if (m.getAuthor().equals(c.getAuthor())) { if (m.getAuthor().equals(c.getAuthor())) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
boolean disabled = m.getVisibility() != INVISIBLE; boolean disabled = m.getVisibility() != INVISIBLE;
boolean selected = boolean selected =
disabled || selection.contains(c.getId()); disabled || selection.contains(c.getId());
items.add(new RevealableContactItem(c, selected, disabled, items.add(new RevealableContactItem(c, authorInfo, selected,
m.getVisibility())); 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.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.contactselection.SelectableContactItem; import org.briarproject.briar.android.contactselection.SelectableContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.privategroup.Visibility; import org.briarproject.briar.api.privategroup.Visibility;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@@ -13,9 +14,9 @@ class RevealableContactItem extends SelectableContactItem {
private final Visibility visibility; private final Visibility visibility;
RevealableContactItem(Contact contact, boolean selected, RevealableContactItem(Contact contact, AuthorInfo authorInfo,
boolean disabled, Visibility visibility) { boolean selected, boolean disabled, Visibility visibility) {
super(contact, selected, disabled); super(contact, authorInfo, selected, disabled);
this.visibility = visibility; 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; package org.briarproject.briar.android.settings;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; 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.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; 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.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 { public class SettingsActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel;
@Inject
FeatureFlags featureFlags;
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
@@ -22,6 +45,37 @@ public class SettingsActivity extends BriarActivity {
} }
setContentView(R.layout.activity_settings); 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 @Override
@@ -37,4 +91,31 @@ public class SettingsActivity extends BriarActivity {
} }
return false; 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.LargeTextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; 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; import java.util.List;
@@ -62,6 +62,7 @@ public abstract class BaseMessageFragment extends BaseFragment
@StringRes @StringRes
protected abstract int getButtonText(); protected abstract int getButtonText();
@StringRes @StringRes
protected abstract int getHintText(); 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.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -42,9 +43,10 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
@Inject @Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager, ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) { BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager); super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager; this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager; this.blogSharingManager = blogSharingManager;
this.clock = clock; 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.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager; import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -42,9 +43,10 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
@Inject @Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager, ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) { ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager); super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager; this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
this.clock = clock; 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.activity.BriarActivity;
import org.briarproject.briar.android.contact.ContactItem; import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.view.BriarRecyclerView; 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 org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,8 +44,12 @@ import static org.briarproject.bramble.util.LogUtils.logException;
abstract class SharingStatusActivity extends BriarActivity abstract class SharingStatusActivity extends BriarActivity
implements EventListener { implements EventListener {
// objects accessed from background thread need to be volatile
@Inject @Inject
ConnectionRegistry connectionRegistry; volatile AuthorManager authorManager;
@Inject
volatile ConnectionRegistry connectionRegistry;
@Inject @Inject
EventBus eventBus; EventBus eventBus;
@@ -134,8 +140,9 @@ abstract class SharingStatusActivity extends BriarActivity
try { try {
List<ContactItem> contactItems = new ArrayList<>(); List<ContactItem> contactItems = new ArrayList<>();
for (Contact c : getSharedWith()) { for (Contact c : getSharedWith()) {
AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
boolean online = connectionRegistry.isConnected(c.getId()); boolean online = connectionRegistry.isConnected(c.getId());
ContactItem item = new ContactItem(c, online); ContactItem item = new ContactItem(c, authorInfo, online);
contactItems.add(item); contactItems.add(item);
} }
displaySharedWith(contactItems); displaySharedWith(contactItems);

View File

@@ -24,20 +24,9 @@ public class TestDataActivity extends BriarActivity {
@Inject @Inject
TestDataCreator testDataCreator; TestDataCreator testDataCreator;
private TextView contactsTextView; private TextView contactsTextView, forumsTextView;
private SeekBar contactsSeekBar; private SeekBar contactsSeekBar, messagesSeekBar, avatarsSeekBar,
blogPostsSeekBar, forumsSeekBar, forumPostsSeekBar;
private TextView messagesTextView;
private SeekBar messagesSeekBar;
private TextView blogPostsTextView;
private SeekBar blogPostsSeekBar;
private TextView forumsTextView;
private SeekBar forumsSeekBar;
private TextView forumPostsTextView;
private SeekBar forumPostsSeekBar;
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
@@ -51,12 +40,15 @@ public class TestDataActivity extends BriarActivity {
setContentView(R.layout.activity_test_data); setContentView(R.layout.activity_test_data);
contactsTextView = findViewById(R.id.textViewContactsSb); contactsTextView = findViewById(R.id.textViewContactsSb);
messagesTextView = findViewById(R.id.textViewMessagesSb); TextView messagesTextView = findViewById(R.id.textViewMessagesSb);
blogPostsTextView = findViewById(R.id.TextViewBlogPostsSb); TextView avatarsTextView = findViewById(R.id.textViewAvatarsSb);
TextView blogPostsTextView = findViewById(R.id.TextViewBlogPostsSb);
forumsTextView = findViewById(R.id.TextViewForumsSb); forumsTextView = findViewById(R.id.TextViewForumsSb);
forumPostsTextView = findViewById(R.id.TextViewForumMessagesSb); TextView forumPostsTextView =
findViewById(R.id.TextViewForumMessagesSb);
contactsSeekBar = findViewById(R.id.seekBarContacts); contactsSeekBar = findViewById(R.id.seekBarContacts);
messagesSeekBar = findViewById(R.id.seekBarMessages); messagesSeekBar = findViewById(R.id.seekBarMessages);
avatarsSeekBar = findViewById(R.id.seekBarAvatars);
blogPostsSeekBar = findViewById(R.id.seekBarBlogPosts); blogPostsSeekBar = findViewById(R.id.seekBarBlogPosts);
forumsSeekBar = findViewById(R.id.seekBarForums); forumsSeekBar = findViewById(R.id.seekBarForums);
forumPostsSeekBar = findViewById(R.id.seekBarForumMessages); forumPostsSeekBar = findViewById(R.id.seekBarForumMessages);
@@ -78,40 +70,12 @@ public class TestDataActivity extends BriarActivity {
} }
}); });
messagesSeekBar messagesSeekBar.setOnSeekBarChangeListener(
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { new OnSeekBarChangeUpdateProgress(messagesTextView));
@Override avatarsSeekBar.setOnSeekBarChangeListener(
public void onProgressChanged(SeekBar seekBar, new OnSeekBarChangeUpdateProgress(avatarsTextView));
int progress, boolean fromUser) { blogPostsSeekBar.setOnSeekBarChangeListener(
messagesTextView.setText(String.valueOf(progress)); new OnSeekBarChangeUpdateProgress(blogPostsTextView));
}
@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) {
}
});
forumsSeekBar forumsSeekBar
.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { .setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override @Override
@@ -129,23 +93,8 @@ public class TestDataActivity extends BriarActivity {
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
} }
}); });
forumPostsSeekBar.setOnSeekBarChangeListener(
forumPostsSeekBar new OnSeekBarChangeUpdateProgress(forumPostsTextView));
.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) {
}
});
findViewById(R.id.buttonCreateTestData).setOnClickListener( findViewById(R.id.buttonCreateTestData).setOnClickListener(
v -> createTestData()); v -> createTestData());
@@ -153,8 +102,9 @@ public class TestDataActivity extends BriarActivity {
private void createTestData() { private void createTestData() {
testDataCreator.createTestData(contactsSeekBar.getProgress() + 1, testDataCreator.createTestData(contactsSeekBar.getProgress() + 1,
messagesSeekBar.getProgress(), blogPostsSeekBar.getProgress(), messagesSeekBar.getProgress(), avatarsSeekBar.getProgress(),
forumsSeekBar.getProgress(), forumPostsSeekBar.getProgress()); blogPostsSeekBar.getProgress(), forumsSeekBar.getProgress(),
forumPostsSeekBar.getProgress());
Intent intent = new Intent(this, ENTRY_ACTIVITY); Intent intent = new Intent(this, ENTRY_ACTIVITY);
intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent); startActivity(intent);
@@ -174,4 +124,28 @@ public class TestDataActivity extends BriarActivity {
} }
return false; 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; package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.identity.Author; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.MessageTree.MessageNode; 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;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.UnreadMessageButton; 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.client.NamedGroup;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.KEYGUARD_SERVICE;
import static android.content.Context.POWER_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_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.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER; import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT; 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 androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS; 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.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME; 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) { public static void showOnboardingDialog(Context ctx, String text) {
new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme) new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme)
.setMessage(text) .setMessage(text)

View File

@@ -8,10 +8,15 @@ import android.view.LayoutInflater;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.briarproject.bramble.api.identity.Author; 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.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.android.util.UiUtils;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.Nullable; 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.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.graphics.Typeface.BOLD; import static android.graphics.Typeface.BOLD;
import static android.util.TypedValue.COMPLEX_UNIT_PX; 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.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute; 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 @UiThread
public class AuthorView extends ConstraintLayout { public class AuthorView extends ConstraintLayout {
@@ -74,8 +79,7 @@ public class AuthorView extends ConstraintLayout {
public void setAuthor(Author author, AuthorInfo authorInfo) { public void setAuthor(Author author, AuthorInfo authorInfo) {
authorName authorName
.setText(getContactDisplayName(author, authorInfo.getAlias())); .setText(getContactDisplayName(author, authorInfo.getAlias()));
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes()); setAvatar(avatar, author.getId(), authorInfo);
avatar.setImageDrawable(d);
if (authorInfo.getStatus() != NONE) { if (authorInfo.getStatus() != NONE) {
trustIndicator.setTrustLevel(authorInfo.getStatus()); trustIndicator.setTrustLevel(authorInfo.getStatus());
@@ -94,6 +98,27 @@ public class AuthorView extends ConstraintLayout {
requestLayout(); 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) { public void setDate(long date) {
this.date.setText(UiUtils.formatDate(getContext(), date)); this.date.setText(UiUtils.formatDate(getContext(), date));
@@ -117,10 +142,10 @@ public class AuthorView extends ConstraintLayout {
/** /**
* Styles this view for a different persona. * Styles this view for a different persona.
* * <p>
* Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar * Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar
* and override the one set by * and override the one set by
* {@link AuthorView#setAuthor(Author, AuthorInfo)}. * {@link AuthorView#setAuthor(Author, AuthorInfo)}.
*/ */
public void setPersona(int persona) { public void setPersona(int persona) {
switch (persona) { switch (persona) {

View File

@@ -46,7 +46,7 @@ class ImagePreviewViewHolder extends ViewHolder {
.clear(imageView); .clear(imageView);
} else { } else {
GlideApp.with(imageView) GlideApp.with(imageView)
.load(item.getItem()) .load(item.getItem().getHeader())
.diskCacheStrategy(NONE) .diskCacheStrategy(NONE)
.error(ERROR_RES) .error(ERROR_RES)
.downsample(FIT_CENTER) .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.AttachmentItemResult;
import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener; import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
import java.util.ArrayList; import java.util.ArrayList;
@@ -29,18 +30,12 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; 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.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static androidx.customview.view.AbsSavedState.EMPTY_STATE; import static androidx.customview.view.AbsSavedState.EMPTY_STATE;
import static androidx.lifecycle.Lifecycle.State.DESTROYED; 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.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
@@ -120,26 +115,15 @@ public class TextAttachmentController extends TextSendController
builder.show(); builder.show();
return; return;
} }
Intent intent = getAttachFileIntent(); Intent intent = UiUtils.createSelectImageIntent(true);
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) { if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
attachmentListener.onAttachImage(intent); 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 * This is called with the result Intent returned by the Activity started
* returned by the Activity started with {@link #getAttachFileIntent()}. * with {@link UiUtils#createSelectImageIntent(boolean)}.
* <p> * <p>
* This method must be called at most once per call to * This method must be called at most once per call to
* {@link AttachmentListener#onAttachImage(Intent)}. * {@link AttachmentListener#onAttachImage(Intent)}.

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