Compare commits

...

80 Commits

Author SHA1 Message Date
akwizgran
3fb27dbb12 Bump version numbers for 1.2.14 release. 2021-01-29 14:10:24 +00:00
akwizgran
831c65b647 Merge branch 'vector-compat' into 'master'
Use vector support libraries instead of rasterizing all drawables

See merge request briar/briar!1346
2021-01-28 17:59:08 +00:00
akwizgran
afcd38b84c Update translations. 2021-01-28 16:15:44 +00:00
Torsten Grote
5c1bcdeb9d Merge branch 'update-bridges' into 'master'
Update bridges

See merge request briar/briar!1352
2021-01-26 14:11:00 +00:00
akwizgran
6c1f5450cb Add run configuration for BridgeTest. 2021-01-26 13:57:33 +00:00
akwizgran
0d070cf422 Change dummy address for meek bridge.
See https://gitweb.torproject.org/builders/tor-browser-build.git/commit/projects/tor-browser/Bundle-Data/PTConfigs/bridge_prefs.js?id=8bd845464ae14bf56e0187dfa6f6e773a6593f55
2021-01-26 13:53:51 +00:00
akwizgran
d34d66c691 Update list of obfs4 bridges. 2021-01-26 13:51:41 +00:00
Torsten Grote
6005d156eb Rename ic_lock icon to notification_lock 2021-01-26 08:24:16 -03:00
Torsten Grote
94dd75f24b Use VectorDrawableCompat compatible ways of setting drawables programmatically
so they won't crash on API < 21
2021-01-25 13:34:27 -03:00
Torsten Grote
c93e5441b0 Store rasterized notification icons, because NotificationCompat doesn't handle it
and would crash on API < 21
2021-01-25 13:34:27 -03:00
Torsten Grote
8ec8cc927b Use vector support libraries instead of rasterizing all drawables
https://developer.android.com/guide/topics/graphics/vector-drawable-resources#vector-drawables-backward-solution
2021-01-25 13:34:26 -03:00
akwizgran
4663e727eb Merge branch '214-user-avatars' into 'master'
Merge user avatars feature branch

See merge request briar/briar!1334
2021-01-25 15:15:54 +00:00
akwizgran
e2acd19ffd Trivial code cleanups. 2021-01-25 15:05:15 +00:00
akwizgran
0befa6a823 Use NullSafety.equals(). 2021-01-25 15:05:15 +00:00
Torsten Grote
01083f47ea Merge branch '1865-setupcontroller-to-viewmodel' into 'master'
Migrate SetupController to a ViewModel

See merge request briar/briar!1340
2021-01-25 14:03:32 +00:00
Daniel Lublin
a349bd146c Migrate SetupController to a ViewModel
Solves #1865
2021-01-25 14:34:19 +01:00
Torsten Grote
4ffa9e191c Merge branch '1912-specify-group-id-when-loading-attachment' into '214-user-avatars'
Ensure that attachment has expected group ID when loading

See merge request briar/briar!1347
2021-01-25 12:58:19 +00:00
akwizgran
e616fc3da7 Throw NoSuchMessageException if attachment is invalid. 2021-01-22 14:01:36 +00:00
akwizgran
aed5ac5bb4 Ensure that attachment has expected group ID when loading. 2021-01-22 13:35:06 +00:00
Sebastian Kürten
cae53a9fcc Reorganize MediaModule and AttachmentModule 2021-01-21 10:13:29 -03:00
Sebastian Kürten
6660625ba6 Update avatar in contact list when changed while list is open 2021-01-21 10:13:29 -03:00
Sebastian Kürten
bf9ba13b68 Update app bar in ConversationActivity with received avatar 2021-01-21 09:33:56 -03:00
Sebastian Kürten
a52c97ecf7 Format touched xml layouts 2021-01-21 09:33:56 -03:00
Sebastian Kürten
a2174e7677 SettingsViewModel: use LiveEvent instead of LiveData 2021-01-21 09:33:55 -03:00
Sebastian Kürten
d3cf3d680e Display error message toast when updating profile picture fails 2021-01-21 09:33:55 -03:00
Sebastian Kürten
cbb87aa00c Move compression of image to IoExecutor 2021-01-21 09:33:55 -03:00
Sebastian Kürten
53d985161f Remove layout_gravity without any effect 2021-01-21 09:33:54 -03:00
Sebastian Kürten
86002b0402 Move some findViewById() out of a lambda 2021-01-21 09:33:54 -03:00
Sebastian Kürten
f75e789493 Improve dialog for avatar confirmation 2021-01-21 09:33:54 -03:00
Sebastian Kürten
b22f302fdd Statically import Level.WARNING 2021-01-21 09:33:53 -03:00
Sebastian Kürten
c4a42760c8 Use BriarDialogTheme for avatar confirmation 2021-01-21 09:33:53 -03:00
Sebastian Kürten
8d92f36522 Remove some useless tools:text 2021-01-21 09:33:53 -03:00
Sebastian Kürten
6c86873ea7 Reduce margin verbosity in SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
4fa9d654b5 Eliminate NestedScrollView from SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
3d303ccad5 Natural order of views in SettingsActivity 2021-01-21 09:33:52 -03:00
Sebastian Kürten
b0d99a9f33 Avoid staircase indent 2021-01-21 09:33:51 -03:00
Sebastian Kürten
1a5e789bec Call loadOwnIdentityInfo() in SettingsViewModel's constructor 2021-01-21 09:33:51 -03:00
Sebastian Kürten
97040c6299 Remove a useless method call 2021-01-21 09:33:51 -03:00
Sebastian Kürten
301085c685 Move findViewById() out of callback 2021-01-21 09:33:50 -03:00
Sebastian Kürten
946c79d918 Be consequent with AlertDialog.Builder method usage 2021-01-21 09:33:50 -03:00
Sebastian Kürten
20418cfc7f Rename inflater variable 2021-01-21 09:33:50 -03:00
Sebastian Kürten
7b09f0f98d Rename a string 2021-01-21 09:33:49 -03:00
Sebastian Kürten
97a7c8824b Replace usage of UnsupportedMimeTypeException from jsoup with own type 2021-01-21 09:33:49 -03:00
Sebastian Kürten
423684a14f Reduce visibility of SettingsViewModel 2021-01-21 09:33:48 -03:00
Sebastian Kürten
09d91b522f Fix a warning in SettingsActvitiy 2021-01-21 09:33:48 -03:00
Sebastian Kürten
64c0e9e9e4 Fix a few warnings in ConfirmAvatarDialogFragment 2021-01-21 09:33:48 -03:00
Sebastian Kürten
15021bffef Inline getAttachmentFileIntent() 2021-01-21 09:33:48 -03:00
Sebastian Kürten
43c6ae4258 Implement UI for setting profile pictures 2021-01-21 09:33:47 -03:00
Sebastian Kürten
f819930570 Create ImageCompressor amd ImageCompressorImpl
* Methods from AttachmentCreationTask have been moved into them:
  * compressImage()
  * createBitmap()
* ImageCompressor is availabe via AttachmentModule
2021-01-21 09:33:47 -03:00
Torsten Grote
aa00ba7220 test avatars: get rid of the 1% 2021-01-21 09:33:47 -03:00
Torsten Grote
19db58ee19 Allow the user to configure the percentage of test contacts with avatars 2021-01-21 09:33:46 -03:00
Torsten Grote
05f4d63356 Create test avatars when creating test contacts 2021-01-21 09:33:46 -03:00
Torsten Grote
6e5af2d3d3 Create TestAvatarCreator for use in debug builds only 2021-01-21 09:33:46 -03:00
Torsten Grote
00bf1eac0a Factor out MessageEncoder from AvatarManager 2021-01-21 09:33:45 -03:00
akwizgran
8a10f16861 Deliver test messages as though they arrived from contacts. 2021-01-21 09:33:45 -03:00
Torsten Grote
9bd7214d1d Make AuthorManager volatile as it is accessed from DbThread 2021-01-21 09:33:45 -03:00
Torsten Grote
fce1247aa6 Add a shortcut for setting avatar with ContactItem 2021-01-21 09:33:44 -03:00
Torsten Grote
990f983ea9 Evict Glide memory cache in a low mem situation 2021-01-21 09:33:44 -03:00
Torsten Grote
6e57d7bb42 Show avatars for contacts outside AuthorView 2021-01-21 09:33:38 -03:00
Torsten Grote
1b0cb532de Show Avatars in AuthorView 2021-01-21 09:20:03 -03:00
Torsten Grote
fe7121b4ec Turn AttachmentReader into a proper class
and inject it where needed
2021-01-21 09:20:02 -03:00
Torsten Grote
5aa041f9e1 Add AuthorManager#getMyAuthorInfo() without transaction
and add test for it
2021-01-21 09:20:02 -03:00
Torsten Grote
6939d8d230 Upgrade glide to latest stable version 2021-01-21 09:20:02 -03:00
Torsten Grote
c3cea37641 Add AttachmentHeader to AuthorInfo
This way the UI can retrieve the author's avatar (if it exists).
2021-01-21 09:20:01 -03:00
Torsten Grote
d0d2e0ed82 Centralize attachment loading in AttachmentReader
This is needed so Glide can load attachments from the DB by using the same AttachmentHeader class.
2021-01-21 09:20:01 -03:00
Torsten Grote
cf8f5c989f Move AuthorInfo from bramble to briar 2021-01-21 09:20:01 -03:00
Torsten Grote
8b45e01c42 Split up AvatarManagerImplTests 2021-01-21 09:20:00 -03:00
Torsten Grote
ec972e8a1d Handle concurrent updates of our avatar 2021-01-21 09:20:00 -03:00
Torsten Grote
100791c3f3 Don't accept incoming messages in our own avatar group 2021-01-21 09:19:59 -03:00
Torsten Grote
83ac866cc1 Implement AvatarManager with unit and integration tests 2021-01-21 09:19:59 -03:00
Torsten Grote
ef9b22670d Factor our attachment classes and constants
because they will be used by more than one client
2021-01-21 09:19:59 -03:00
Torsten Grote
186ac30f37 Use metadata constants in TransportPropertyValidator 2021-01-21 09:19:56 -03:00
Torsten Grote
5aa24414c6 Merge branch '1867-viewmodel-for-contactlistfragment' into 'master'
Introduce ViewModel for ContactListFragment

Closes #1867

See merge request briar/briar!1341
2021-01-18 13:12:43 +00:00
Sebastian Kürten
dd6d72ed30 Introduce ViewModel for ContactListFragment 2021-01-18 14:01:48 +01:00
akwizgran
4344be2ca0 Merge branch '1753-wake-lock' into 'master'
Only query for allowed packages in AndroidWakeLockManager

Closes #1753

See merge request briar/briar!1332
2021-01-12 11:21:19 +00:00
akwizgran
1e94af3ef3 Merge branch 'screenshots-api29' into 'master'
Fix screenshot instrumentation tests on API 29+

See merge request briar/briar!1333
2021-01-11 17:58:19 +00:00
Torsten Grote
cb69340749 Merge branch 'move-version-numbers-back-to-modules' into 'master'
Move version constants back into modules so F-Droid can find them

See merge request briar/briar!1338
2021-01-11 16:58:24 +00:00
akwizgran
f3d068414b Move version constants back into modules so F-Droid can find them.
This reverts commit de9c6d44, except that the version numbers have
increased in the meantime.
2021-01-11 16:46:41 +00:00
Torsten Grote
dd3c19aba2 Fix screenshot instrumentation tests on API 29+ 2021-01-05 14:40:25 -03:00
Torsten Grote
e8ede55422 Only query for allowed packages in AndroidWakeLockManager 2021-01-05 14:11:00 -03:00
326 changed files with 5298 additions and 1749 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>

24
.idea/runConfigurations/BridgeTest.xml generated Normal file
View File

@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar.bramble-java" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<envs>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</envs>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -5,14 +5,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle' apply from: 'witness.gradle'
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion 30
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion 29
versionCode rootProject.ext.versionCode versionCode 10214
versionName rootProject.ext.versionName versionName "1.2.14"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.system;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.PowerManager; import android.os.PowerManager;
@@ -106,14 +105,21 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager {
private String getWakeLockTag(Context ctx) { private String getWakeLockTag(Context ctx) {
PackageManager pm = ctx.getPackageManager(); PackageManager pm = ctx.getPackageManager();
for (PackageInfo info : pm.getInstalledPackages(0)) { if (isInstalled(pm, "com.huawei.powergenie")) {
String name = info.packageName.toLowerCase(); return "LocationManagerService";
if (name.startsWith("com.huawei.powergenie")) { } else if (isInstalled(pm, "com.evenwell.PowerMonitor")) {
return "LocationManagerService"; return "AudioIn";
} else if (name.startsWith("com.evenwell.powermonitor")) {
return "AudioIn";
}
} }
return ctx.getPackageName(); return ctx.getPackageName();
} }
private boolean isInstalled(PackageManager pm, String packageName) {
try {
pm.getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
} }

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;
@@ -179,6 +178,11 @@ public interface ContactManager {
*/ */
Collection<Contact> getContacts() throws DbException; Collection<Contact> getContacts() throws DbException;
/**
* Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/** /**
* Removes a contact and all associated state. * Removes a contact and all associated state.
*/ */
@@ -215,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
@@ -213,6 +207,11 @@ class ContactManagerImpl implements ContactManager, EventListener {
return db.transactionWithResult(true, db::getContacts); return db.transactionWithResult(true, db::getContacts);
} }
@Override
public Collection<Contact> getContacts(Transaction txn) throws DbException {
return db.getContacts(txn);
}
@Override @Override
public void removeContact(ContactId c) throws DbException { public void removeContact(ContactId c) throws DbException {
db.transaction(false, txn -> removeContact(txn, c)); db.transaction(false, txn -> removeContact(txn, c));
@@ -256,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

@@ -1,5 +1,14 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0 Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw iat-mode=0 Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
Bridge obfs4 13.58.29.242:9443 0C58939A77DA6B6B29D4B5236A75865659607AE0 cert=OylWIEHb/ezpq1zWxW0sgKRn+9ARH2eOcQOZ8/Gew+4l+oKOhQ2jUX/Y+FSl61JorXZUWA iat-mode=0
Bridge obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0
Bridge meek_lite 0.0.2.0:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

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

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" /></svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -16,15 +16,17 @@ def getStdout = { command, defaultValue ->
} }
android { android {
compileSdkVersion rootProject.ext.compileSdkVersion compileSdkVersion 30
buildToolsVersion rootProject.ext.buildToolsVersion buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion 16
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion 29
versionCode rootProject.ext.versionCode versionCode 10214
versionName rootProject.ext.versionName versionName "1.2.14"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
def now = (long) (System.currentTimeMillis() / 1000) def now = (long) (System.currentTimeMillis() / 1000)
@@ -108,7 +110,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
@@ -142,11 +144,9 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24" androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0' androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'junit:junit:4.13.1'
androidTestScreenshotImplementation('tools.fastlane:screengrab:1.2.0') { androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.0.0'
// workaround for jetifier issue https://issuetracker.google.com/issues/123060356 androidTestScreenshotImplementation 'com.jraska:falcon:2.1.1'
exclude group: 'com.android.support.test.uiautomator'
}
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
} }

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

@@ -453,6 +453,8 @@
<queries> <queries>
<package android:name="info.guardianproject.ripple" /> <package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" /> <package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

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

@@ -255,7 +255,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
R.string.ongoing_notification_title; R.string.ongoing_notification_title;
int text = locked ? R.string.lock_tap_to_unlock : int text = locked ? R.string.lock_tap_to_unlock :
R.string.ongoing_notification_text; R.string.ongoing_notification_text;
int icon = locked ? R.drawable.startup_lock : int icon = locked ? R.drawable.notification_lock :
R.drawable.notification_ongoing; R.drawable.notification_ongoing;
// Ongoing foreground notification that shows BriarService is running // Ongoing foreground notification that shows BriarService is running
NotificationCompat.Builder b = NotificationCompat.Builder b =
@@ -624,7 +624,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b = NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID); new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID);
b.setSmallIcon(R.drawable.ic_signout); b.setSmallIcon(R.drawable.notification_signout);
b.setColor(getColor(appContext, R.color.briar_primary)); b.setColor(getColor(appContext, R.color.briar_primary));
b.setContentTitle( b.setContentTitle(
appContext.getText(R.string.reminder_notification_title)); appContext.getText(R.string.reminder_notification_title));

View File

@@ -27,18 +27,24 @@ import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory; import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.DozeHelperModule;
import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.forum.ForumModule; 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;
@@ -63,11 +69,15 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = { @Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class, ContactExchangeModule.class,
LoginModule.class, LoginModule.class,
NavDrawerModule.class, NavDrawerModule.class,
ViewModelModule.class, ViewModelModule.class,
SettingsModule.class,
DevReportModule.class, DevReportModule.class,
ContactListModule.class,
// below need to be within same scope as ViewModelProvider.Factory // below need to be within same scope as ViewModelProvider.Factory
ForumModule.BindsModule.class, ForumModule.BindsModule.class,
GroupListModule.class, GroupListModule.class,
@@ -182,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()
@@ -245,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

@@ -81,8 +81,7 @@ public class AuthorNameFragment extends SetupFragment {
public void onClick(View view) { public void onClick(View view) {
Editable text = authorNameInput.getText(); Editable text = authorNameInput.getText();
if (text != null) { if (text != null) {
setupController.setAuthorName(text.toString().trim()); viewModel.setAuthorName(text.toString().trim());
setupController.showPasswordFragment();
} }
} }

View File

@@ -104,9 +104,15 @@ public class DozeFragment extends SetupFragment
@Override @Override
public void onClick(View view) { public void onClick(View view) {
next.setVisibility(INVISIBLE); setNextClicked();
progressBar.setVisibility(VISIBLE); viewModel.dozeExceptionConfirmed();
setupController.createAccount();
} }
@Override
void setNextClicked() {
super.setNextClicked();
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
} }

View File

@@ -0,0 +1,8 @@
package org.briarproject.briar.android.account;
import android.content.Context;
interface DozeHelper {
boolean needToShowDozeFragment(Context context);
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import static org.briarproject.briar.android.account.HuaweiView.needsToBeShown;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
class DozeHelperImpl implements DozeHelper {
@Override
public boolean needToShowDozeFragment(Context context) {
return needsDozeWhitelisting(context.getApplicationContext()) ||
needsToBeShown(context.getApplicationContext());
}
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.briar.android.account;
import dagger.Module;
import dagger.Provides;
@Module
public class DozeHelperModule {
@Provides
DozeHelper provideDozeHelper() {
return new DozeHelperImpl();
}
}

View File

@@ -30,7 +30,6 @@ import static org.briarproject.briar.android.util.UiUtils.setError;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SetPasswordFragment extends SetupFragment { public class SetPasswordFragment extends SetupFragment {
private final static String TAG = SetPasswordFragment.class.getName(); private final static String TAG = SetPasswordFragment.class.getName();
private TextInputLayout passwordEntryWrapper; private TextInputLayout passwordEntryWrapper;
@@ -56,7 +55,7 @@ public class SetPasswordFragment extends SetupFragment {
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro)); requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container, View v = inflater.inflate(R.layout.fragment_setup_password, container,
false); false);
strengthMeter = v.findViewById(R.id.strength_meter); strengthMeter = v.findViewById(R.id.strength_meter);
passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper);
@@ -71,7 +70,7 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.addTextChangedListener(this); passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this); nextButton.setOnClickListener(this);
if (!setupController.needToShowDozeFragment()) { if (!viewModel.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button); nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE); passwordConfirmation.setImeOptions(IME_ACTION_DONE);
} }
@@ -97,7 +96,7 @@ public class SetPasswordFragment extends SetupFragment {
strengthMeter strengthMeter
.setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE);
float strength = setupController.estimatePasswordStrength(password1); float strength = viewModel.estimatePasswordStrength(password1);
strengthMeter.setStrength(strength); strengthMeter.setStrength(strength);
boolean strongEnough = strength >= QUITE_WEAK; boolean strongEnough = strength >= QUITE_WEAK;
@@ -117,14 +116,20 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setupController.setPassword(passwordEntry.getText().toString());
if (setupController.needToShowDozeFragment()) { setNextClicked();
setupController.showDozeFragment(); viewModel.setPassword(passwordEntry.getText().toString());
} else {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
setupController.createAccount();
}
} }
@Override
void setNextClicked() {
super.setNextClicked();
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
if (!viewModel.needToShowDozeFragment()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}
} }

View File

@@ -4,7 +4,6 @@ import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.bramble.api.account.AccountManager;
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.R; import org.briarproject.briar.R;
@@ -15,28 +14,36 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SetupActivity extends BaseActivity public class SetupActivity extends BaseActivity
implements BaseFragmentListener { implements BaseFragmentListener {
private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password";
@Inject @Inject
AccountManager accountManager; ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Inject @Override
SetupController setupController; public void injectActivity(ActivityComponent component) {
component.inject(this);
@Nullable viewModel = new ViewModelProvider(this, viewModelFactory)
private String authorName, password; .get(SetupViewModel.class);
viewModel.getState().observeEvent(this, this::onStateChanged);
}
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
@@ -44,58 +51,27 @@ public class SetupActivity extends BaseActivity
// fade-in after splash screen instead of default animation // fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out); overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
}
if (state == null) { private void onStateChanged(SetupViewModel.State state) {
if (accountManager.accountExists()) throw new AssertionError(); if (state == AUTHOR_NAME) {
showInitialFragment(AuthorNameFragment.newInstance()); showInitialFragment(AuthorNameFragment.newInstance());
} else { } else if (state == SET_PASSWORD) {
authorName = state.getString(STATE_KEY_AUTHOR_NAME); showPasswordFragment();
password = state.getString(STATE_KEY_PASSWORD); } else if (state == DOZE) {
showDozeFragment();
} else if (state == CREATED || state == FAILED) {
// TODO: Show an error if failed
showApp();
} }
} }
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
setupController.setSetupActivity(this);
}
@Override
protected void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (authorName != null)
state.putString(STATE_KEY_AUTHOR_NAME, authorName);
if (password != null)
state.putString(STATE_KEY_PASSWORD, password);
}
@Nullable
String getAuthorName() {
return authorName;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
}
@Nullable
String getPassword() {
return password;
}
void setPassword(String password) {
this.password = password;
}
void showPasswordFragment() { void showPasswordFragment() {
if (authorName == null) throw new IllegalStateException();
showNextFragment(SetPasswordFragment.newInstance()); showNextFragment(SetPasswordFragment.newInstance());
} }
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { void showDozeFragment() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
showNextFragment(DozeFragment.newInstance()); showNextFragment(DozeFragment.newInstance());
} }

View File

@@ -1,34 +0,0 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface SetupController {
void setSetupActivity(SetupActivity setupActivity);
boolean needToShowDozeFragment();
void setAuthorName(String authorName);
float estimatePasswordStrength(String password);
void setPassword(String password);
/**
* This should be called after the author name has been set.
*/
void showPasswordFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void showDozeFragment();
/**
* This should be called after the author name and the password have been
* set.
*/
void createAccount();
}

View File

@@ -1,115 +0,0 @@
package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
@NotNullByDefault
public class SetupControllerImpl implements SetupController {
private static final Logger LOG =
Logger.getLogger(SetupControllerImpl.class.getName());
private final AccountManager accountManager;
private final PasswordStrengthEstimator strengthEstimator;
@IoExecutor
private final Executor ioExecutor;
@Nullable
private volatile SetupActivity setupActivity;
@Inject
SetupControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.strengthEstimator = strengthEstimator;
this.ioExecutor = ioExecutor;
}
@Override
public void setSetupActivity(SetupActivity setupActivity) {
this.setupActivity = setupActivity;
}
@Override
public boolean needToShowDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
return DozeView.needsToBeShown(setupActivity) ||
HuaweiView.needsToBeShown(setupActivity);
}
@Override
public void setAuthorName(String authorName) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setAuthorName(authorName);
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void setPassword(String password) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.setPassword(password);
}
@Override
public void showPasswordFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showPasswordFragment();
}
@Override
public void showDozeFragment() {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
setupActivity.showDozeFragment();
}
@Override
public void createAccount() {
SetupActivity setupActivity = this.setupActivity;
UiResultHandler<Boolean> resultHandler =
new UiResultHandler<Boolean>(setupActivity) {
@Override
public void onResultUi(Boolean result) {
// TODO: Show an error if result is false
if (setupActivity == null)
throw new IllegalStateException();
setupActivity.showApp();
}
};
createAccount(resultHandler);
}
// Package access for testing
void createAccount(ResultHandler<Boolean> resultHandler) {
SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException();
String authorName = setupActivity.getAuthorName();
if (authorName == null) throw new IllegalStateException();
String password = setupActivity.getPassword();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
LOG.info("Creating account");
resultHandler.onResult(accountManager.createAccount(authorName,
password));
});
}
}

View File

@@ -1,11 +1,13 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
@@ -17,7 +19,10 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
@@ -29,8 +34,40 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
abstract class SetupFragment extends BaseFragment implements TextWatcher, abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener { OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject @Inject
SetupController setupController; ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(requireActivity())
.get(SetupViewModel.class);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
}
if (clicked) {
setNextClicked();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_KEY_CLICKED, clicked);
}
@CallSuper
void setNextClicked() {
this.clicked = true;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.account;
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 SetupModule {
@Binds
@IntoMap
@ViewModelKey(SetupViewModel.class)
abstract ViewModel bindSetupViewModel(
SetupViewModel setupViewModel);
}

View File

@@ -0,0 +1,110 @@
package org.briarproject.briar.android.account;
import android.app.Application;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class SetupViewModel extends AndroidViewModel {
enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED}
private static final Logger LOG =
getLogger(SetupActivity.class.getName());
@Nullable
private String authorName, password;
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
private final AccountManager accountManager;
private final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
private final DozeHelper dozeHelper;
@Inject
SetupViewModel(Application app,
AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator,
DozeHelper dozeHelper) {
super(app);
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
this.dozeHelper = dozeHelper;
ioExecutor.execute(() -> {
if (accountManager.accountExists()) {
throw new AssertionError();
} else {
state.postEvent(AUTHOR_NAME);
}
});
}
LiveEvent<State> getState() {
return state;
}
void setAuthorName(String authorName) {
this.authorName = authorName;
state.setEvent(SET_PASSWORD);
}
void setPassword(String password) {
if (authorName == null) throw new IllegalStateException();
this.password = password;
if (needToShowDozeFragment()) {
state.setEvent(DOZE);
} else {
createAccount();
}
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
boolean needToShowDozeFragment() {
return dozeHelper.needToShowDozeFragment(getApplication());
}
void dozeExceptionConfirmed() {
createAccount();
}
private void createAccount() {
if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException();
ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account");
state.postEvent(CREATED);
} else {
LOG.warning("Failed to create account");
state.postEvent(FAILED);
}
});
}
}

View File

@@ -21,7 +21,6 @@ import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity; import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
@@ -68,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;
@@ -88,7 +88,6 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
ActivityModule.class, ActivityModule.class,
BlogModule.class, BlogModule.class,
ContactModule.class,
CreateGroupModule.class, CreateGroupModule.class,
ForumModule.class, ForumModule.class,
GroupInvitationModule.class, GroupInvitationModule.class,
@@ -241,4 +240,6 @@ public interface ActivityComponent {
void inject(CrashFragment crashFragment); void inject(CrashFragment crashFragment);
void inject(ConfirmAvatarDialogFragment fragment);
} }

View File

@@ -2,8 +2,6 @@ package org.briarproject.briar.android.activity;
import android.app.Activity; import android.app.Activity;
import org.briarproject.briar.android.account.SetupController;
import org.briarproject.briar.android.account.SetupControllerImpl;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbController;
@@ -35,13 +33,6 @@ public class ActivityModule {
return activity; return activity;
} }
@ActivityScope
@Provides
SetupController provideSetupController(
SetupControllerImpl setupController) {
return setupController;
}
@ActivityScope @ActivityScope
@Provides @Provides
protected BriarController provideBriarController( protected BriarController provideBriarController(

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,22 +2,26 @@ 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.NotThreadSafe; import javax.annotation.concurrent.Immutable;
@NotThreadSafe @Immutable
@NotNullByDefault @NotNullByDefault
public class ContactItem { public class ContactItem {
private final Contact contact; private final Contact contact;
private boolean connected; private final AuthorInfo authorInfo;
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,12 +29,12 @@ public class ContactItem {
return contact; return contact;
} }
public AuthorInfo getAuthorInfo() {
return authorInfo;
}
boolean isConnected() { boolean isConnected() {
return connected; return connected;
} }
void setConnected(boolean connected) {
this.connected = 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

@@ -1,50 +1,73 @@
package org.briarproject.briar.android.contact; package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; 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 androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
@NotNullByDefault @NotNullByDefault
public class ContactListAdapter extends public class ContactListAdapter extends
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> { ListAdapter<ContactListItem, ContactListItemViewHolder> {
public ContactListAdapter(Context context, // TODO: using the click listener interface from BaseContactListAdapter on
// purpose here because it is entangled with ContactListItemViewHolder. At
// some point we probably want to change that.
protected final OnContactClickListener<ContactListItem> listener;
public ContactListAdapter(
OnContactClickListener<ContactListItem> listener) { OnContactClickListener<ContactListItem> listener) {
super(context, ContactListItem.class, listener); super(new ContactListCallback());
this.listener = listener;
}
@NotNullByDefault
private static class ContactListCallback
extends ItemCallback<ContactListItem> {
@Override
public boolean areItemsTheSame(ContactListItem c1, ContactListItem c2) {
return c1.getContact().equals(c2.getContact());
}
@Override
public boolean areContentsTheSame(ContactListItem c1,
ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
if (c1.isConnected() != c2.isConnected()) {
return false;
}
return NullSafety.equals(c1.getAuthorInfo().getAvatarHeader(),
c2.getAuthorInfo().getAvatarHeader());
}
} }
@Override @Override
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) { int viewType) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate( View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact, viewGroup, false); R.layout.list_item_contact, viewGroup, false);
return new ContactListItemViewHolder(v); return new ContactListItemViewHolder(v);
} }
@Override @Override
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) { public void onBindViewHolder(ContactListItemViewHolder viewHolder,
// check for all properties that influence visual int position) {
// representation of contact viewHolder.bind(getItem(position), listener);
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
} }
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
}
} }

View File

@@ -10,23 +10,9 @@ import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
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.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -37,55 +23,35 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.core.app.ActivityCompat; import androidx.lifecycle.ViewModelProvider;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial; import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener; import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener, public class ContactListFragment extends BaseFragment
OnMenuItemClickListener { implements OnMenuItemClickListener,
OnContactClickListener<ContactListItem> {
public static final String TAG = ContactListFragment.class.getName(); public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
ConnectionRegistry connectionRegistry; ViewModelProvider.Factory viewModelFactory;
@Inject
EventBus eventBus;
@Inject
AndroidNotificationManager notificationManager;
private ContactListAdapter adapter; private ContactListViewModel viewModel;
private final ContactListAdapter adapter = new ContactListAdapter(this);
private BriarRecyclerView list; private BriarRecyclerView list;
/** /**
* The Snackbar is non-null when shown and null otherwise. * The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact. * Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
@@ -93,12 +59,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@Nullable @Nullable
private Snackbar snackbar = null; private Snackbar snackbar = null;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactManager contactManager;
@Inject
volatile ConversationManager conversationManager;
public static ContactListFragment newInstance() { public static ContactListFragment newInstance() {
Bundle args = new Bundle(); Bundle args = new Bundle();
ContactListFragment fragment = new ContactListFragment(); ContactListFragment fragment = new ContactListFragment();
@@ -114,6 +74,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ContactListViewModel.class);
} }
@Nullable @Nullable
@@ -129,37 +91,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial); FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
speedDial.addOnMenuItemClickListener(this); speedDial.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
Intent i = new Intent(getActivity(),
ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
if (SDK_INT >= 23 && !isSamsung7()) {
ContactListItemViewHolder holder =
(ContactListItemViewHolder) list
.getRecyclerView()
.findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create(holder.avatar,
getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create(holder.bulb,
getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} else {
// work-around for android bug #224270
startActivity(i);
}
};
adapter = new ContactListAdapter(requireContext(),
onContactClickListener);
list = contentView.findViewById(R.id.list); list = contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(requireContext())); list.setLayoutManager(new LinearLayoutManager(requireContext()));
list.setAdapter(adapter); list.setAdapter(adapter);
@@ -167,9 +98,30 @@ public class ContactListFragment extends BaseFragment implements EventListener,
list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action)); list.setEmptyAction(getString(R.string.no_contacts_action));
viewModel.getContactListItems()
.observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
});
});
viewModel.getHasPendingContacts()
.observe(getViewLifecycleOwner(), hasPending -> {
if (hasPending) showSnackBar();
else dismissSnackBar();
});
return contentView; return contentView;
} }
@Override
public void onItemClick(View view, ContactListItem item) {
Intent i = new Intent(getActivity(), ConversationActivity.class);
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
startActivity(i);
}
@Override @Override
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v, public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) { int itemId) {
@@ -188,131 +140,20 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this); viewModel.clearAllContactNotifications();
notificationManager.clearAllContactNotifications(); viewModel.clearAllContactAddedNotifications();
notificationManager.clearAllContactAddedNotifications(); viewModel.loadContacts();
loadContacts(); viewModel.checkForPendingContacts();
checkForPendingContacts();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar);
} else {
runOnUiThreadUnlessDestroyed(this::showSnackBar);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
eventBus.removeListener(this);
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
dismissSnackBar(); dismissSnackBar();
} }
private void loadContacts() {
int revision = adapter.getRevision();
listener.runOnDbThread(() -> {
try {
long start = now();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts()) {
try {
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
} catch (NoSuchContactException e) {
// Continue
}
}
logDuration(LOG, "Full load", start);
displayContacts(revision, contacts);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayContacts(int revision, List<ContactListItem> contacts) {
runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (contacts.isEmpty()) list.showData();
else adapter.replaceAll(contacts);
} else {
LOG.info("Concurrent update, reloading");
loadContacts();
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
}
}
@UiThread
private void updateItem(ContactId c, ConversationMessageHeader h) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.addMessage(h);
adapter.updateItemAt(position, item);
}
}
@UiThread
private void removeItem(ContactId c) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item);
}
@UiThread
private void setConnected(ContactId c, boolean connected) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.setConnected(connected);
adapter.updateItemAt(position, item);
}
}
@UiThread @UiThread
private void showSnackBar() { private void showSnackBar() {
if (snackbar != null) return; if (snackbar != null) return;
@@ -335,5 +176,4 @@ public class ContactListFragment extends BaseFragment implements EventListener,
Intent i = new Intent(getContext(), PendingContactListActivity.class); Intent i = new Intent(getContext(), PendingContactListActivity.class);
startActivity(i); startActivity(i);
} }
} }

View File

@@ -2,31 +2,58 @@ 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.NotThreadSafe; import javax.annotation.concurrent.Immutable;
@NotThreadSafe @Immutable
@NotNullByDefault @NotNullByDefault
public class ContactListItem extends ContactItem { public class ContactListItem extends ContactItem
implements Comparable<ContactListItem> {
private boolean empty; private final boolean empty;
private long timestamp; private final long timestamp;
private 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();
} }
void addMessage(ConversationMessageHeader h) { private ContactListItem(Contact contact, AuthorInfo authorInfo,
empty = false; boolean connected, boolean empty, int unread, long timestamp) {
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp(); super(contact, authorInfo, connected);
if (!h.isRead()) unread++; this.empty = empty;
this.timestamp = timestamp;
this.unread = unread;
}
ContactListItem(ContactListItem item, boolean connected) {
this(item.getContact(), item.getAuthorInfo(), connected, item.empty,
item.unread, item.timestamp);
}
ContactListItem(ContactListItem item, ConversationMessageHeader h) {
this(item.getContact(), item.getAuthorInfo(), item.isConnected(), false,
h.isRead() ? item.unread : item.unread + 1,
Math.max(h.getTimestamp(), item.timestamp));
}
/**
* Creates a new copy of the given item with a new avatar
* referenced by the given attachment header.
*/
ContactListItem(ContactListItem item,
AttachmentHeader attachmentHeader) {
this(item.getContact(), new AuthorInfo(item.getAuthorInfo().getStatus(),
item.getAuthorInfo().getAlias(), attachmentHeader),
item.isConnected(), item.empty, item.unread, item.timestamp);
} }
boolean isEmpty() { boolean isEmpty() {
@@ -41,4 +68,8 @@ public class ContactListItem extends ContactItem {
return unread; return unread;
} }
@Override
public int compareTo(ContactListItem o) {
return Long.compare(o.getTimestamp(), timestamp);
}
} }

View File

@@ -3,11 +3,9 @@ package org.briarproject.briar.android.contact;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.contact.ContactId;
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;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Locale; import java.util.Locale;
@@ -15,7 +13,6 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import static androidx.core.view.ViewCompat.setTransitionName;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
@UiThread @UiThread
@@ -39,7 +36,8 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
// unread count // unread count
int unreadCount = item.getUnreadCount(); int unreadCount = item.getUnreadCount();
if (unreadCount > 0) { if (unreadCount > 0) {
unread.setText(String.format(Locale.getDefault(), "%d", unreadCount)); unread.setText(
String.format(Locale.getDefault(), "%d", unreadCount));
unread.setVisibility(View.VISIBLE); unread.setVisibility(View.VISIBLE);
} else { } else {
unread.setVisibility(View.INVISIBLE); unread.setVisibility(View.INVISIBLE);
@@ -52,10 +50,6 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
long timestamp = item.getTimestamp(); long timestamp = item.getTimestamp();
date.setText(formatDate(date.getContext(), timestamp)); date.setText(formatDate(date.getContext(), timestamp));
} }
ContactId c = item.getContact().getId();
setTransitionName(avatar, UiUtils.getAvatarTransitionName(c));
setTransitionName(bulb, UiUtils.getBulbTransitionName(c));
} }
} }

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.contact;
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 ContactListModule {
@Binds
@IntoMap
@ViewModelKey(ContactListViewModel.class)
abstract ViewModel bindContactListViewModel(
ContactListViewModel contactListViewModel);
}

View File

@@ -0,0 +1,195 @@
package org.briarproject.briar.android.contact;
import android.app.Application;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
class ContactListViewModel extends DbViewModel implements EventListener {
private static final Logger LOG =
getLogger(ContactListViewModel.class.getName());
private final ContactManager contactManager;
private final AuthorManager authorManager;
private final ConversationManager conversationManager;
private final ConnectionRegistry connectionRegistry;
private final EventBus eventBus;
private final AndroidNotificationManager notificationManager;
private final MutableLiveData<LiveResult<List<ContactListItem>>>
contactListItems = new MutableLiveData<>();
private final MutableLiveData<Boolean> hasPendingContacts =
new MutableLiveData<>();
@Inject
ContactListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, TransactionManager db,
AndroidExecutor androidExecutor, ContactManager contactManager,
AuthorManager authorManager,
ConversationManager conversationManager,
ConnectionRegistry connectionRegistry, EventBus eventBus,
AndroidNotificationManager notificationManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.contactManager = contactManager;
this.authorManager = authorManager;
this.conversationManager = conversationManager;
this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus;
this.notificationManager = notificationManager;
this.eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
void loadContacts() {
loadList(this::loadContacts, contactListItems::setValue);
}
private List<ContactListItem> loadContacts(Transaction txn)
throws DbException {
long start = now();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getContacts(txn)) {
ContactId id = c.getId();
AuthorInfo authorInfo = authorManager.getAuthorInfo(txn, c);
MessageTracker.GroupCount count =
conversationManager.getGroupCount(txn, id);
boolean connected = connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, authorInfo, connected, count));
}
Collections.sort(contacts);
logDuration(LOG, "Full load", start);
return contacts;
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
updateItem(((ContactConnectedEvent) e).getContactId(),
item -> new ContactListItem(item, true), false);
} else if (e instanceof ContactDisconnectedEvent) {
updateItem(((ContactDisconnectedEvent) e).getContactId(),
item -> new ContactListItem(item, false), false);
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), item -> new ContactListItem(item, h),
true);
} else if (e instanceof PendingContactAddedEvent ||
e instanceof PendingContactRemovedEvent) {
checkForPendingContacts();
} else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
updateItem(a.getContactId(), item -> new ContactListItem(item,
a.getAttachmentHeader()), false);
}
}
LiveData<LiveResult<List<ContactListItem>>> getContactListItems() {
return contactListItems;
}
LiveData<Boolean> getHasPendingContacts() {
return hasPendingContacts;
}
private void updateItem(ContactId c,
Function<ContactListItem, ContactListItem> replacer, boolean sort) {
List<ContactListItem> list = updateListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c),
replacer);
if (list == null) return;
if (sort) Collections.sort(list);
contactListItems.setValue(new LiveResult<>(list));
}
private void removeItem(ContactId c) {
List<ContactListItem> list = removeListItems(contactListItems,
itemToTest -> itemToTest.getContact().getId().equals(c));
if (list == null) return;
contactListItems.setValue(new LiveResult<>(list));
}
void checkForPendingContacts() {
runOnDbThread(() -> {
try {
boolean hasPending =
!contactManager.getPendingContacts().isEmpty();
hasPendingContacts.postValue(hasPending);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void clearAllContactNotifications() {
notificationManager.clearAllContactNotifications();
}
void clearAllContactAddedNotifications() {
notificationManager.clearAllContactAddedNotifications();
}
}

View File

@@ -1,7 +0,0 @@
package org.briarproject.briar.android.contact;
import dagger.Module;
@Module
public class ContactModule {
}

View File

@@ -0,0 +1,50 @@
package org.briarproject.briar.android.contact;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
@NotNullByDefault
public class LegacyContactListAdapter extends
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
public LegacyContactListAdapter(Context context,
OnContactClickListener<ContactListItem> listener) {
super(context, ContactListItem.class, listener);
}
@Override
public ContactListItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact, viewGroup, false);
return new ContactListItemViewHolder(v);
}
@Override
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
}
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
}
}

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

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.transition.Slide; import android.transition.Slide;
@@ -59,6 +60,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 +73,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;
@@ -105,15 +106,14 @@ import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
import androidx.recyclerview.selection.StorageStrategy; 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 androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
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;
import static android.view.Gravity.RIGHT; import static android.view.Gravity.RIGHT;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.setTransitionName;
import static androidx.lifecycle.Lifecycle.State.STARTED; import static androidx.lifecycle.Lifecycle.State.STARTED;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION; import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static java.util.Collections.sort; import static java.util.Collections.sort;
@@ -134,9 +134,8 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
import static org.briarproject.briar.android.conversation.ImageActivity.DATE; 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.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
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;
@@ -236,10 +235,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);
@@ -252,9 +250,6 @@ public class ConversationActivity extends BriarActivity
viewModel.getAddedPrivateMessage().observeEvent(this, viewModel.getAddedPrivateMessage().observeEvent(this,
this::onAddedPrivateMessage); this::onAddedPrivateMessage);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, this, visitor = new ConversationVisitor(this, this, this,
viewModel.getContactDisplayName()); viewModel.getContactDisplayName());
adapter = new ConversationAdapter(this, this); adapter = new ConversationAdapter(this, this);
@@ -372,7 +367,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);
@@ -486,12 +481,10 @@ public class ConversationActivity extends BriarActivity
@UiThread @UiThread
private void displayContactOnlineStatus() { private void displayContactOnlineStatus() {
if (connectionRegistry.isConnected(contactId)) { if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageDrawable(ContextCompat.getDrawable( toolbarStatus.setImageResource(R.drawable.contact_online);
ConversationActivity.this, R.drawable.contact_online));
toolbarStatus.setContentDescription(getString(R.string.online)); toolbarStatus.setContentDescription(getString(R.string.online));
} else { } else {
toolbarStatus.setImageDrawable(ContextCompat.getDrawable( toolbarStatus.setImageResource(R.drawable.contact_offline);
ConversationActivity.this, R.drawable.contact_offline));
toolbarStatus.setContentDescription(getString(R.string.offline)); toolbarStatus.setContentDescription(getString(R.string.offline));
} }
} }
@@ -942,13 +935,16 @@ public class ConversationActivity extends BriarActivity
return; return;
} }
int color =
ContextCompat.getColor(this, R.color.briar_primary);
Drawable drawable = VectorDrawableCompat
.create(getResources(), R.drawable.ic_more_vert_accent, null);
new MaterialTapTargetPrompt.Builder(ConversationActivity.this, new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
R.style.OnboardingDialogTheme).setTarget(target) R.style.OnboardingDialogTheme).setTarget(target)
.setPrimaryText(R.string.introduction_onboarding_title) .setPrimaryText(R.string.introduction_onboarding_title)
.setSecondaryText(R.string.introduction_onboarding_text) .setSecondaryText(R.string.introduction_onboarding_text)
.setIcon(R.drawable.ic_more_vert_accent) .setIconDrawable(drawable)
.setBackgroundColour( .setBackgroundColour(color)
ContextCompat.getColor(this, R.color.briar_primary))
.show(); .show();
} }

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

@@ -15,12 +15,14 @@ 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.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.ContactListAdapter;
import org.briarproject.briar.android.contact.ContactListItem; import org.briarproject.briar.android.contact.ContactListItem;
import org.briarproject.briar.android.contact.LegacyContactListAdapter;
import org.briarproject.briar.android.fragment.BaseFragment; 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;
@@ -45,14 +47,16 @@ public class ContactChooserFragment extends BaseFragment {
private static final Logger LOG = Logger.getLogger(TAG); private static final Logger LOG = Logger.getLogger(TAG);
private BriarRecyclerView list; private BriarRecyclerView list;
private ContactListAdapter adapter; private LegacyContactListAdapter adapter;
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;
@@ -72,7 +76,8 @@ public class ContactChooserFragment extends BaseFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false); View contentView = inflater.inflate(R.layout.list, container, false);
@@ -83,7 +88,7 @@ public class ContactChooserFragment extends BaseFragment {
Contact c2 = item.getContact(); Contact c2 = item.getContact();
showMessageScreen(c1, c2); showMessageScreen(c1, c2);
}; };
adapter = new ContactListAdapter(requireActivity(), adapter = new LegacyContactListAdapter(requireActivity(),
onContactClickListener); onContactClickListener);
list = contentView.findViewById(R.id.list); list = contentView.findViewById(R.id.list);
@@ -122,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);

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