Compare commits

..

2 Commits

Author SHA1 Message Date
akwizgran
21d6dfe817 Add transactional helper methods to DbViewModel. 2021-01-26 14:52:25 +00:00
akwizgran
c1e83b22c1 Add helper method for running a DB task and logging any exception it throws. 2021-01-26 13:51:03 +00:00
331 changed files with 2048 additions and 6649 deletions

View File

@@ -2,7 +2,6 @@ image: briar/ci-image-android:latest
stages: stages:
- test - test
- optional_tests
- check_reproducibility - check_reproducibility
test: test:
@@ -32,33 +31,3 @@ test_reproducible:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline" - "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only: only:
- tags - tags
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
manual_tests:
extends: .optional_tests
when: manual
except:
- tags
pre_release_tests:
extends: .optional_tests
only:
- tags

View File

@@ -1,24 +0,0 @@
<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

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10214 versionCode 10213
versionName "1.2.14" versionName "1.2.13"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api;
public interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.client; package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
@@ -120,17 +119,4 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap( Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException; BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
/**
* Stores the given contact ID in the group metadata of the given contact
* group.
*/
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
throws DbException;
} }

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api.client;
public interface ContactGroupConstants {
/**
* Group metadata key for associating a contact ID with a contact group.
*/
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -6,9 +6,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault @NotNullByDefault
public class ValidationUtils { public class ValidationUtils {
@@ -66,9 +64,4 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size) if (dictionary != null && dictionary.size() != size)
throw new FormatException(); throw new FormatException();
} }
public static void checkRange(@Nullable Long l, long min, long max)
throws FormatException {
if (l != null && (l < min || l > max)) throw new FormatException();
}
} }

View File

@@ -1,8 +0,0 @@
package org.briarproject.bramble.test;
public interface TimeTravel {
void setCurrentTimeMillis(long now) throws InterruptedException;
void addCurrentTimeMillis(long add) throws InterruptedException;
}

View File

@@ -21,6 +21,7 @@ import org.briarproject.bramble.rendezvous.RendezvousModule;
import org.briarproject.bramble.settings.SettingsModule; import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
@@ -28,6 +29,7 @@ import dagger.Module;
@Module(includes = { @Module(includes = {
ClientModule.class, ClientModule.class,
ClockModule.class,
ConnectionModule.class, ConnectionModule.class,
ContactModule.class, ContactModule.class,
CryptoModule.class, CryptoModule.class,

View File

@@ -2,13 +2,11 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -41,7 +39,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION; import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
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.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -392,27 +389,4 @@ class ClientHelperImpl implements ClientHelper {
return tpMap; return tpMap;
} }
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
}
@Override
public void setContactId(Transaction txn, GroupId contactGroupId,
ContactId c) throws DbException {
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
try {
mergeGroupMetadata(txn, contactGroupId, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.sync;
interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -5,5 +5,6 @@ interface ClientVersioningConstants {
// Metadata keys // Metadata keys
String MSG_KEY_UPDATE_VERSION = "version"; String MSG_KEY_UPDATE_VERSION = "version";
String MSG_KEY_LOCAL = "local"; String MSG_KEY_LOCAL = "local";
String GROUP_KEY_CONTACT_ID = "contactId";
} }

View File

@@ -50,6 +50,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -160,7 +161,13 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g); db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED); db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group // Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId()); BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Create and store the first local update // Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients); List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions); Collections.sort(versions);
@@ -222,7 +229,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after = Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates); getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed // Call hooks for any visibilities that have changed
ContactId c = clientHelper.getContactId(txn, m.getGroupId()); ContactId c = getContactId(txn, m.getGroupId());
if (!before.equals(after)) { if (!before.equals(after)) {
Contact contact = db.getContact(txn, c); Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after); callVisibilityHooks(txn, contact, before, after);
@@ -514,6 +521,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
storeUpdate(txn, g, states, 1); storeUpdate(txn, g, states, 1);
} }
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientState> updateStatesFromRemoteStates( private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) { List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>(); Set<ClientMajorVersion> remoteSet = new HashSet<>();

View File

@@ -1,14 +1,5 @@
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 meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw iat-mode=0
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

@@ -1,18 +1,18 @@
package org.briarproject.bramble; package org.briarproject.bramble;
import org.briarproject.bramble.system.TimeTravelModule; import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
public interface BrambleCoreIntegrationTestEagerSingletons public interface BrambleCoreIntegrationTestEagerSingletons
extends BrambleCoreEagerSingletons { extends BrambleCoreEagerSingletons {
void inject(TimeTravelModule.EagerSingletons init); void inject(DefaultTaskSchedulerModule.EagerSingletons init);
class Helper { class Helper {
public static void injectEagerSingletons( public static void injectEagerSingletons(
BrambleCoreIntegrationTestEagerSingletons c) { BrambleCoreIntegrationTestEagerSingletons c) {
BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c); BrambleCoreEagerSingletons.Helper.injectEagerSingletons(c);
c.inject(new TimeTravelModule.EagerSingletons()); c.inject(new DefaultTaskSchedulerModule.EagerSingletons());
} }
} }
} }

View File

@@ -1,122 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.fail;
/**
* A {@link TaskScheduler} for use in tests. The scheduler keeps all scheduled
* tasks in a queue until {@link #runTasks()} is called.
*/
@NotNullByDefault
class TestTaskScheduler implements TaskScheduler {
private final Queue<Task> queue = new PriorityBlockingQueue<>();
private final Clock clock;
TestTaskScheduler(Clock clock) {
this.clock = clock;
}
@Override
public Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return schedule(task, executor, delay, unit, cancelled);
}
@Override
public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit) {
AtomicBoolean cancelled = new AtomicBoolean(false);
return scheduleWithFixedDelay(task, executor, delay, interval, unit,
cancelled);
}
private Cancellable schedule(Runnable task, Executor executor, long delay,
TimeUnit unit, AtomicBoolean cancelled) {
long delayMillis = MILLISECONDS.convert(delay, unit);
long dueMillis = clock.currentTimeMillis() + delayMillis;
Task t = new Task(task, executor, dueMillis, cancelled);
queue.add(t);
return t;
}
private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor,
long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) {
// All executions of this periodic task share a cancelled flag
Runnable wrapped = () -> {
task.run();
scheduleWithFixedDelay(task, executor, interval, interval, unit,
cancelled);
};
return schedule(wrapped, executor, delay, unit, cancelled);
}
/**
* Runs any scheduled tasks that are due.
*/
void runTasks() throws InterruptedException {
long now = clock.currentTimeMillis();
while (true) {
Task t = queue.peek();
if (t == null || t.dueMillis > now) return;
t = queue.poll();
// Submit the task to its executor and wait for it to finish
if (!t.run().await(1, MINUTES)) fail();
}
}
private static class Task
implements Cancellable, Comparable<Task> {
private final Runnable task;
private final Executor executor;
private final long dueMillis;
private final AtomicBoolean cancelled;
private Task(Runnable task, Executor executor, long dueMillis,
AtomicBoolean cancelled) {
this.task = task;
this.executor = executor;
this.dueMillis = dueMillis;
this.cancelled = cancelled;
}
@SuppressWarnings("UseCompareMethod") // Animal Sniffer
@Override
public int compareTo(Task task) {
return Long.valueOf(dueMillis).compareTo(task.dueMillis);
}
/**
* Submits the task to its executor and returns a latch that will be
* released when the task finishes.
*/
public CountDownLatch run() {
if (cancelled.get()) return new CountDownLatch(0);
CountDownLatch latch = new CountDownLatch(1);
executor.execute(() -> {
task.run();
latch.countDown();
});
return latch;
}
@Override
public void cancel() {
cancelled.set(true);
}
}
}

View File

@@ -1,98 +0,0 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.test.SettableClock;
import org.briarproject.bramble.test.TimeTravel;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class TimeTravelModule {
public static class EagerSingletons {
@Inject
TaskScheduler scheduler;
}
private final ScheduledExecutorService scheduledExecutorService;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final TimeTravel timeTravel;
public TimeTravelModule() {
this(false);
}
public TimeTravelModule(boolean travel) {
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduledExecutorService =
new ScheduledThreadPoolExecutor(1, policy);
if (travel) {
// Use a SettableClock and TestTaskScheduler to allow time travel
AtomicLong time = new AtomicLong(System.currentTimeMillis());
clock = new SettableClock(time);
TestTaskScheduler testTaskScheduler = new TestTaskScheduler(clock);
taskScheduler = testTaskScheduler;
timeTravel = new TimeTravel() {
@Override
public void setCurrentTimeMillis(long now)
throws InterruptedException {
time.set(now);
testTaskScheduler.runTasks();
}
@Override
public void addCurrentTimeMillis(long add)
throws InterruptedException {
time.addAndGet(add);
testTaskScheduler.runTasks();
}
};
} else {
// Use the default clock and task scheduler
clock = new SystemClock();
taskScheduler = new TaskSchedulerImpl(scheduledExecutorService);
timeTravel = new TimeTravel() {
@Override
public void setCurrentTimeMillis(long now) {
throw new UnsupportedOperationException();
}
@Override
public void addCurrentTimeMillis(long add) {
throw new UnsupportedOperationException();
}
};
}
}
@Provides
Clock provideClock() {
return clock;
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) {
lifecycleManager.registerForShutdown(scheduledExecutorService);
return taskScheduler;
}
@Provides
TimeTravel provideTimeTravel() {
return timeTravel;
}
}

View File

@@ -3,8 +3,8 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.battery.DefaultBatteryManagerModule; import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
import org.briarproject.bramble.event.DefaultEventExecutorModule; import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule; import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -12,11 +12,11 @@ import dagger.Provides;
@Module(includes = { @Module(includes = {
DefaultBatteryManagerModule.class, DefaultBatteryManagerModule.class,
DefaultEventExecutorModule.class, DefaultEventExecutorModule.class,
DefaultTaskSchedulerModule.class,
DefaultWakefulIoExecutorModule.class, DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class, TestDatabaseConfigModule.class,
TestPluginConfigModule.class, TestPluginConfigModule.class,
TestSecureRandomModule.class, TestSecureRandomModule.class
TimeTravelModule.class
}) })
public class BrambleCoreIntegrationTestModule { public class BrambleCoreIntegrationTestModule {

View File

@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -59,6 +60,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId(); private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis(); private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
private ClientVersioningManagerImpl createInstance() { private ClientVersioningManagerImpl createInstance() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -120,8 +123,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup); oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(), oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED); contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroup.getId(), oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
contact.getId()); groupMeta);
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(now)); will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now, oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -457,8 +460,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId); oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID // Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId()); oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
will(returnValue(contact.getId())); contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed // No states or visibilities have changed
}}); }});
@@ -488,9 +492,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update // Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody)); will(returnValue(oldLocalUpdateBody));
// Get contact ID // Get client ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId()); oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
will(returnValue(contact.getId())); contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed // No states or visibilities have changed
}}); }});
@@ -541,6 +546,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of( BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true)); new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate); oneOf(clientHelper).toList(newRemoteUpdate);
@@ -570,8 +577,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false); newLocalUpdateMeta, true, false);
// The client's visibility has changed // The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId()); oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
will(returnValue(contact.getId())); contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -611,6 +619,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of( BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true)); new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate); oneOf(clientHelper).toList(newRemoteUpdate);
@@ -640,8 +650,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false); newLocalUpdateMeta, true, false);
// The client's visibility has changed // The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId()); oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
will(returnValue(contact.getId())); contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId()); oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact)); will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE); oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

@@ -8,9 +8,9 @@ import org.briarproject.bramble.system.JavaSystemModule;
import dagger.Module; import dagger.Module;
@Module(includes = { @Module(includes = {
CircumventionModule.class,
JavaNetworkModule.class, JavaNetworkModule.class,
JavaSystemModule.class, JavaSystemModule.class,
CircumventionModule.class,
SocksModule.class SocksModule.class
}) })
public class BrambleJavaModule { public class BrambleJavaModule {

View File

@@ -53,7 +53,7 @@ public class BridgeTest extends BrambleTestCase {
return component.getCircumventionProvider().getBridges(false); return component.getCircumventionProvider().getBridges(false);
} }
private final static long TIMEOUT = SECONDS.toMillis(60); private final static long TIMEOUT = SECONDS.toMillis(30);
private final static Logger LOG = getLogger(BridgeTest.class.getName()); private final static Logger LOG = getLogger(BridgeTest.class.getName());

View File

@@ -1 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 423 B

View File

@@ -22,11 +22,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10214 versionCode 10213
versionName "1.2.14" versionName "1.2.13"
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)

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.system.ClockModule;
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.attachment.media.MediaModule;
@@ -17,7 +16,6 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
ClockModule.class,
MediaModule.class, MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.system.ClockModule;
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.attachment.media.MediaModule;
@@ -18,7 +17,6 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
ClockModule.class,
MediaModule.class, MediaModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,

View File

@@ -29,7 +29,6 @@ import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule;
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;
@@ -42,7 +41,6 @@ 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.attachment.AttachmentReader;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
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;
@@ -77,7 +75,6 @@ import dagger.Component;
BriarAccountModule.class, BriarAccountModule.class,
AppModule.class, AppModule.class,
AttachmentModule.class, AttachmentModule.class,
ClockModule.class,
MediaModule.class MediaModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
@@ -182,8 +179,6 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager(); AndroidWakeLockManager wakeLockManager();
AutoDeleteManager autoDeleteManager();
void inject(SignInReminderReceiver briarService); void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService); void inject(BriarService briarService);

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.notification_lock : int icon = locked ? R.drawable.startup_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.notification_signout); b.setSmallIcon(R.drawable.ic_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

@@ -36,9 +36,9 @@ 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.privategroup.conversation.GroupConversationModule; import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
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.settings.SettingsModule;
import org.briarproject.briar.android.sharing.SharingModule; import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl; import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
@@ -68,6 +68,7 @@ import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = { @Module(includes = {
SetupModule.class, SetupModule.class,
@@ -268,12 +269,12 @@ public class AppModule {
@Override @Override
public boolean shouldEnableImageAttachments() { public boolean shouldEnableImageAttachments() {
return false; return IS_DEBUG_BUILD;
} }
@Override @Override
public boolean shouldEnableProfilePictures() { public boolean shouldEnableProfilePictures() {
return false; return IS_DEBUG_BUILD;
} }
}; };
} }

View File

@@ -27,7 +27,6 @@ import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment; import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsDialog;
import org.briarproject.briar.android.conversation.ImageActivity; import org.briarproject.briar.android.conversation.ImageActivity;
import org.briarproject.briar.android.conversation.ImageFragment; import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity; import org.briarproject.briar.android.forum.CreateForumActivity;
@@ -239,6 +238,4 @@ public interface ActivityComponent {
void inject(ConfirmAvatarDialogFragment fragment); void inject(ConfirmAvatarDialogFragment fragment);
void inject(ConversationSettingsDialog dialog);
} }

View File

@@ -20,7 +20,6 @@ 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.android.view.TextSendController.SendState;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List; import java.util.List;
@@ -28,9 +27,6 @@ import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static android.view.View.FOCUS_DOWN; import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
@@ -38,7 +34,6 @@ import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID; import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -126,8 +121,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) { List<AttachmentHeader> headers) {
ui.input.hideSoftKeyboard(); ui.input.hideSoftKeyboard();
feedController.repeatPost(item, text, feedController.repeatPost(item, text,
new UiExceptionHandler<DbException>(this) { new UiExceptionHandler<DbException>(this) {
@@ -137,7 +132,6 @@ public class ReblogFragment extends BaseFragment implements SendListener {
} }
}); });
finish(); finish();
return new MutableLiveData<>(SENT);
} }
private void showProgressBar() { private void showProgressBar() {

View File

@@ -31,16 +31,12 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static java.util.logging.Level.WARNING; 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.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.view.TextSendController.SendState;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -116,8 +112,8 @@ public class WriteBlogPostActivity extends BriarActivity
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) { List<AttachmentHeader> headers) {
if (isNullOrEmpty(text)) throw new AssertionError(); if (isNullOrEmpty(text)) throw new AssertionError();
// hide publish button, show progress bar // hide publish button, show progress bar
@@ -126,7 +122,6 @@ public class WriteBlogPostActivity extends BriarActivity
progressBar.setVisibility(VISIBLE); progressBar.setVisibility(VISIBLE);
storePost(text); storePost(text);
return new MutableLiveData<>(SENT);
} }
private void storePost(String text) { private void storePost(String text) {

View File

@@ -45,10 +45,8 @@ import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
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.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.now; import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
@@ -173,14 +171,9 @@ class ContactListViewModel extends DbViewModel implements EventListener {
} }
void checkForPendingContacts() { void checkForPendingContacts() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { boolean hasPending = !contactManager.getPendingContacts().isEmpty();
boolean hasPending = hasPendingContacts.postValue(hasPending);
!contactManager.getPendingContacts().isEmpty();
hasPendingContacts.postValue(hasPending);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -2,12 +2,9 @@ package org.briarproject.briar.android.contact.add.remote;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
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.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -18,7 +15,6 @@ import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -66,15 +62,10 @@ public class AddContactViewModel extends DbViewModel {
} }
private void loadHandshakeLink() { private void loadHandshakeLink() {
runOnDbThread(() -> { // If an exception is thrown the UI should stay disabled,
try { // leaving the user unable to proceed
handshakeLink.postValue(contactManager.getHandshakeLink()); runOnDbThreadOrLogException(() ->
} catch (DbException e) { handshakeLink.postValue(contactManager.getHandshakeLink()));
logException(LOG, WARNING, e);
// the UI should stay disabled in this case,
// leaving the user unable to proceed
}
});
} }
LiveData<String> getHandshakeLink() { LiveData<String> getHandshakeLink() {
@@ -106,17 +97,11 @@ public class AddContactViewModel extends DbViewModel {
void addContact(String nickname) { void addContact(String nickname) {
if (remoteHandshakeLink == null) throw new IllegalStateException(); if (remoteHandshakeLink == null) throw new IllegalStateException();
runOnDbThread(() -> { runOnDbThread(() -> {
try { contactManager.addPendingContact(remoteHandshakeLink, nickname);
contactManager.addPendingContact(remoteHandshakeLink, nickname); addContactResult.postValue(new LiveResult<>(true));
addContactResult.postValue(new LiveResult<>(true)); }, e -> {
} catch (UnsupportedVersionException e) { logException(LOG, WARNING, e);
logException(LOG, WARNING, e); addContactResult.postValue(new LiveResult<>(e));
addContactResult.postValue(new LiveResult<>(e));
} catch (DbException | FormatException
| GeneralSecurityException e) {
logException(LOG, WARNING, e);
addContactResult.postValue(new LiveResult<>(e));
}
}); });
} }
@@ -126,13 +111,13 @@ public class AddContactViewModel extends DbViewModel {
public void updatePendingContact(String name, PendingContact p) { public void updatePendingContact(String name, PendingContact p) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { contactManager.removePendingContact(p.getId());
contactManager.removePendingContact(p.getId()); addContact(name);
addContact(name); }, e -> {
} catch (NoSuchPendingContactException e) { if (e instanceof NoSuchPendingContactException) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// no error in UI as pending contact was converted into contact // no error in UI as pending contact was converted into contact
} catch (DbException e) { } else {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
addContactResult.postValue(new LiveResult<>(e)); addContactResult.postValue(new LiveResult<>(e));
} }

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent; import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent; import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
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.TransactionManager; 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;
@@ -33,10 +32,8 @@ import javax.inject.Inject;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
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.api.contact.PendingContactState.OFFLINE; import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class PendingContactListViewModel extends DbViewModel public class PendingContactListViewModel extends DbViewModel
@@ -90,24 +87,20 @@ public class PendingContactListViewModel extends DbViewModel
} }
private void loadPendingContacts() { private void loadPendingContacts() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Collection<Pair<PendingContact, PendingContactState>> pairs =
Collection<Pair<PendingContact, PendingContactState>> pairs = contactManager.getPendingContacts();
contactManager.getPendingContacts(); List<PendingContactItem> items = new ArrayList<>(pairs.size());
List<PendingContactItem> items = new ArrayList<>(pairs.size()); boolean online = pairs.isEmpty();
boolean online = pairs.isEmpty(); for (Pair<PendingContact, PendingContactState> pair : pairs) {
for (Pair<PendingContact, PendingContactState> pair : pairs) { PendingContact p = pair.getFirst();
PendingContact p = pair.getFirst(); PendingContactState state = pair.getSecond();
PendingContactState state = pair.getSecond(); long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
long lastPoll = rendezvousPoller.getLastPollTime(p.getId()); items.add(new PendingContactItem(p, state, lastPoll));
items.add(new PendingContactItem(p, state, lastPoll)); online = online || state != OFFLINE;
online = online || state != OFFLINE;
}
pendingContacts.postValue(items);
hasInternetConnection.postValue(online);
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
pendingContacts.postValue(items);
hasInternetConnection.postValue(online);
}); });
} }
@@ -116,13 +109,8 @@ public class PendingContactListViewModel extends DbViewModel
} }
void removePendingContact(PendingContactId id) { void removePendingContact(PendingContactId id) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { contactManager.removePendingContact(id));
contactManager.removePendingContact(id);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
LiveData<Boolean> getHasInternetConnection() { LiveData<Boolean> getHasInternetConnection() {

View File

@@ -31,8 +31,6 @@ import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
// TODO: we can probably switch to androidx DialogFragment here but need to
// test this properly
public class AliasDialogFragment extends AppCompatDialogFragment { public class AliasDialogFragment extends AppCompatDialogFragment {
final static String TAG = AliasDialogFragment.class.getName(); final static String TAG = AliasDialogFragment.class.getName();

View File

@@ -3,7 +3,6 @@ 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;
@@ -50,7 +49,6 @@ import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache; import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache; import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity; import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
@@ -60,7 +58,6 @@ import org.briarproject.briar.android.view.TextAttachmentController;
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener; import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener;
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.SendState;
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.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -108,7 +105,6 @@ 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 uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
@@ -140,14 +136,12 @@ import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.view.AuthorView.setAvatar; 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;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity public class ConversationActivity extends BriarActivity
implements BaseFragmentListener, EventListener, ConversationListener, implements EventListener, ConversationListener, TextCache,
TextCache, AttachmentCache, AttachmentListener, ActionMode.Callback { AttachmentCache, AttachmentListener, ActionMode.Callback {
public static final String CONTACT_ID = "briar.CONTACT_ID"; public static final String CONTACT_ID = "briar.CONTACT_ID";
@@ -272,11 +266,15 @@ public class ConversationActivity extends BriarActivity
ImagePreview imagePreview = findViewById(R.id.imagePreview); ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView, sendController = new TextAttachmentController(textInputView,
imagePreview, this, viewModel); imagePreview, this, viewModel);
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> { viewModel.hasImageSupport().observe(this, new Observer<Boolean>() {
if (format != TEXT_ONLY) { @Override
// TODO: remove cast when removing feature flag public void onChanged(@Nullable Boolean hasSupport) {
((TextAttachmentController) sendController) if (hasSupport != null && hasSupport) {
.setImagesSupported(); // TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().removeObserver(this);
}
} }
}); });
} else { } else {
@@ -286,9 +284,6 @@ public class ConversationActivity extends BriarActivity
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH); textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
textInputView.setReady(false); textInputView.setReady(false);
textInputView.setOnKeyboardShownListener(this::scrollToBottom); textInputView.setOnKeyboardShownListener(this::scrollToBottom);
viewModel.getAutoDeleteTimer().observe(this, timer ->
sendController.setAutoDeleteTimer(timer));
} }
private void scrollToBottom() { private void scrollToBottom() {
@@ -372,12 +367,6 @@ public class ConversationActivity extends BriarActivity
// enable alias action if available // enable alias action if available
observeOnce(viewModel.getContactItem(), 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));
// show auto-delete timer setting only, if contacts supports it
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
boolean visible = format == TEXT_IMAGES_AUTO_DELETE;
MenuItem item = menu.findItem(R.id.action_conversation_settings);
item.setVisible(visible);
});
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@@ -399,10 +388,6 @@ public class ConversationActivity extends BriarActivity
AliasDialogFragment.newInstance().show( AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG); getSupportFragmentManager(), AliasDialogFragment.TAG);
return true; return true;
case R.id.action_conversation_settings:
if (contactId == null) return false;
onAutoDeleteTimerNoticeClicked();
return true;
case R.id.action_delete_all_messages: case R.id.action_delete_all_messages:
askToDeleteAllMessages(); askToDeleteAllMessages();
return true; return true;
@@ -494,10 +479,12 @@ public class ConversationActivity extends BriarActivity
@UiThread @UiThread
private void displayContactOnlineStatus() { private void displayContactOnlineStatus() {
if (connectionRegistry.isConnected(contactId)) { if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageResource(R.drawable.contact_online); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_online));
toolbarStatus.setContentDescription(getString(R.string.online)); toolbarStatus.setContentDescription(getString(R.string.online));
} else { } else {
toolbarStatus.setImageResource(R.drawable.contact_offline); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_offline));
toolbarStatus.setContentDescription(getString(R.string.offline)); toolbarStatus.setContentDescription(getString(R.string.offline));
} }
} }
@@ -653,8 +640,8 @@ public class ConversationActivity extends BriarActivity
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
} else if (e instanceof ConversationMessageReceivedEvent) { } else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent<?> p = ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent<?>) e; (ConversationMessageReceivedEvent) e;
if (p.getContactId().equals(contactId)) { if (p.getContactId().equals(contactId)) {
LOG.info("Message received, adding"); LOG.info("Message received, adding");
onNewConversationMessage(p.getMessageHeader()); onNewConversationMessage(p.getMessageHeader());
@@ -748,13 +735,20 @@ public class ConversationActivity extends BriarActivity
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> attachmentHeaders, List<AttachmentHeader> attachmentHeaders) {
long expectedAutoDeleteTimer) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty()) if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError(); throw new AssertionError();
return viewModel long timestamp = System.currentTimeMillis();
.sendMessage(text, attachmentHeaders, expectedAutoDeleteTimer); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, attachmentHeaders, timestamp);
textInputView.clearText();
}
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
ConversationItem item = adapter.getLastItem();
return item == null ? 0 : item.getTime() + 1;
} }
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) { private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
@@ -941,16 +935,13 @@ 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)
.setIconDrawable(drawable) .setIcon(R.drawable.ic_more_vert_accent)
.setBackgroundColour(color) .setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary))
.show(); .show();
} }
@@ -964,11 +955,13 @@ public class ConversationActivity extends BriarActivity
adapter.notifyItemChanged(position, item); adapter.notifyItemChanged(position, item);
} }
runOnDbThread(() -> { runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try { try {
switch (item.getRequestType()) { switch (item.getRequestType()) {
case INTRODUCTION: case INTRODUCTION:
respondToIntroductionRequest(item.getSessionId(), respondToIntroductionRequest(item.getSessionId(),
accept); accept, timestamp);
break; break;
case FORUM: case FORUM:
respondToForumRequest(item.getSessionId(), accept); respondToForumRequest(item.getSessionId(), accept);
@@ -1042,18 +1035,11 @@ public class ConversationActivity extends BriarActivity
ActivityCompat.startActivity(this, i, options.toBundle()); ActivityCompat.startActivity(this, i, options.toBundle());
} }
@Override
public void onAutoDeleteTimerNoticeClicked() {
ConversationSettingsDialog dialog =
ConversationSettingsDialog.newInstance(contactId);
dialog.show(getSupportFragmentManager(),
ConversationSettingsDialog.TAG);
}
@DatabaseExecutor @DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId, private void respondToIntroductionRequest(SessionId sessionId,
boolean accept) throws DbException { boolean accept, long time) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, accept); introductionManager.respondToIntroduction(contactId, sessionId, time,
accept);
} }
@DatabaseExecutor @DatabaseExecutor

View File

@@ -13,8 +13,6 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.BriarAdapter;
import org.briarproject.briar.android.util.ItemReturningAdapter; import org.briarproject.briar.android.util.ItemReturningAdapter;
import java.util.Collection;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.selection.SelectionTracker; import androidx.recyclerview.selection.SelectionTracker;
@@ -22,14 +20,13 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool; import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@NotNullByDefault @NotNullByDefault
class ConversationAdapter class ConversationAdapter
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> extends BriarAdapter<ConversationItem, ConversationItemViewHolder>
implements ItemReturningAdapter<ConversationItem> { implements ItemReturningAdapter<ConversationItem> {
private final ConversationListener listener; private ConversationListener listener;
private final RecycledViewPool imageViewPool; private final RecycledViewPool imageViewPool;
private final ImageItemDecoration imageItemDecoration; private final ImageItemDecoration imageItemDecoration;
@Nullable @Nullable
@@ -68,20 +65,22 @@ class ConversationAdapter
@LayoutRes int type) { @LayoutRes int type) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate( View v = LayoutInflater.from(viewGroup.getContext()).inflate(
type, viewGroup, false); type, viewGroup, false);
if (type == R.layout.list_item_conversation_msg_in) { switch (type) {
return new ConversationMessageViewHolder(v, listener, true, case R.layout.list_item_conversation_msg_in:
imageViewPool, imageItemDecoration); return new ConversationMessageViewHolder(v, listener, true,
} else if (type == R.layout.list_item_conversation_msg_out) { imageViewPool, imageItemDecoration);
return new ConversationMessageViewHolder(v, listener, false, case R.layout.list_item_conversation_msg_out:
imageViewPool, imageItemDecoration); return new ConversationMessageViewHolder(v, listener, false,
} else if (type == R.layout.list_item_conversation_notice_in) { imageViewPool, imageItemDecoration);
return new ConversationNoticeViewHolder(v, listener, true); case R.layout.list_item_conversation_notice_in:
} else if (type == R.layout.list_item_conversation_notice_out) { return new ConversationNoticeViewHolder(v, listener, true);
return new ConversationNoticeViewHolder(v, listener, false); case R.layout.list_item_conversation_notice_out:
} else if (type == R.layout.list_item_conversation_request) { return new ConversationNoticeViewHolder(v, listener, false);
return new ConversationRequestViewHolder(v, listener, true); case R.layout.list_item_conversation_request:
return new ConversationRequestViewHolder(v, listener, true);
default:
throw new IllegalArgumentException("Unknown ConversationItem");
} }
throw new IllegalArgumentException("Unknown ConversationItem");
} }
@Override @Override
@@ -108,49 +107,22 @@ class ConversationAdapter
return c1.equals(c2); return c1.equals(c2);
} }
@Override
public void add(ConversationItem item) {
items.beginBatchedUpdates();
items.add(item);
updateTimersInBatch();
items.endBatchedUpdates();
}
@Override
public void addAll(Collection<ConversationItem> itemsToAdd) {
items.beginBatchedUpdates();
// there can be items already in the adapter
// SortedList takes care of duplicates and detecting changed items
items.addAll(itemsToAdd);
updateTimersInBatch();
items.endBatchedUpdates();
}
private void updateTimersInBatch() {
long lastTimerIncoming = NO_AUTO_DELETE_TIMER;
long lastTimerOutgoing = NO_AUTO_DELETE_TIMER;
for (int i = 0; i < items.size(); i++) {
ConversationItem c = items.get(i);
boolean itemChanged;
boolean timerChanged;
if (c.isIncoming()) {
timerChanged = lastTimerIncoming != c.getAutoDeleteTimer();
lastTimerIncoming = c.getAutoDeleteTimer();
} else {
timerChanged = lastTimerOutgoing != c.getAutoDeleteTimer();
lastTimerOutgoing = c.getAutoDeleteTimer();
}
itemChanged = c.setTimerNoticeVisible(timerChanged);
if (itemChanged) items.updateItemAt(i, c);
}
}
void setSelectionTracker(SelectionTracker<String> tracker) { void setSelectionTracker(SelectionTracker<String> tracker) {
this.tracker = tracker; this.tracker = tracker;
} }
@Nullable
ConversationItem getLastItem() {
if (items.size() > 0) {
return items.get(items.size() - 1);
} else {
return null;
}
}
SparseArray<ConversationItem> getOutgoingMessages() { SparseArray<ConversationItem> getOutgoingMessages() {
SparseArray<ConversationItem> messages = new SparseArray<>(); SparseArray<ConversationItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i); ConversationItem item = items.get(i);
if (!item.isIncoming()) { if (!item.isIncoming()) {

View File

@@ -9,7 +9,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.lifecycle.LiveData;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -23,25 +22,20 @@ abstract class ConversationItem {
protected String text; protected String text;
private final MessageId id; private final MessageId id;
private final GroupId groupId; private final GroupId groupId;
private final long time, autoDeleteTimer; private final long time;
private final boolean isIncoming; private final boolean isIncoming;
private final LiveData<String> contactName; private boolean read, sent, seen;
private boolean read, sent, seen, showTimerNotice;
ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h, ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h) {
LiveData<String> contactName) {
this.layoutRes = layoutRes; this.layoutRes = layoutRes;
this.text = null; this.text = null;
this.id = h.getId(); this.id = h.getId();
this.groupId = h.getGroupId(); this.groupId = h.getGroupId();
this.time = h.getTimestamp(); this.time = h.getTimestamp();
this.autoDeleteTimer = h.getAutoDeleteTimer();
this.read = h.isRead(); this.read = h.isRead();
this.sent = h.isSent(); this.sent = h.isSent();
this.seen = h.isSeen(); this.seen = h.isSeen();
this.isIncoming = !h.isLocal(); this.isIncoming = !h.isLocal();
this.contactName = contactName;
this.showTimerNotice = false;
} }
@LayoutRes @LayoutRes
@@ -74,10 +68,6 @@ abstract class ConversationItem {
return time; return time;
} }
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
/** /**
* Only useful for incoming messages. * Only useful for incoming messages.
*/ */
@@ -121,25 +111,4 @@ abstract class ConversationItem {
return isIncoming; return isIncoming;
} }
public LiveData<String> getContactName() {
return contactName;
}
/**
* Set this to true when {@link #getAutoDeleteTimer()} has changed
* since the last message from the same peer.
*
* @return true if the value was set, false if it was already set.
*/
boolean setTimerNoticeVisible(boolean visible) {
if (this.showTimerNotice != visible) {
this.showTimerNotice = visible;
return true;
}
return false;
}
boolean isTimerNoticeVisible() {
return showTimerNotice;
}
} }

View File

@@ -1,8 +1,6 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -14,11 +12,8 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView.ViewHolder; import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.trim; import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -29,9 +24,8 @@ abstract class ConversationItemViewHolder extends ViewHolder {
protected final ConstraintLayout layout; protected final ConstraintLayout layout;
@Nullable @Nullable
private final OutItemViewHolder outViewHolder; private final OutItemViewHolder outViewHolder;
private final TextView topNotice, text; private final TextView text;
protected final TextView time; protected final TextView time;
protected final ImageView bomb;
@Nullable @Nullable
private String itemKey = null; private String itemKey = null;
@@ -39,13 +33,11 @@ abstract class ConversationItemViewHolder extends ViewHolder {
boolean isIncoming) { boolean isIncoming) {
super(v); super(v);
this.listener = listener; this.listener = listener;
outViewHolder = isIncoming ? null : new OutItemViewHolder(v); this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
root = v; root = v;
topNotice = v.findViewById(R.id.topNotice);
layout = v.findViewById(R.id.layout); layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text); text = v.findViewById(R.id.text);
time = v.findViewById(R.id.time); time = v.findViewById(R.id.time);
bomb = v.findViewById(R.id.bomb);
} }
@CallSuper @CallSuper
@@ -53,8 +45,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
itemKey = item.getKey(); itemKey = item.getKey();
root.setActivated(selected); root.setActivated(selected);
setTopNotice(item);
if (item.getText() != null) { if (item.getText() != null) {
text.setText(trim(item.getText())); text.setText(trim(item.getText()));
} }
@@ -62,9 +52,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
long timestamp = item.getTime(); long timestamp = item.getTime();
time.setText(formatDate(time.getContext(), timestamp)); time.setText(formatDate(time.getContext(), timestamp));
boolean showBomb = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER;
bomb.setVisibility(showBomb ? VISIBLE : GONE);
if (outViewHolder != null) outViewHolder.bind(item); if (outViewHolder != null) outViewHolder.bind(item);
} }
@@ -77,31 +64,4 @@ abstract class ConversationItemViewHolder extends ViewHolder {
return itemKey; return itemKey;
} }
private void setTopNotice(ConversationItem item) {
if (item.isTimerNoticeVisible()) {
Context ctx = itemView.getContext();
topNotice.setVisibility(VISIBLE);
boolean enabled = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER;
String tapToLearnMore = ctx.getString(R.string.tap_to_learn_more);
String text;
if (item.isIncoming()) {
String name = item.getContactName().getValue();
int strRes = enabled ?
R.string.auto_delete_msg_contact_enabled :
R.string.auto_delete_msg_contact_disabled;
text = ctx.getString(strRes, name, tapToLearnMore);
} else {
int strRes = enabled ?
R.string.auto_delete_msg_you_enabled :
R.string.auto_delete_msg_you_disabled;
text = ctx.getString(strRes, tapToLearnMore);
}
topNotice.setText(text);
topNotice.setOnClickListener(
v -> listener.onAutoDeleteTimerNoticeClicked());
} else {
topNotice.setVisibility(GONE);
}
}
} }

View File

@@ -18,6 +18,4 @@ interface ConversationListener {
void onAttachmentClicked(View view, ConversationMessageItem messageItem, void onAttachmentClicked(View view, ConversationMessageItem messageItem,
AttachmentItem attachmentItem); AttachmentItem attachmentItem);
void onAutoDeleteTimerNoticeClicked();
} }

View File

@@ -10,7 +10,6 @@ import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -19,8 +18,8 @@ class ConversationMessageItem extends ConversationItem {
private final List<AttachmentItem> attachments; private final List<AttachmentItem> attachments;
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h, ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
LiveData<String> contactName, List<AttachmentItem> attachments) { List<AttachmentItem> attachments) {
super(layoutRes, h, contactName); super(layoutRes, h);
this.attachments = attachments; this.attachments = attachments;
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.res.ColorStateList;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -15,7 +14,6 @@ import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.widget.ImageViewCompat.setImageTintList;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -86,7 +84,6 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
if (item.getText() == null) { if (item.getText() == null) {
statusLayout.setBackgroundResource(R.drawable.msg_status_bubble); statusLayout.setBackgroundResource(R.drawable.msg_status_bubble);
time.setTextColor(timeColorBubble); time.setTextColor(timeColorBubble);
setImageTintList(bomb, ColorStateList.valueOf(timeColorBubble));
constraintSet = imageConstraints; constraintSet = imageConstraints;
} else { } else {
resetStatusLayoutForText(); resetStatusLayoutForText();
@@ -114,7 +111,6 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
// also reset padding (the background drawable defines some) // also reset padding (the background drawable defines some)
statusLayout.setPadding(0, 0, 0, 0); statusLayout.setPadding(0, 0, 0, 0);
time.setTextColor(timeColor); time.setTextColor(timeColor);
setImageTintList(bomb, ColorStateList.valueOf(timeColor));
} }
} }

View File

@@ -8,7 +8,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.lifecycle.LiveData;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -18,15 +17,15 @@ class ConversationNoticeItem extends ConversationItem {
private final String msgText; private final String msgText;
ConversationNoticeItem(@LayoutRes int layoutRes, String text, ConversationNoticeItem(@LayoutRes int layoutRes, String text,
LiveData<String> contactName, ConversationRequest<?> r) { ConversationRequest r) {
super(layoutRes, r, contactName); super(layoutRes, r);
this.text = text; this.text = text;
this.msgText = r.getText(); this.msgText = r.getText();
} }
ConversationNoticeItem(@LayoutRes int layoutRes, String text, ConversationNoticeItem(@LayoutRes int layoutRes, String text,
LiveData<String> contactName, ConversationResponse r) { ConversationResponse r) {
super(layoutRes, r, contactName); super(layoutRes, r);
this.text = text; this.text = text;
this.msgText = null; this.msgText = null;
} }

View File

@@ -11,7 +11,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.lifecycle.LiveData;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -27,15 +26,14 @@ class ConversationRequestItem extends ConversationNoticeItem {
private boolean answered; private boolean answered;
ConversationRequestItem(@LayoutRes int layoutRes, String text, ConversationRequestItem(@LayoutRes int layoutRes, String text,
LiveData<String> contactName, RequestType type, RequestType type, ConversationRequest r) {
ConversationRequest<?> r) { super(layoutRes, text, r);
super(layoutRes, text, contactName, r);
this.requestType = type; this.requestType = type;
this.sessionId = r.getSessionId(); this.sessionId = r.getSessionId();
this.answered = r.wasAnswered(); this.answered = r.wasAnswered();
if (r instanceof InvitationRequest) { if (r instanceof InvitationRequest) {
this.requestedGroupId = ((Shareable) r.getNameable()).getId(); this.requestedGroupId = ((Shareable) r.getNameable()).getId();
this.canBeOpened = ((InvitationRequest<?>) r).canBeOpened(); this.canBeOpened = ((InvitationRequest) r).canBeOpened();
} else { } else {
this.requestedGroupId = null; this.requestedGroupId = null;
this.canBeOpened = false; this.canBeOpened = false;

View File

@@ -1,122 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SwitchCompat;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsDialog extends DialogFragment {
final static String TAG = ConversationSettingsDialog.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
static ConversationSettingsDialog newInstance(ContactId contactId) {
Bundle args = new Bundle();
args.putInt(CONTACT_ID, contactId.getInt());
ConversationSettingsDialog dialog = new ConversationSettingsDialog();
dialog.setArguments(args);
return dialog;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
injectFragment(((BaseFragment.BaseFragmentListener) context)
.getActivityComponent());
}
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_FRAME,
R.style.BriarFullScreenDialogTheme);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_conversation_settings,
container, false);
Bundle args = requireArguments();
int id = args.getInt(CONTACT_ID, -1);
if (id == -1) throw new IllegalStateException();
ContactId contactId = new ContactId(id);
FragmentActivity activity = requireActivity();
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(ConversationViewModel.class);
viewModel.setContactId(contactId);
Toolbar toolbar = view.findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v -> dismiss());
SwitchCompat switchDisappearingMessages = view.findViewById(
R.id.switchDisappearingMessages);
switchDisappearingMessages.setOnCheckedChangeListener(
(button, value) -> viewModel.setAutoDeleteTimerEnabled(value));
TextView buttonLearnMore =
view.findViewById(R.id.buttonLearnMore);
buttonLearnMore.setOnClickListener(e -> showLearnMoreDialog());
viewModel.getAutoDeleteTimer()
.observe(getViewLifecycleOwner(), timer -> {
LOG.info("Received auto delete timer: " + timer);
boolean disappearingMessages =
timer != NO_AUTO_DELETE_TIMER;
switchDisappearingMessages
.setChecked(disappearingMessages);
switchDisappearingMessages.setEnabled(true);
});
return view;
}
private void showLearnMoreDialog() {
ConversationSettingsLearnMoreDialog
dialog = new ConversationSettingsLearnMoreDialog();
dialog.show(getChildFragmentManager(),
ConversationSettingsLearnMoreDialog.TAG);
}
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsLearnMoreDialog extends DialogFragment {
final static String TAG =
ConversationSettingsLearnMoreDialog.class.getName();
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
FragmentActivity activity = requireActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(activity,
R.style.OnboardingDialogTheme);
LayoutInflater inflater = LayoutInflater.from(builder.getContext());
View view = inflater.inflate(
R.layout.fragment_conversation_settings_learn_more, null);
builder.setView(view);
builder.setTitle(R.string.disappearing_messages_title);
builder.setPositiveButton(R.string.ok, null);
return builder.create();
}
}

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager; 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;
@@ -29,22 +28,16 @@ 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.contact.ContactItem;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.TextSendController.SendState;
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.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.autodelete.UnexpectedTimerException;
import org.briarproject.briar.api.autodelete.event.AutoDeleteTimerMirroredEvent;
import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent; import org.briarproject.briar.api.avatar.event.AvatarUpdatedEvent;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager; 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;
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
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;
@@ -62,8 +55,6 @@ import androidx.lifecycle.MutableLiveData;
import static androidx.lifecycle.Transformations.map; import static androidx.lifecycle.Transformations.map;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
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.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -71,12 +62,6 @@ import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.android.view.TextSendController.SendState.ERROR;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
import static org.briarproject.briar.android.view.TextSendController.SendState.UNEXPECTED_TIMER;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@NotNullByDefault @NotNullByDefault
public class ConversationViewModel extends DbViewModel public class ConversationViewModel extends DbViewModel
@@ -99,8 +84,6 @@ public class ConversationViewModel extends DbViewModel
private final PrivateMessageFactory privateMessageFactory; private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever; private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator; private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
@@ -109,7 +92,7 @@ public class ConversationViewModel extends DbViewModel
private final LiveData<String> contactName = map(contactItem, c -> private final LiveData<String> contactName = map(contactItem, c ->
UiUtils.getContactDisplayName(c.getContact())); UiUtils.getContactDisplayName(c.getContact()));
private final LiveData<GroupId> messagingGroupId; private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<PrivateMessageFormat> privateMessageFormat = private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveEvent<Boolean> showImageOnboarding = private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@@ -117,10 +100,8 @@ public class ConversationViewModel extends DbViewModel
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveData<Boolean> showIntroductionAction = private final MutableLiveData<Boolean> showIntroductionAction =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Long> autoDeleteTimer =
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted = private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>(false); new MutableLiveData<>();
private final MutableLiveEvent<PrivateMessageHeader> addedHeader = private final MutableLiveEvent<PrivateMessageHeader> addedHeader =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@@ -137,9 +118,7 @@ public class ConversationViewModel extends DbViewModel
SettingsManager settingsManager, SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory, PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator, AttachmentCreator attachmentCreator) {
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -150,10 +129,10 @@ public class ConversationViewModel extends DbViewModel
this.privateMessageFactory = privateMessageFactory; this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever; this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
messagingGroupId = map(contactItem, c -> messagingGroupId = map(contactItem, c ->
messagingManager.getContactGroup(c.getContact()).getId()); messagingManager.getContactGroup(c.getContact()).getId());
contactDeleted.setValue(false);
eventBus.addListener(this); eventBus.addListener(this);
} }
@@ -170,14 +149,9 @@ public class ConversationViewModel extends DbViewModel
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e; AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) { if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received"); LOG.info("Attachment received");
runOnDbThread(() -> attachmentRetriever runOnDbThreadOrLogException(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId())); .loadAttachmentItem(a.getMessageId()));
} }
} else if (e instanceof AutoDeleteTimerMirroredEvent) {
AutoDeleteTimerMirroredEvent a = (AutoDeleteTimerMirroredEvent) e;
if (a.getContactId().equals(contactId)) {
autoDeleteTimer.postValue(a.getNewTimer());
}
} else if (e instanceof AvatarUpdatedEvent) { } else if (e instanceof AvatarUpdatedEvent) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e; AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
if (a.getContactId().equals(contactId)) { if (a.getContactId().equals(contactId)) {
@@ -220,49 +194,50 @@ public class ConversationViewModel extends DbViewModel
private void loadContact(ContactId contactId) { private void loadContact(ContactId contactId) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { long start = now();
long start = now(); Contact c = contactManager.getContact(contactId);
Contact c = contactManager.getContact(contactId); AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
AuthorInfo authorInfo = authorManager.getAuthorInfo(c); contactItem.postValue(new ContactItem(c, authorInfo));
contactItem.postValue(new ContactItem(c, authorInfo)); logDuration(LOG, "Loading contact", start);
logDuration(LOG, "Loading contact", start); start = now();
start = now(); checkFeaturesAndOnboarding(contactId);
long timer = db.transactionWithResult(true, txn -> logDuration(LOG, "Checking for image support", start);
autoDeleteManager.getAutoDeleteTimer(txn, contactId)); }, e -> {
autoDeleteTimer.postValue(timer); if (e instanceof NoSuchContactException) {
logDuration(LOG, "Getting auto-delete timer", start);
start = now();
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) {
contactDeleted.postValue(true); contactDeleted.postValue(true);
} catch (DbException e) { } else {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
}); });
} }
void markMessageRead(GroupId g, MessageId m) { void markMessageRead(GroupId g, MessageId m) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); messagingManager.setReadFlag(g, m, true);
messagingManager.setReadFlag(g, m, true); logDuration(LOG, "Marking read", start);
logDuration(LOG, "Marking read", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
void setContactAlias(String alias) { void setContactAlias(String alias) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { contactManager.setContactAlias(requireNonNull(contactId),
contactManager.setContactAlias(requireNonNull(contactId), alias.isEmpty() ? null : alias);
alias.isEmpty() ? null : alias); loadContact(contactId);
loadContact(contactId); });
} catch (DbException e) { }
logException(LOG, WARNING, e);
} @UiThread
void sendMessage(@Nullable String text,
List<AttachmentHeader> headers, long timestamp) {
// messagingGroupId is loaded with the contact
observeForeverOnce(messagingGroupId, groupId -> {
requireNonNull(groupId);
observeForeverOnce(imageSupport, hasImageSupport -> {
requireNonNull(hasImageSupport);
createMessage(groupId, text, headers, timestamp,
hasImageSupport);
});
}); });
} }
@@ -292,12 +267,10 @@ public class ConversationViewModel extends DbViewModel
@DatabaseExecutor @DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException { private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images and auto-deletion are supported // check if images are supported
PrivateMessageFormat format = db.transactionWithResult(true, txn -> boolean imagesSupported = db.transactionWithResult(true, txn ->
messagingManager.getContactMessageFormat(txn, c)); messagingManager.contactSupportsImages(txn, c));
if (LOG.isLoggable(INFO)) imageSupport.postValue(imagesSupported);
LOG.info("PrivateMessageFormat loaded: " + format.name());
privateMessageFormat.postValue(format);
// check if introductions are supported // check if introductions are supported
Collection<Contact> contacts = contactManager.getContacts(); Collection<Contact> contacts = contactManager.getContacts();
@@ -306,7 +279,7 @@ public class ConversationViewModel extends DbViewModel
// we only show one onboarding dialog at a time // we only show one onboarding dialog at a time
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE); Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (format != TEXT_ONLY && if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) { settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
onOnboardingShown(SHOW_ONBOARDING_IMAGE); onOnboardingShown(SHOW_ONBOARDING_IMAGE);
showImageOnboarding.postEvent(true); showImageOnboarding.postEvent(true);
@@ -325,85 +298,38 @@ public class ConversationViewModel extends DbViewModel
} }
@UiThread @UiThread
LiveData<SendState> sendMessage(@Nullable String text, private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long expectedTimer) { List<AttachmentHeader> headers, long timestamp,
MutableLiveData<SendState> liveData = new MutableLiveData<>(); boolean hasImageSupport) {
runOnDbThread(() -> {
try {
db.transaction(false, txn -> {
long start = now();
PrivateMessage m = createMessage(txn, text, headers,
expectedTimer);
messagingManager.addLocalMessage(txn, m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders(),
m.getAutoDeleteTimer());
// TODO add text to cache when available here
MessageId id = message.getId();
txn.attach(() -> {
attachmentCreator.onAttachmentsSent(id);
liveData.setValue(SENT);
addedHeader.setEvent(h);
});
});
} catch (UnexpectedTimerException e) {
liveData.postValue(UNEXPECTED_TIMER);
} catch (DbException e) {
logException(LOG, WARNING, e);
liveData.postValue(ERROR);
}
});
return liveData;
}
private PrivateMessage createMessage(Transaction txn, @Nullable String text,
List<AttachmentHeader> headers, long expectedTimer)
throws DbException {
// Sending is only possible (setReady(true)) after loading all messages
// which happens after the contact has been loaded.
// privateMessageFormat is loaded together with contact
Contact contact = requireNonNull(contactItem.getValue()).getContact();
GroupId groupId = messagingManager.getContactGroup(contact).getId();
PrivateMessageFormat format =
requireNonNull(privateMessageFormat.getValue());
long timestamp = conversationManager
.getTimestampForOutgoingMessage(txn, requireNonNull(contactId));
try { try {
if (format == TEXT_ONLY) { PrivateMessage pm;
return privateMessageFactory.createLegacyPrivateMessage( if (hasImageSupport) {
groupId, timestamp, requireNonNull(text)); pm = privateMessageFactory.createPrivateMessage(groupId,
} else if (format == TEXT_IMAGES) {
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers); timestamp, text, headers);
} else { } else {
long timer = autoDeleteManager pm = privateMessageFactory.createLegacyPrivateMessage(
.getAutoDeleteTimer(txn, contactId, timestamp); groupId, timestamp, requireNonNull(text));
if (timer != expectedTimer)
throw new UnexpectedTimerException();
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers, timer);
} }
storeMessage(pm);
} catch (FormatException e) { } catch (FormatException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
void setAutoDeleteTimerEnabled(boolean enabled) { @UiThread
final long timer = enabled ? DAYS.toMillis(7) : NO_AUTO_DELETE_TIMER; private void storeMessage(PrivateMessage m) {
// ContactId is set before menu gets inflated and UI interaction attachmentCreator.onAttachmentsSent(m.getMessage().getId());
final ContactId c = requireNonNull(contactId); runOnDbThreadOrLogException(() -> {
runOnDbThread(() -> { long start = now();
try { messagingManager.addLocalMessage(m);
db.transaction(false, txn -> logDuration(LOG, "Storing message", start);
autoDeleteManager.setAutoDeleteTimer(txn, c, timer)); Message message = m.getMessage();
autoDeleteTimer.postValue(timer); PrivateMessageHeader h = new PrivateMessageHeader(
} catch (DbException e) { message.getId(), message.getGroupId(),
logException(LOG, WARNING, e); message.getTimestamp(), true, true, false, false,
} m.hasText(), m.getAttachmentHeaders());
// TODO add text to cache when available here
addedHeader.postEvent(h);
}); });
} }
@@ -419,8 +345,8 @@ public class ConversationViewModel extends DbViewModel
return contactName; return contactName;
} }
LiveData<PrivateMessageFormat> getPrivateMessageFormat() { LiveData<Boolean> hasImageSupport() {
return privateMessageFormat; return imageSupport;
} }
LiveEvent<Boolean> showImageOnboarding() { LiveEvent<Boolean> showImageOnboarding() {
@@ -435,10 +361,6 @@ public class ConversationViewModel extends DbViewModel
return showIntroductionAction; return showIntroductionAction;
} }
LiveData<Long> getAutoDeleteTimer() {
return autoDeleteTimer;
}
LiveData<Boolean> isContactDeleted() { LiveData<Boolean> isContactDeleted() {
return contactDeleted; return contactDeleted;
} }
@@ -449,12 +371,7 @@ public class ConversationViewModel extends DbViewModel
@UiThread @UiThread
void recheckFeaturesAndOnboarding(ContactId contactId) { void recheckFeaturesAndOnboarding(ContactId contactId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { checkFeaturesAndOnboarding(contactId));
checkFeaturesAndOnboarding(contactId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
} }

View File

@@ -60,12 +60,10 @@ class ConversationVisitor implements
} }
if (h.isLocal()) { if (h.isLocal()) {
item = new ConversationMessageItem( item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_out, h, contactName, R.layout.list_item_conversation_msg_out, h, attachments);
attachments);
} else { } else {
item = new ConversationMessageItem( item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_in, h, contactName, R.layout.list_item_conversation_msg_in, h, attachments);
attachments);
} }
if (h.hasText()) { if (h.hasText()) {
String text = textCache.getText(h.getId()); String text = textCache.getText(h.getId());
@@ -81,15 +79,13 @@ class ConversationVisitor implements
String text = ctx.getString(R.string.blogs_sharing_invitation_sent, String text = ctx.getString(R.string.blogs_sharing_invitation_sent,
r.getName(), contactName.getValue()); r.getName(), contactName.getValue());
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text = ctx.getString( String text = ctx.getString(
R.string.blogs_sharing_invitation_received, R.string.blogs_sharing_invitation_received,
contactName.getValue(), r.getName()); contactName.getValue(), r.getName());
return new ConversationRequestItem( return new ConversationRequestItem(
R.layout.list_item_conversation_request, text, contactName, R.layout.list_item_conversation_request, text, BLOG, r);
BLOG, r);
} }
} }
@@ -108,8 +104,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text; String text;
if (r.wasAccepted()) { if (r.wasAccepted()) {
@@ -122,8 +117,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_in, text, R.layout.list_item_conversation_notice_in, text, r);
contactName, r);
} }
} }
@@ -134,15 +128,13 @@ class ConversationVisitor implements
String text = ctx.getString(R.string.forum_invitation_sent, String text = ctx.getString(R.string.forum_invitation_sent,
r.getName(), contactName.getValue()); r.getName(), contactName.getValue());
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text = ctx.getString( String text = ctx.getString(
R.string.forum_invitation_received, R.string.forum_invitation_received,
contactName.getValue(), r.getName()); contactName.getValue(), r.getName());
return new ConversationRequestItem( return new ConversationRequestItem(
R.layout.list_item_conversation_request, text, contactName, R.layout.list_item_conversation_request, text, FORUM, r);
FORUM, r);
} }
} }
@@ -161,8 +153,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text; String text;
if (r.wasAccepted()) { if (r.wasAccepted()) {
@@ -175,8 +166,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_in, text, R.layout.list_item_conversation_notice_in, text, r);
contactName, r);
} }
} }
@@ -188,15 +178,13 @@ class ConversationVisitor implements
R.string.groups_invitations_invitation_sent, R.string.groups_invitations_invitation_sent,
contactName.getValue(), r.getName()); contactName.getValue(), r.getName());
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text = ctx.getString( String text = ctx.getString(
R.string.groups_invitations_invitation_received, R.string.groups_invitations_invitation_received,
contactName.getValue(), r.getName()); contactName.getValue(), r.getName());
return new ConversationRequestItem( return new ConversationRequestItem(
R.layout.list_item_conversation_request, text, contactName, R.layout.list_item_conversation_request, text, GROUP, r);
GROUP, r);
} }
} }
@@ -215,8 +203,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text; String text;
if (r.wasAccepted()) { if (r.wasAccepted()) {
@@ -229,8 +216,7 @@ class ConversationVisitor implements
contactName.getValue()); contactName.getValue());
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_in, text, R.layout.list_item_conversation_notice_in, text, r);
contactName, r);
} }
} }
@@ -241,8 +227,7 @@ class ConversationVisitor implements
String text = ctx.getString(R.string.introduction_request_sent, String text = ctx.getString(R.string.introduction_request_sent,
contactName.getValue(), name); contactName.getValue(), name);
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text; String text;
if (r.wasAnswered()) { if (r.wasAnswered()) {
@@ -258,7 +243,7 @@ class ConversationVisitor implements
contactName.getValue(), name); contactName.getValue(), name);
} }
return new ConversationRequestItem( return new ConversationRequestItem(
R.layout.list_item_conversation_request, text, contactName, R.layout.list_item_conversation_request, text,
INTRODUCTION, r); INTRODUCTION, r);
} }
} }
@@ -283,8 +268,7 @@ class ConversationVisitor implements
introducedAuthor); introducedAuthor);
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, R.layout.list_item_conversation_notice_out, text, r);
contactName, r);
} else { } else {
String text; String text;
if (r.wasAccepted()) { if (r.wasAccepted()) {
@@ -304,8 +288,7 @@ class ConversationVisitor implements
introducedAuthor); introducedAuthor);
} }
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_in, text, R.layout.list_item_conversation_notice_in, text, r);
contactName, r);
} }
} }

View File

@@ -6,7 +6,6 @@ import android.net.Uri;
import android.view.View; import android.view.View;
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.TransactionManager; 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;
@@ -198,14 +197,12 @@ public class ImageViewModel extends DbViewModel implements EventListener {
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp, private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
@Nullable Runnable afterCopy) { @Nullable Runnable afterCopy) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { Attachment a =
Attachment a = attachmentReader.getAttachment(attachment.getHeader());
attachmentReader.getAttachment(attachment.getHeader()); copyImageFromDb(a, osp, afterCopy);
copyImageFromDb(a, osp, afterCopy); }, e -> {
} catch (DbException e) { logException(LOG, WARNING, e);
logException(LOG, WARNING, e); saveState.postEvent(true);
saveState.postEvent(true);
}
}); });
} }

View File

@@ -79,6 +79,12 @@ public class ForumActivity extends
} }
} }
@Override
public void onStart() {
super.onStart();
viewModel.clearForumPostNotification();
}
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result,
@Nullable Intent data) { @Nullable Intent data) {
@@ -103,16 +109,16 @@ public class ForumActivity extends
// Handle presses on the action bar items // Handle presses on the action bar items
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_forum_share) { if (itemId == R.id.action_forum_share) {
Intent i = new Intent(this, ShareForumActivity.class); Intent i2 = new Intent(this, ShareForumActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes()); i2.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_SHARE_FORUM); startActivityForResult(i2, REQUEST_SHARE_FORUM);
return true; return true;
} else if (itemId == R.id.action_forum_sharing_status) { } else if (itemId == R.id.action_forum_sharing_status) {
Intent i = new Intent(this, ForumSharingStatusActivity.class); Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes()); i3.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i); startActivity(i3);
return true; return true;
} else if (itemId == R.id.action_forum_delete) { } else if (itemId == R.id.action_forum_delete) {
showUnsubscribeDialog(); showUnsubscribeDialog();

View File

@@ -40,10 +40,8 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
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.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.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID; import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
@@ -164,15 +162,11 @@ class ForumListViewModel extends DbViewModel implements EventListener {
} }
void loadForumInvitations() { void loadForumInvitations() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); int available = forumSharingManager.getInvitations().size();
int available = forumSharingManager.getInvitations().size(); logDuration(LOG, "Loading available", start);
logDuration(LOG, "Loading available", start); numInvitations.postValue(available);
numInvitations.postValue(available);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -26,6 +26,7 @@ import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
@@ -50,10 +51,8 @@ import androidx.lifecycle.MutableLiveData;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.lang.Math.max; import static java.lang.Math.max;
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.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.now; import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -93,9 +92,8 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
if (f.getGroupId().equals(groupId)) { if (f.getGroupId().equals(groupId)) {
LOG.info("Forum post received, adding..."); LOG.info("Forum post received, adding...");
ForumPostItem item = ForumPostItem item = buildItem(f.getHeader(), f.getText());
new ForumPostItem(f.getHeader(), f.getText()); addItem(item);
addItem(item, false);
} }
} else if (e instanceof ForumInvitationResponseReceivedEvent) { } else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent f = ForumInvitationResponseReceivedEvent f =
@@ -116,20 +114,14 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
} }
} }
protected void clearNotifications() { void clearForumPostNotification() {
notificationManager.clearForumPostNotification(groupId); notificationManager.clearForumPostNotification(groupId);
} }
LiveData<Forum> loadForum() { LiveData<Forum> loadForum() {
MutableLiveData<Forum> forum = new MutableLiveData<>(); MutableLiveData<Forum> forum = new MutableLiveData<>();
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forum.postValue(forumManager.getForum(groupId)));
Forum f = forumManager.getForum(groupId);
forum.postValue(f);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
return forum; return forum;
} }
@@ -140,35 +132,19 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
List<ForumPostHeader> headers = List<ForumPostHeader> headers =
forumManager.getPostHeaders(txn, groupId); forumManager.getPostHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start); logDuration(LOG, "Loading headers", start);
start = now(); return createItems(txn, headers, this::buildItem);
List<ForumPostItem> items = new ArrayList<>();
for (ForumPostHeader header : headers) {
items.add(loadItem(txn, header));
}
logDuration(LOG, "Loading bodies and creating items", start);
return items;
}, this::setItems); }, this::setItems);
} }
private ForumPostItem loadItem(Transaction txn, ForumPostHeader header)
throws DbException {
String text = forumManager.getPostText(txn, header.getId());
return new ForumPostItem(header, text);
}
@Override @Override
public void createAndStoreMessage(String text, public void createAndStoreMessage(String text,
@Nullable MessageId parentId) { @Nullable MessageId parentId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { LocalAuthor author = identityManager.getLocalAuthor();
LocalAuthor author = identityManager.getLocalAuthor(); GroupCount count = forumManager.getGroupCount(groupId);
GroupCount count = forumManager.getGroupCount(groupId); long timestamp = max(count.getLatestMsgTime() + 1,
long timestamp = max(count.getLatestMsgTime() + 1, clock.currentTimeMillis());
clock.currentTimeMillis()); createMessage(text, timestamp, parentId, author);
createMessage(text, timestamp, parentId, author);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -183,47 +159,43 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
} }
private void storePost(ForumPost msg, String text) { private void storePost(ForumPost msg, String text) {
runOnDbThread(false, txn -> { runOnDbThreadOrLogException(() -> {
long start = now(); long start = now();
ForumPostHeader header = forumManager.addLocalPost(txn, msg); ForumPostHeader header = forumManager.addLocalPost(msg);
addItemAsync(buildItem(header, text));
logDuration(LOG, "Storing forum post", start); logDuration(LOG, "Storing forum post", start);
txn.attach(() -> { });
ForumPostItem item = new ForumPostItem(header, text); }
addItem(item, true);
}); private ForumPostItem buildItem(ForumPostHeader header, String text) {
}, e -> logException(LOG, WARNING, e)); return new ForumPostItem(header, text);
}
@Override
protected String loadMessageText(Transaction txn, PostHeader header)
throws DbException {
return forumManager.getPostText(txn, header.getId());
} }
@Override @Override
protected void markItemRead(ForumPostItem item) { protected void markItemRead(ForumPostItem item) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forumManager.setReadFlag(groupId, item.getId(), true));
forumManager.setReadFlag(groupId, item.getId(), true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
public void loadSharingContacts() { public void loadSharingContacts() {
runOnDbThread(true, txn -> { runOnDbThreadOrLogException(true, txn -> {
Collection<Contact> contacts = Collection<Contact> contacts =
forumSharingManager.getSharedWith(txn, groupId); forumSharingManager.getSharedWith(txn, groupId);
Collection<ContactId> contactIds = new ArrayList<>(contacts.size()); Collection<ContactId> contactIds = new ArrayList<>(contacts.size());
for (Contact c : contacts) contactIds.add(c.getId()); for (Contact c : contacts) contactIds.add(c.getId());
txn.attach(() -> sharingController.addAll(contactIds)); txn.attach(() -> sharingController.addAll(contactIds));
}, e -> logException(LOG, WARNING, e)); });
} }
void deleteForum() { void deleteForum() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forumManager.removeForum(forumManager.getForum(groupId)));
Forum f = forumManager.getForum(groupId);
forumManager.removeForum(f);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
Toast.makeText(getApplication(), R.string.forum_left_toast, Toast.makeText(getApplication(), R.string.forum_left_toast,
LENGTH_SHORT).show(); LENGTH_SHORT).show();
} }

View File

@@ -35,8 +35,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
@@ -48,8 +46,6 @@ 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.android.view.AuthorView.setAvatar;
import static org.briarproject.briar.android.view.TextSendController.SendState;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
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
@@ -205,8 +201,8 @@ public class IntroductionMessageFragment extends BaseFragment
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) { List<AttachmentHeader> headers) {
// disable button to prevent accidental double invitations // disable button to prevent accidental double invitations
ui.message.setReady(false); ui.message.setReady(false);
@@ -216,7 +212,6 @@ public class IntroductionMessageFragment extends BaseFragment
hideSoftKeyboard(ui.message); hideSoftKeyboard(ui.message);
introductionActivity.setResult(RESULT_OK); introductionActivity.setResult(RESULT_OK);
introductionActivity.supportFinishAfterTransition(); introductionActivity.supportFinishAfterTransition();
return new MutableLiveData<>(SENT);
} }
private void makeIntroduction(Contact c1, Contact c2, private void makeIntroduction(Contact c1, Contact c2,
@@ -224,7 +219,8 @@ public class IntroductionMessageFragment extends BaseFragment
introductionActivity.runOnDbThread(() -> { introductionActivity.runOnDbThread(() -> {
// actually make the introduction // actually make the introduction
try { try {
introductionManager.makeIntroduction(c1, c2, text); long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, text, timestamp);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
introductionError(); introductionError();

View File

@@ -1,13 +1,17 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
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 androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon; import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static java.util.Objects.requireNonNull;
@NotNullByDefault @NotNullByDefault
class LoginUtils { class LoginUtils {
@@ -15,7 +19,9 @@ class LoginUtils {
static AlertDialog createKeyStrengthenerErrorDialog(Context ctx) { static AlertDialog createKeyStrengthenerErrorDialog(Context ctx) {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme); new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setIcon(getDialogIcon(ctx, R.drawable.alerts_and_states_error)); Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
builder.setIcon(icon);
builder.setTitle(R.string.dialog_title_cannot_check_password); builder.setTitle(R.string.dialog_title_cannot_check_password);
builder.setMessage(R.string.dialog_message_cannot_check_password); builder.setMessage(R.string.dialog_message_cannot_check_password);
builder.setPositiveButton(R.string.ok, null); builder.setPositiveButton(R.string.ok, null);

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.navdrawer;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -60,7 +59,6 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.view.View.GONE; import static android.view.View.GONE;
@@ -435,7 +433,8 @@ public class NavDrawerActivity extends BriarActivity implements
Transport t = getItem(position); Transport t = getItem(position);
ImageView icon = view.findViewById(R.id.imageView); ImageView icon = view.findViewById(R.id.imageView);
icon.setImageResource(t.iconDrawable); icon.setImageDrawable(ContextCompat.getDrawable(
NavDrawerActivity.this, t.iconDrawable));
icon.setColorFilter(ContextCompat.getColor( icon.setColorFilter(ContextCompat.getColor(
NavDrawerActivity.this, t.iconColor)); NavDrawerActivity.this, t.iconColor));
@@ -477,13 +476,11 @@ public class NavDrawerActivity extends BriarActivity implements
private void showTransportsOnboarding(boolean show, ImageView imageView) { private void showTransportsOnboarding(boolean show, ImageView imageView) {
if (show) { if (show) {
int color = resolveColorAttribute(this, R.attr.colorControlNormal); int color = resolveColorAttribute(this, R.attr.colorControlNormal);
Drawable drawable = VectorDrawableCompat
.create(getResources(), R.drawable.transport_tor, null);
new MaterialTapTargetPrompt.Builder(NavDrawerActivity.this, new MaterialTapTargetPrompt.Builder(NavDrawerActivity.this,
R.style.OnboardingDialogTheme).setTarget(imageView) R.style.OnboardingDialogTheme).setTarget(imageView)
.setPrimaryText(R.string.network_settings_title) .setPrimaryText(R.string.network_settings_title)
.setSecondaryText(R.string.transports_onboarding_text) .setSecondaryText(R.string.transports_onboarding_text)
.setIconDrawable(drawable) .setIcon(R.drawable.transport_tor)
.setIconDrawableColourFilter(color) .setIconDrawableColourFilter(color)
.setBackgroundColour( .setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary)) ContextCompat.getColor(this, R.color.briar_primary))

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.navdrawer;
import android.app.Application; import android.app.Application;
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.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
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;
@@ -66,34 +65,27 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void checkExpiryWarning() { void checkExpiryWarning() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
settingsManager.getSettings(SETTINGS_NAMESPACE);
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
if (warningInt == 0) { if (warningInt == 0) {
// we have not warned before // we have not warned before
showExpiryWarning.postValue(true);
} else {
long warningLong = warningInt * 1000L;
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry = (EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
showExpiryWarning.postValue(true);
} else if (daysBeforeExpiry <= 3 && daysSinceLastWarning > 0) {
showExpiryWarning.postValue(true); showExpiryWarning.postValue(true);
} else { } else {
long warningLong = warningInt * 1000L; showExpiryWarning.postValue(false);
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry =
(EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
showExpiryWarning.postValue(true);
} else if (daysBeforeExpiry <= 3 &&
daysSinceLastWarning > 0) {
showExpiryWarning.postValue(true);
} else {
showExpiryWarning.postValue(false);
}
} }
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
}); });
} }
@@ -101,15 +93,11 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void expiryWarningDismissed() { void expiryWarningDismissed() {
showExpiryWarning.setValue(false); showExpiryWarning.setValue(false);
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = new Settings();
Settings settings = new Settings(); int date = (int) (System.currentTimeMillis() / 1000L);
int date = (int) (System.currentTimeMillis() / 1000L); settings.putInt(EXPIRY_DATE_WARNING, date);
settings.putInt(EXPIRY_DATE_WARNING, date); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -125,15 +113,12 @@ public class NavDrawerViewModel extends DbViewModel {
return; return;
} }
runOnDbThread(() -> { runOnDbThread(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
settingsManager.getSettings(SETTINGS_NAMESPACE); shouldAskForDozeWhitelisting.postValue(ask);
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); }, e -> {
shouldAskForDozeWhitelisting.postValue(ask); logException(LOG, WARNING, e);
} catch (DbException e) { shouldAskForDozeWhitelisting.postValue(true);
logException(LOG, WARNING, e);
shouldAskForDozeWhitelisting.postValue(true);
}
}); });
} }
@@ -145,30 +130,21 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void checkTransportsOnboarding() { void checkTransportsOnboarding() {
if (showTransportsOnboarding.getValue() != null) return; if (showTransportsOnboarding.getValue() != null) return;
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = boolean show =
settingsManager.getSettings(SETTINGS_NAMESPACE); settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
boolean show = showTransportsOnboarding.postValue(show);
settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
showTransportsOnboarding.postValue(show);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@UiThread @UiThread
void transportsOnboardingShown() { void transportsOnboardingShown() {
showTransportsOnboarding.setValue(false); showTransportsOnboarding.setValue(false);
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = new Settings();
Settings settings = new Settings(); settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
} }

View File

@@ -45,12 +45,10 @@ import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
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.now; import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
@@ -185,20 +183,16 @@ public class PluginViewModel extends DbViewModel implements EventListener {
} }
private void loadSettings() { private void loadSettings() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { boolean tor = isPluginEnabled(TorConstants.ID,
boolean tor = isPluginEnabled(TorConstants.ID, TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE); torEnabledSetting.postValue(tor);
torEnabledSetting.postValue(tor); boolean wifi = isPluginEnabled(LanTcpConstants.ID,
boolean wifi = isPluginEnabled(LanTcpConstants.ID, LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE); wifiEnabledSetting.postValue(wifi);
wifiEnabledSetting.postValue(wifi); boolean bt = isPluginEnabled(BluetoothConstants.ID,
boolean bt = isPluginEnabled(BluetoothConstants.ID, BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE); btEnabledSetting.postValue(bt);
btEnabledSetting.postValue(bt);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -222,14 +216,10 @@ public class PluginViewModel extends DbViewModel implements EventListener {
} }
private void mergeSettings(Settings s, String namespace) { private void mergeSettings(Settings s, String namespace) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); settingsManager.mergeSettings(s, namespace);
settingsManager.mergeSettings(s, namespace); logDuration(LOG, "Merging settings", start);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -141,7 +141,8 @@ public class TransportsActivity extends BriarActivity {
Transport t = getItem(position); Transport t = getItem(position);
ImageView icon = view.findViewById(R.id.icon); ImageView icon = view.findViewById(R.id.icon);
icon.setImageResource(t.iconDrawable); icon.setImageDrawable(ContextCompat.getDrawable(
TransportsActivity.this, t.iconDrawable));
icon.setColorFilter(ContextCompat.getColor( icon.setColorFilter(ContextCompat.getColor(
TransportsActivity.this, t.iconColor)); TransportsActivity.this, t.iconColor));

View File

@@ -25,7 +25,6 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH;
@@ -79,12 +78,18 @@ public class GroupActivity extends
// start with group disabled and enable when not dissolved // start with group disabled and enable when not dissolved
setGroupEnabled(false); setGroupEnabled(false);
viewModel.isDissolved().observeEvent(this, dissolved -> { viewModel.isDissolved().observe(this, dissolved -> {
setGroupEnabled(!dissolved); setGroupEnabled(!dissolved);
if (dissolved) onGroupDissolved(); if (dissolved) onGroupDissolved();
}); });
} }
@Override
public void onStart() {
super.onStart();
viewModel.clearGroupMessageNotifications();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar // Inflate the menu items for use in the action bar
@@ -111,26 +116,26 @@ public class GroupActivity extends
startActivity(i); startActivity(i);
return true; return true;
} else if (itemId == R.id.action_group_reveal) { } else if (itemId == R.id.action_group_reveal) {
if (requireNonNull(viewModel.isCreator().getValue())) if (viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
Intent i = new Intent(this, RevealContactsActivity.class); Intent i = new Intent(this, RevealContactsActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i); startActivity(i);
return true; return true;
} else if (itemId == R.id.action_group_invite) { } else if (itemId == R.id.action_group_invite) {
if (!requireNonNull(viewModel.isCreator().getValue())) if (!viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
Intent i = new Intent(this, GroupInviteActivity.class); Intent i = new Intent(this, GroupInviteActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_GROUP_INVITE); startActivityForResult(i, REQUEST_GROUP_INVITE);
return true; return true;
} else if (itemId == R.id.action_group_leave) { } else if (itemId == R.id.action_group_leave) {
if (requireNonNull(viewModel.isCreator().getValue())) if (viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
showLeaveGroupDialog(); showLeaveGroupDialog();
return true; return true;
} else if (itemId == R.id.action_group_dissolve) { } else if (itemId == R.id.action_group_dissolve) {
if (!requireNonNull(viewModel.isCreator().getValue())) if (!viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
showDissolveGroupDialog(); showDissolveGroupDialog();
return true; return true;
@@ -153,8 +158,7 @@ public class GroupActivity extends
@Override @Override
public void onReplyClick(GroupMessageItem item) { public void onReplyClick(GroupMessageItem item) {
Boolean isDissolved = viewModel.isDissolved().getLastValue(); if (!viewModel.isDissolved().getValue()) super.onReplyClick(item);
if (isDissolved != null && !isDissolved) super.onReplyClick(item);
} }
private void setGroupEnabled(boolean enabled) { private void setGroupEnabled(boolean enabled) {
@@ -197,7 +201,7 @@ public class GroupActivity extends
viewModel.deletePrivateGroup(); viewModel.deletePrivateGroup();
} }
private void onGroupDissolved() { public void onGroupDissolved() {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(this, R.style.BriarDialogTheme); new AlertDialog.Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.groups_dissolved_dialog_title)); builder.setTitle(getString(R.string.groups_dissolved_dialog_title));

View File

@@ -22,11 +22,10 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.sharing.SharingController; import org.briarproject.briar.android.sharing.SharingController;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
@@ -53,10 +52,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.lang.Math.max; import static java.lang.Math.max;
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.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.now; import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -71,8 +68,8 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
private final MutableLiveData<PrivateGroup> privateGroup = private final MutableLiveData<PrivateGroup> privateGroup =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>(); private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
private final MutableLiveEvent<Boolean> isDissolved = private final MutableLiveData<Boolean> isDissolved =
new MutableLiveEvent<>(); new MutableLiveData<>();
@Inject @Inject
GroupViewModel(Application application, GroupViewModel(Application application,
@@ -104,10 +101,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
if (!g.isLocal() && g.getGroupId().equals(groupId)) { if (!g.isLocal() && g.getGroupId().equals(groupId)) {
LOG.info("Group message received, adding..."); LOG.info("Group message received, adding...");
GroupMessageItem item = buildItem(g.getHeader(), g.getText()); GroupMessageItem item = buildItem(g.getHeader(), g.getText());
addItem(item, false); addItem(item);
// In case the join message comes from the creator,
// we need to reload the sharing contacts
// in case it was delayed and the sharing count is wrong (#850).
if (item instanceof JoinMessageItem && if (item instanceof JoinMessageItem &&
(((JoinMessageItem) item).isInitial())) { (((JoinMessageItem) item).isInitial())) {
loadSharingContacts(); loadSharingContacts();
@@ -129,7 +123,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} else if (e instanceof GroupDissolvedEvent) { } else if (e instanceof GroupDissolvedEvent) {
GroupDissolvedEvent g = (GroupDissolvedEvent) e; GroupDissolvedEvent g = (GroupDissolvedEvent) e;
if (g.getGroupId().equals(groupId)) { if (g.getGroupId().equals(groupId)) {
isDissolved.setEvent(true); isDissolved.setValue(true);
} }
} else { } else {
super.eventOccurred(e); super.eventOccurred(e);
@@ -137,25 +131,21 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} }
@Override @Override
protected void performInitialLoad() { public void setGroupId(GroupId groupId) {
super.performInitialLoad(); super.setGroupId(groupId);
loadPrivateGroup(groupId); loadPrivateGroup(groupId);
} }
protected void clearNotifications() { public void clearGroupMessageNotifications() {
notificationManager.clearGroupMessageNotification(groupId); notificationManager.clearGroupMessageNotification(groupId);
} }
private void loadPrivateGroup(GroupId groupId) { private void loadPrivateGroup(GroupId groupId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { PrivateGroup g = privateGroupManager.getPrivateGroup(groupId);
PrivateGroup g = privateGroupManager.getPrivateGroup(groupId); privateGroup.postValue(g);
privateGroup.postValue(g); Author author = identityManager.getLocalAuthor();
Author author = identityManager.getLocalAuthor(); isCreator.postValue(g.getCreator().equals(author));
isCreator.postValue(g.getCreator().equals(author));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -164,34 +154,16 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
loadList(txn -> { loadList(txn -> {
// check first if group is dissolved // check first if group is dissolved
isDissolved isDissolved
.postEvent(privateGroupManager.isDissolved(txn, groupId)); .postValue(privateGroupManager.isDissolved(txn, groupId));
// now continue to load the items // now continue to load the items
long start = now(); long start = now();
List<GroupMessageHeader> headers = List<GroupMessageHeader> headers =
privateGroupManager.getHeaders(txn, groupId); privateGroupManager.getHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start); logDuration(LOG, "Loading headers", start);
start = now(); return createItems(txn, headers, this::buildItem);
List<GroupMessageItem> items = new ArrayList<>();
for (GroupMessageHeader header : headers) {
items.add(loadItem(txn, header));
}
logDuration(LOG, "Loading bodies and creating items", start);
return items;
}, this::setItems); }, this::setItems);
} }
private GroupMessageItem loadItem(Transaction txn,
GroupMessageHeader header) throws DbException {
String text;
if (header instanceof JoinMessageHeader) {
// will be looked up later
text = "";
} else {
text = privateGroupManager.getMessageText(txn, header.getId());
}
return buildItem(header, text);
}
private GroupMessageItem buildItem(GroupMessageHeader header, String text) { private GroupMessageItem buildItem(GroupMessageHeader header, String text) {
if (header instanceof JoinMessageHeader) { if (header instanceof JoinMessageHeader) {
return new JoinMessageItem((JoinMessageHeader) header, text); return new JoinMessageItem((JoinMessageHeader) header, text);
@@ -199,21 +171,27 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
return new GroupMessageItem(header, text); return new GroupMessageItem(header, text);
} }
@Override
protected String loadMessageText(
Transaction txn, PostHeader header) throws DbException {
if (header instanceof JoinMessageHeader) {
// will be looked up later
return "";
}
return privateGroupManager.getMessageText(txn, header.getId());
}
@Override @Override
public void createAndStoreMessage(String text, public void createAndStoreMessage(String text,
@Nullable MessageId parentId) { @Nullable MessageId parentId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { LocalAuthor author = identityManager.getLocalAuthor();
LocalAuthor author = identityManager.getLocalAuthor(); MessageId previousMsgId =
MessageId previousMsgId = privateGroupManager.getPreviousMsgId(groupId);
privateGroupManager.getPreviousMsgId(groupId); GroupCount count = privateGroupManager.getGroupCount(groupId);
GroupCount count = privateGroupManager.getGroupCount(groupId); long timestamp = count.getLatestMsgTime();
long timestamp = count.getLatestMsgTime(); timestamp = max(clock.currentTimeMillis(), timestamp + 1);
timestamp = max(clock.currentTimeMillis(), timestamp + 1); createMessage(text, timestamp, parentId, author, previousMsgId);
createMessage(text, timestamp, parentId, author, previousMsgId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -229,49 +207,36 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} }
private void storePost(GroupMessage msg, String text) { private void storePost(GroupMessage msg, String text) {
runOnDbThread(false, txn -> { runOnDbThreadOrLogException(() -> {
long start = now(); long start = now();
GroupMessageHeader header = GroupMessageHeader header =
privateGroupManager.addLocalMessage(txn, msg); privateGroupManager.addLocalMessage(msg);
addItemAsync(buildItem(header, text));
logDuration(LOG, "Storing group message", start); logDuration(LOG, "Storing group message", start);
txn.attach(() -> });
addItem(buildItem(header, text), true)
);
}, e -> logException(LOG, WARNING, e));
} }
@Override @Override
protected void markItemRead(GroupMessageItem item) { protected void markItemRead(GroupMessageItem item) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { privateGroupManager.setReadFlag(groupId, item.getId(), true));
privateGroupManager.setReadFlag(groupId, item.getId(), true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
public void loadSharingContacts() { public void loadSharingContacts() {
runOnDbThread(true, txn -> { runOnDbThreadOrLogException(true, txn -> {
Collection<GroupMember> members = Collection<GroupMember> members =
privateGroupManager.getMembers(txn, groupId); privateGroupManager.getMembers(txn, groupId);
Collection<ContactId> contactIds = new ArrayList<>(); Collection<ContactId> contactIds = new ArrayList<>();
for (GroupMember m : members) { for (GroupMember m : members) {
if (m.getContactId() != null) if (m.getContactId() != null) contactIds.add(m.getContactId());
contactIds.add(m.getContactId());
} }
txn.attach(() -> sharingController.addAll(contactIds)); txn.attach(() -> sharingController.addAll(contactIds));
}, e -> logException(LOG, WARNING, e)); });
} }
void deletePrivateGroup() { void deletePrivateGroup() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { privateGroupManager.removePrivateGroup(groupId));
privateGroupManager.removePrivateGroup(groupId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
LiveData<PrivateGroup> getPrivateGroup() { LiveData<PrivateGroup> getPrivateGroup() {
@@ -282,7 +247,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
return isCreator; return isCreator;
} }
LiveEvent<Boolean> isDissolved() { LiveData<Boolean> isDissolved() {
return isDissolved; return isDissolved;
} }

View File

@@ -16,7 +16,7 @@ class JoinMessageItem extends GroupMessageItem {
JoinMessageItem(JoinMessageHeader h, String text) { JoinMessageItem(JoinMessageHeader h, String text) {
super(h, text); super(h, text);
isInitial = h.isInitial(); this.isInitial = h.isInitial();
} }
@Override @Override

View File

@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
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.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -17,8 +15,6 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
@@ -40,8 +36,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -50,12 +44,9 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
implements CreateGroupController { implements CreateGroupController {
private static final Logger LOG = private static final Logger LOG =
getLogger(CreateGroupControllerImpl.class.getName()); Logger.getLogger(CreateGroupControllerImpl.class.getName());
private final Executor cryptoExecutor; private final Executor cryptoExecutor;
private final TransactionManager db;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final PrivateGroupFactory groupFactory; private final PrivateGroupFactory groupFactory;
@@ -66,27 +57,17 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private final Clock clock; private final Clock clock;
@Inject @Inject
CreateGroupControllerImpl( CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
TransactionManager db, LifecycleManager lifecycleManager, ContactManager contactManager,
AutoDeleteManager autoDeleteManager, AuthorManager authorManager, IdentityManager identityManager,
ConversationManager conversationManager,
LifecycleManager lifecycleManager,
ContactManager contactManager,
AuthorManager authorManager,
IdentityManager identityManager,
PrivateGroupFactory groupFactory, PrivateGroupFactory groupFactory,
GroupMessageFactory groupMessageFactory, GroupMessageFactory groupMessageFactory,
PrivateGroupManager groupManager, PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory, GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager, GroupInvitationManager groupInvitationManager, Clock clock) {
Clock clock) {
super(dbExecutor, lifecycleManager, contactManager, authorManager); super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.groupFactory = groupFactory; this.groupFactory = groupFactory;
@@ -150,14 +131,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
db.transaction(false, txn -> { LocalAuthor localAuthor = identityManager.getLocalAuthor();
LocalAuthor localAuthor = List<Contact> contacts = new ArrayList<>();
identityManager.getLocalAuthor(txn); for (ContactId c : contactIds) {
List<InvitationContext> contexts = try {
createInvitationContexts(txn, contactIds); contacts.add(contactManager.getContact(c));
txn.attach(() -> signInvitations(g, localAuthor, contexts, } catch (NoSuchContactException e) {
text, handler)); // Continue
}); }
}
signInvitations(g, localAuthor, contacts, text, handler);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
handler.onException(e); handler.onException(e);
@@ -165,32 +148,17 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
}); });
} }
private List<InvitationContext> createInvitationContexts(Transaction txn,
Collection<ContactId> contactIds) throws DbException {
List<InvitationContext> contexts = new ArrayList<>();
for (ContactId c : contactIds) {
try {
Contact contact = contactManager.getContact(txn, c);
long timestamp = conversationManager
.getTimestampForOutgoingMessage(txn, c);
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
contexts.add(new InvitationContext(contact, timestamp, timer));
} catch (NoSuchContactException e) {
// Continue
}
}
return contexts;
}
private void signInvitations(GroupId g, LocalAuthor localAuthor, private void signInvitations(GroupId g, LocalAuthor localAuthor,
List<InvitationContext> contexts, @Nullable String text, Collection<Contact> contacts, @Nullable String text,
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
for (InvitationContext ctx : contexts) { long timestamp = clock.currentTimeMillis();
ctx.signature = groupInvitationFactory.signInvitation( List<InvitationContext> contexts = new ArrayList<>();
ctx.contact, g, ctx.timestamp, for (Contact c : contacts) {
localAuthor.getPrivateKey()); byte[] signature = groupInvitationFactory.signInvitation(c, g,
timestamp, localAuthor.getPrivateKey());
contexts.add(new InvitationContext(c.getId(), timestamp,
signature));
} }
sendInvitations(g, contexts, text, handler); sendInvitations(g, contexts, text, handler);
}); });
@@ -201,12 +169,11 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) { ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
for (InvitationContext ctx : contexts) { for (InvitationContext context : contexts) {
try { try {
groupInvitationManager.sendInvitation(g, groupInvitationManager.sendInvitation(g,
ctx.contact.getId(), text, ctx.timestamp, context.contactId, text, context.timestamp,
requireNonNull(ctx.signature), context.signature);
ctx.autoDeleteTimer);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
// Continue // Continue
} }
@@ -221,16 +188,15 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private static class InvitationContext { private static class InvitationContext {
private final Contact contact; private final ContactId contactId;
private final long timestamp, autoDeleteTimer; private final long timestamp;
@Nullable private final byte[] signature;
private byte[] signature = null;
private InvitationContext(Contact contact, long timestamp, private InvitationContext(ContactId contactId, long timestamp,
long autoDeleteTimer) { byte[] signature) {
this.contact = contact; this.contactId = contactId;
this.timestamp = timestamp; this.timestamp = timestamp;
this.autoDeleteTimer = autoDeleteTimer; this.signature = signature;
} }
} }
} }

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -49,10 +48,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
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.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.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
@@ -200,25 +197,17 @@ class GroupListViewModel extends DbViewModel implements EventListener {
} }
void removeGroup(GroupId g) { void removeGroup(GroupId g) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); groupManager.removePrivateGroup(g);
groupManager.removePrivateGroup(g); logDuration(LOG, "Removing group", start);
logDuration(LOG, "Removing group", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
void loadNumInvitations() { void loadNumInvitations() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { int i = groupInvitationManager.getInvitations().size();
int i = groupInvitationManager.getInvitations().size(); numInvitations.postValue(i);
numInvitations.postValue(i);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -15,7 +15,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.LargeTextInputView; import org.briarproject.briar.android.view.LargeTextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextSendController.SendState;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List; import java.util.List;
@@ -23,10 +22,6 @@ import java.util.List;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -84,14 +79,13 @@ public abstract class BaseMessageFragment extends BaseFragment
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) { List<AttachmentHeader> headers) {
// disable button to prevent accidental double actions // disable button to prevent accidental double actions
sendController.setReady(false); sendController.setReady(false);
message.hideSoftKeyboard(); message.hideSoftKeyboard();
listener.onButtonClick(text); listener.onButtonClick(text);
return new MutableLiveData<>(SENT);
} }
@UiThread @UiThread

View File

@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
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.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection; import java.util.Collection;
@@ -24,7 +26,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -33,17 +34,22 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
implements ShareBlogController { implements ShareBlogController {
private final static Logger LOG = private final static Logger LOG =
getLogger(ShareBlogControllerImpl.class.getName()); Logger.getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager; private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject @Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager, AuthorManager authorManager,
BlogSharingManager blogSharingManager) { ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager, authorManager); super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager; this.blogSharingManager = blogSharingManager;
this.clock = clock;
} }
@Override @Override
@@ -58,7 +64,10 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
try { try {
for (ContactId c : contacts) { for (ContactId c : contacts) {
try { try {
blogSharingManager.sendInvitation(g, c, text); long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
blogSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) { } catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -10,8 +10,10 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
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.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl; import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
@@ -24,7 +26,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@@ -33,17 +34,22 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
implements ShareForumController { implements ShareForumController {
private final static Logger LOG = private final static Logger LOG =
getLogger(ShareForumControllerImpl.class.getName()); Logger.getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager; private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject @Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor, ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager, LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager, AuthorManager authorManager,
ForumSharingManager forumSharingManager) { ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager, authorManager); super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
this.clock = clock;
} }
@Override @Override
@@ -58,7 +64,10 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
try { try {
for (ContactId c : contacts) { for (ContactId c : contacts) {
try { try {
forumSharingManager.sendInvitation(g, c, text); long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
forumSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) { } catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -4,7 +4,8 @@ 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.android.util.ItemReturningAdapter; import org.briarproject.briar.android.util.ItemReturningAdapter;
@@ -20,7 +21,8 @@ import androidx.recyclerview.widget.ListAdapter;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
@UiThread @UiThread
@NotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ThreadItemAdapter<I extends ThreadItem> public class ThreadItemAdapter<I extends ThreadItem>
extends ListAdapter<I, BaseThreadItemViewHolder<I>> extends ListAdapter<I, BaseThreadItemViewHolder<I>>
implements ItemReturningAdapter<I> { implements ItemReturningAdapter<I> {
@@ -39,7 +41,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Override @Override
public boolean areContentsTheSame(I a, I b) { public boolean areContentsTheSame(I a, I b) {
return a.isHighlighted() == b.isHighlighted() && return a.isHighlighted() == b.isHighlighted() &&
a.isRead() == b.isRead(); a.isRead() && b.isRead();
} }
}); });
this.listener = listener; this.listener = listener;
@@ -61,7 +63,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
ui.bind(item, listener); ui.bind(item, listener);
} }
int findItemPosition(MessageId id) { public int findItemPosition(MessageId id) {
for (int i = 0; i < getItemCount(); i++) { for (int i = 0; i < getItemCount(); i++) {
if (id.equals(getItem(i).getId())) return i; if (id.equals(getItem(i).getId())) return i;
} }
@@ -89,7 +91,8 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Nullable @Nullable
I getHighlightedItem() { I getHighlightedItem() {
for (I item : getCurrentList()) { for (int i = 0; i < getItemCount(); i++) {
I item = getItem(i);
if (item.isHighlighted()) return item; if (item.isHighlighted()) return item;
} }
return null; return null;

View File

@@ -19,7 +19,6 @@ import org.briarproject.briar.android.view.BriarRecyclerView;
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.android.view.TextSendController.SendState;
import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.android.view.UnreadMessageButton;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
@@ -30,13 +29,10 @@ import javax.annotation.Nullable;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -114,7 +110,7 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
getViewModel().blockAndClearNotifications(); getViewModel().blockNotifications();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@@ -235,8 +231,8 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
} }
@Override @Override
public LiveData<SendState> onSendClick(@Nullable String text, public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) { List<AttachmentHeader> headers) {
if (isNullOrEmpty(text)) throw new AssertionError(); if (isNullOrEmpty(text)) throw new AssertionError();
MessageId replyId = getViewModel().getReplyId(); MessageId replyId = getViewModel().getReplyId();
@@ -245,7 +241,6 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
textInput.clearText(); textInput.clearText();
getViewModel().setReplyId(null); getViewModel().setReplyId(null);
updateTextInput(); updateTextInput();
return new MutableLiveData<>(SENT);
} }
protected abstract int getMaxTextLength(); protected abstract int getMaxTextLength();

View File

@@ -5,6 +5,7 @@ import android.app.Application;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager; 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;
@@ -25,8 +26,11 @@ import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTree; import org.briarproject.briar.api.client.MessageTree;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.client.MessageTreeImpl; import org.briarproject.briar.client.MessageTreeImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -39,16 +43,16 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class ThreadListViewModel<I extends ThreadItem> public abstract class ThreadListViewModel<I extends ThreadItem>
extends DbViewModel implements EventListener { extends DbViewModel
implements EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(ThreadListViewModel.class.getName()); getLogger(ThreadListViewModel.class.getName());
@@ -61,7 +65,7 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
private final MessageTracker messageTracker; private final MessageTracker messageTracker;
private final EventBus eventBus; private final EventBus eventBus;
// UIThread @DatabaseExecutor
private final MessageTree<I> messageTree = new MessageTreeImpl<>(); private final MessageTree<I> messageTree = new MessageTreeImpl<>();
private final MutableLiveData<LiveResult<List<I>>> items = private final MutableLiveData<LiveResult<List<I>>> items =
new MutableLiveData<>(); new MutableLiveData<>();
@@ -73,9 +77,6 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
protected volatile GroupId groupId; protected volatile GroupId groupId;
@Nullable @Nullable
private MessageId replyId; private MessageId replyId;
/**
* Stored list position. Needs to be loaded and set before the list itself.
*/
private final AtomicReference<MessageId> storedMessageId = private final AtomicReference<MessageId> storedMessageId =
new AtomicReference<>(); new AtomicReference<>();
@@ -113,28 +114,19 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
* Needs to be called right after initialization, * Needs to be called right after initialization,
* before calling any other methods. * before calling any other methods.
*/ */
public final void setGroupId(GroupId groupId) {
boolean needsInitialLoad = this.groupId == null;
this.groupId = groupId;
if (needsInitialLoad) performInitialLoad();
}
@CallSuper @CallSuper
protected void performInitialLoad() { public void setGroupId(GroupId groupId) {
// load stored MessageId (last list position) before the list itself this.groupId = groupId;
loadStoredMessageId(); loadStoredMessageId();
loadItems(); loadItems();
loadSharingContacts(); loadSharingContacts();
} }
protected abstract void clearNotifications(); public void blockNotifications() {
void blockAndClearNotifications() {
notificationManager.blockNotification(groupId); notificationManager.blockNotification(groupId);
clearNotifications();
} }
void unblockNotifications() { public void unblockNotifications() {
notificationManager.unblockNotification(groupId); notificationManager.unblockNotification(groupId);
} }
@@ -151,16 +143,11 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
} }
private void loadStoredMessageId() { private void loadStoredMessageId() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { storedMessageId.set(messageTracker.loadStoredMessageId(groupId));
storedMessageId if (LOG.isLoggable(INFO)) {
.set(messageTracker.loadStoredMessageId(groupId)); LOG.info("Loaded last top visible message id " +
if (LOG.isLoggable(INFO)) { storedMessageId);
LOG.info("Loaded last top visible message id " +
storedMessageId);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
}); });
} }
@@ -174,63 +161,75 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
* Loads the ContactIds of all contacts the group is shared with * Loads the ContactIds of all contacts the group is shared with
* and adds them to {@link SharingController}. * and adds them to {@link SharingController}.
*/ */
protected abstract void loadSharingContacts(); public abstract void loadSharingContacts();
@UiThread @UiThread
protected void setItems(LiveResult<List<I>> items) { protected void setItems(LiveResult<List<I>> items) {
if (items.hasError()) { this.items.setValue(items);
this.items.setValue(items); }
} else {
messageTree.clear(); @DatabaseExecutor
// not null, because hasError() is false protected <H extends PostHeader> List<I> createItems(
messageTree.add(requireNonNull(items.getResultOrNull())); Transaction txn, Collection<H> headers, ItemGetter<H, I> itemGetter)
LiveResult<List<I>> result = throws DbException {
new LiveResult<>(messageTree.depthFirstOrder()); long start = now();
this.items.setValue(result); List<I> items = new ArrayList<>();
for (H header : headers) {
String text = loadMessageText(txn, header);
items.add(itemGetter.getItem(header, text));
} }
logDuration(LOG, "Loading bodies and creating items", start);
messageTree.clear();
messageTree.add(items);
return messageTree.depthFirstOrder();
} }
/** /**
* Add a remote item on the UI thread. * Add a remote item on the UI thread.
* * The list will not scroll, but show an unread indicator.
* @param scrollToItem whether the list will scroll to the newly added item
*/ */
@UiThread @UiThread
protected void addItem(I item, boolean scrollToItem) { protected void addItem(I item) {
messageTree.add(item); messageTree.add(item);
if (scrollToItem) this.scrollToItem.set(item.getId());
items.setValue(new LiveResult<>(messageTree.depthFirstOrder())); items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));
} }
/**
* Add a local item from the DB thread.
* The list will scroll to the new item.
*/
@DatabaseExecutor
protected void addItemAsync(I item) {
messageTree.add(item);
scrollToItem.set(item.getId());
items.postValue(new LiveResult<>(messageTree.depthFirstOrder()));
}
@DatabaseExecutor
protected abstract String loadMessageText(Transaction txn,
PostHeader header) throws DbException;
@UiThread @UiThread
void setReplyId(@Nullable MessageId id) { public void setReplyId(@Nullable MessageId id) {
replyId = id; replyId = id;
} }
@UiThread @UiThread
@Nullable @Nullable
MessageId getReplyId() { public MessageId getReplyId() {
return replyId; return replyId;
} }
void storeMessageId(@Nullable MessageId messageId) { void storeMessageId(@Nullable MessageId messageId) {
if (messageId != null) { if (messageId != null) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { messageTracker.storeMessageId(groupId, messageId));
messageTracker.storeMessageId(groupId, messageId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
} }
protected abstract void markItemRead(I item); protected abstract void markItemRead(I item);
/**
* Returns the {@link MessageId} of the item that was at the top of the
* list last time or null if there has been nothing stored, yet.
*/
@Nullable @Nullable
MessageId getAndResetRestoredMessageId() { MessageId getAndResetRestoredMessageId() {
return storedMessageId.getAndSet(null); return storedMessageId.getAndSet(null);
@@ -253,4 +252,8 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
return scrollToItem.getAndSet(null); return scrollToItem.getAndSet(null);
} }
public interface ItemGetter<H extends PostHeader, I> {
I getItem(H header, String text);
}
} }

View File

@@ -54,7 +54,6 @@ import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
@@ -89,6 +88,7 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode; import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.content.ContextCompat.getSystemService; import static androidx.core.content.ContextCompat.getSystemService;
import static androidx.core.graphics.drawable.DrawableCompat.setTint; import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
@@ -436,8 +436,7 @@ public class UiUtils {
} }
public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) { public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
Drawable icon = Drawable icon = getDrawable(ctx, resId);
VectorDrawableCompat.create(ctx.getResources(), resId, null);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary)); setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
return icon; return icon;
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.view;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -52,8 +53,9 @@ public class BriarRecyclerView extends FrameLayout {
R.styleable.BriarRecyclerView); R.styleable.BriarRecyclerView);
isScrollingToEnd = attributes isScrollingToEnd = attributes
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true); .getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
int drawableRes = attributes.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1); Drawable drawable = attributes
if (drawableRes != -1) setEmptyImage(drawableRes); .getDrawable(R.styleable.BriarRecyclerView_emptyImage);
if (drawable != null) setEmptyImage(drawable);
String emtpyText = String emtpyText =
attributes.getString(R.styleable.BriarRecyclerView_emptyText); attributes.getString(R.styleable.BriarRecyclerView_emptyText);
if (emtpyText != null) setEmptyText(emtpyText); if (emtpyText != null) setEmptyText(emtpyText);
@@ -137,6 +139,11 @@ public class BriarRecyclerView extends FrameLayout {
} }
} }
public void setEmptyImage(Drawable drawable) {
if (recyclerView == null) initViews();
emptyImage.setImageDrawable(drawable);
}
public void setEmptyImage(@DrawableRes int res) { public void setEmptyImage(@DrawableRes int res) {
if (recyclerView == null) initViews(); if (recyclerView == null) initViews();
emptyImage.setImageResource(res); emptyImage.setImageResource(res);

View File

@@ -5,7 +5,6 @@ import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -20,7 +19,6 @@ import static java.util.Objects.requireNonNull;
public class CompositeSendButton extends FrameLayout { public class CompositeSendButton extends FrameLayout {
private final AppCompatImageButton sendButton, imageButton; private final AppCompatImageButton sendButton, imageButton;
private final ImageView bombBadge;
private final ProgressBar progressBar; private final ProgressBar progressBar;
private boolean hasImageSupport = false; private boolean hasImageSupport = false;
@@ -34,7 +32,6 @@ public class CompositeSendButton extends FrameLayout {
sendButton = findViewById(R.id.sendButton); sendButton = findViewById(R.id.sendButton);
imageButton = findViewById(R.id.imageButton); imageButton = findViewById(R.id.imageButton);
bombBadge = findViewById(R.id.bombBadge);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
} }
@@ -74,10 +71,6 @@ public class CompositeSendButton extends FrameLayout {
return hasImageSupport; return hasImageSupport;
} }
public void setBombVisible(boolean visible) {
bombBadge.setVisibility(visible ? VISIBLE : INVISIBLE);
}
public void showImageButton(boolean showImageButton, boolean sendEnabled) { public void showImageButton(boolean showImageButton, boolean sendEnabled) {
if (showImageButton) { if (showImageButton) {
imageButton.setVisibility(VISIBLE); imageButton.setVisibility(VISIBLE);

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@@ -26,9 +25,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AlertDialog.Builder;
import androidx.customview.view.AbsSavedState; import androidx.customview.view.AbsSavedState;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
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;
@@ -38,7 +37,6 @@ import static androidx.core.content.ContextCompat.getColor;
import static androidx.customview.view.AbsSavedState.EMPTY_STATE; import static androidx.customview.view.AbsSavedState.EMPTY_STATE;
import static androidx.lifecycle.Lifecycle.State.DESTROYED; import static androidx.lifecycle.Lifecycle.State.DESTROYED;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute; import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE; import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
@UiThread @UiThread
@@ -52,6 +50,7 @@ public class TextAttachmentController extends TextSendController
private final AttachmentManager attachmentManager; private final AttachmentManager attachmentManager;
private final List<Uri> imageUris = new ArrayList<>(); private final List<Uri> imageUris = new ArrayList<>();
private final CharSequence textHint;
private boolean loadingUris = false; private boolean loadingUris = false;
public TextAttachmentController(TextInputView v, ImagePreview imagePreview, public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
@@ -65,44 +64,23 @@ public class TextAttachmentController extends TextSendController
sendButton = (CompositeSendButton) compositeSendButton; sendButton = (CompositeSendButton) compositeSendButton;
sendButton.setOnImageClickListener(view -> onImageButtonClicked()); sendButton.setOnImageClickListener(view -> onImageButtonClicked());
textHint = textInput.getHint();
} }
@Override @Override
protected void updateViewState() { protected void updateViewState() {
super.updateViewState(); textInput.setEnabled(ready && !loadingUris);
boolean sendEnabled = ready && !loadingUris &&
(!textIsEmpty || canSendEmptyText());
if (loadingUris) { if (loadingUris) {
sendButton.showProgress(true); sendButton.showProgress(true);
} else if (imageUris.isEmpty()) { } else if (imageUris.isEmpty()) {
sendButton.showProgress(false); sendButton.showProgress(false);
sendButton.showImageButton(textIsEmpty, isSendButtonEnabled()); sendButton.showImageButton(textIsEmpty, sendEnabled);
} else { } else {
sendButton.showProgress(false); sendButton.showProgress(false);
sendButton.showImageButton(false, isSendButtonEnabled()); sendButton.showImageButton(false, sendEnabled);
}
}
@Override
protected boolean isTextInputEnabled() {
return super.isTextInputEnabled() && !loadingUris;
}
@Override
protected boolean isSendButtonEnabled() {
return super.isSendButtonEnabled() && !loadingUris;
}
@Override
protected boolean isBombVisible() {
return super.isBombVisible() && (!textIsEmpty || !imageUris.isEmpty());
}
@Override
protected CharSequence getCurrentTextHint() {
if (imageUris.isEmpty()) {
return super.getCurrentTextHint();
} else {
Context ctx = textInput.getContext();
return ctx.getString(R.string.image_caption_hint);
} }
} }
@@ -111,17 +89,11 @@ public class TextAttachmentController extends TextSendController
if (canSend()) { if (canSend()) {
if (loadingUris) throw new AssertionError(); if (loadingUris) throw new AssertionError();
listener.onSendClick(textInput.getText(), listener.onSendClick(textInput.getText(),
attachmentManager.getAttachmentHeadersForSending(), attachmentManager.getAttachmentHeadersForSending());
expectedTimer).observe(listener, this::onSendStateChanged); reset();
} }
} }
@Override
protected void onSendStateChanged(SendState sendState) {
super.onSendStateChanged(sendState);
if (sendState == SENT) reset();
}
@Override @Override
protected boolean canSendEmptyText() { protected boolean canSendEmptyText() {
return !imageUris.isEmpty(); return !imageUris.isEmpty();
@@ -180,7 +152,6 @@ public class TextAttachmentController extends TextSendController
private void onNewUris(boolean restart, List<Uri> newUris) { private void onNewUris(boolean restart, List<Uri> newUris) {
if (newUris.isEmpty()) return; if (newUris.isEmpty()) return;
if (loadingUris) throw new AssertionError(); if (loadingUris) throw new AssertionError();
if (textIsEmpty) onStartingMessage();
loadingUris = true; loadingUris = true;
if (newUris.size() > MAX_ATTACHMENTS_PER_MESSAGE) { if (newUris.size() > MAX_ATTACHMENTS_PER_MESSAGE) {
newUris = newUris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE); newUris = newUris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE);
@@ -188,6 +159,7 @@ public class TextAttachmentController extends TextSendController
} }
imageUris.addAll(newUris); imageUris.addAll(newUris);
updateViewState(); updateViewState();
textInput.setHint(R.string.image_caption_hint);
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris); List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
imagePreview.showPreview(items); imagePreview.showPreview(items);
// store attachments and show preview when successful // store attachments and show preview when successful
@@ -233,6 +205,8 @@ public class TextAttachmentController extends TextSendController
} }
private void reset() { private void reset() {
// restore hint
textInput.setHint(textHint);
// hide image layout // hide image layout
imagePreview.setVisibility(GONE); imagePreview.setVisibility(GONE);
// reset image URIs // reset image URIs
@@ -279,14 +253,12 @@ public class TextAttachmentController extends TextSendController
public void showImageOnboarding(Activity activity) { public void showImageOnboarding(Activity activity) {
int color = resolveColorAttribute(activity, R.attr.colorControlNormal); int color = resolveColorAttribute(activity, R.attr.colorControlNormal);
Drawable drawable = VectorDrawableCompat
.create(activity.getResources(), R.drawable.ic_image, null);
new MaterialTapTargetPrompt.Builder(activity, new MaterialTapTargetPrompt.Builder(activity,
R.style.OnboardingDialogTheme).setTarget(sendButton) R.style.OnboardingDialogTheme).setTarget(sendButton)
.setPrimaryText(R.string.dialog_title_image_support) .setPrimaryText(R.string.dialog_title_image_support)
.setSecondaryText(R.string.dialog_message_image_support) .setSecondaryText(R.string.dialog_message_image_support)
.setBackgroundColour(getColor(activity, R.color.briar_primary)) .setBackgroundColour(getColor(activity, R.color.briar_primary))
.setIconDrawable(drawable) .setIcon(R.drawable.ic_image)
.setIconDrawableColourFilter(color) .setIconDrawableColourFilter(color)
.show(); .show();
} }
@@ -327,7 +299,7 @@ public class TextAttachmentController extends TextSendController
} }
@UiThread @UiThread
public interface AttachmentListener extends SendListener { public interface AttachmentListener extends SendListener, LifecycleOwner {
void onAttachImage(Intent intent); void onAttachImage(Intent intent);

View File

@@ -1,9 +1,7 @@
package org.briarproject.briar.android.view; package org.briarproject.briar.android.view;
import android.content.Context;
import android.os.Parcelable; import android.os.Parcelable;
import android.view.View; import android.view.View;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@@ -14,20 +12,11 @@ import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List; import java.util.List;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import static android.widget.Toast.LENGTH_LONG;
import static com.google.android.material.snackbar.Snackbar.LENGTH_SHORT; import static com.google.android.material.snackbar.Snackbar.LENGTH_SHORT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static org.briarproject.briar.android.view.TextSendController.SendState.ERROR;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
import static org.briarproject.briar.android.view.TextSendController.SendState.UNEXPECTED_TIMER;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -37,12 +26,8 @@ public class TextSendController implements TextInputListener {
protected final View compositeSendButton; protected final View compositeSendButton;
protected final SendListener listener; protected final SendListener listener;
protected boolean textIsEmpty = true; protected boolean ready = true, textIsEmpty = true;
private boolean ready = true;
private long currentTimer = NO_AUTO_DELETE_TIMER;
protected long expectedTimer = NO_AUTO_DELETE_TIMER;
private final CharSequence defaultHint;
private final boolean allowEmptyText; private final boolean allowEmptyText;
public TextSendController(TextInputView v, SendListener listener, public TextSendController(TextInputView v, SendListener listener,
@@ -51,91 +36,31 @@ public class TextSendController implements TextInputListener {
this.compositeSendButton.setOnClickListener(view -> onSendEvent()); this.compositeSendButton.setOnClickListener(view -> onSendEvent());
this.listener = listener; this.listener = listener;
this.textInput = v.getEmojiTextInputView(); this.textInput = v.getEmojiTextInputView();
this.defaultHint = textInput.getHint();
this.allowEmptyText = allowEmptyText; this.allowEmptyText = allowEmptyText;
} }
@Override @Override
public void onTextIsEmptyChanged(boolean isEmpty) { public void onTextIsEmptyChanged(boolean isEmpty) {
textIsEmpty = isEmpty; textIsEmpty = isEmpty;
if (!isEmpty) onStartingMessage();
updateViewState(); updateViewState();
} }
@Override @Override
public void onSendEvent() { public void onSendEvent() {
if (canSend()) { if (canSend()) {
listener.onSendClick(textInput.getText(), emptyList(), listener.onSendClick(textInput.getText(), emptyList());
expectedTimer).observe(listener, this::onSendStateChanged);
} }
} }
@CallSuper
protected void onSendStateChanged(SendState sendState) {
if (sendState == SENT) {
textInput.clearText();
} else if (sendState == UNEXPECTED_TIMER) {
boolean enabled = expectedTimer == NO_AUTO_DELETE_TIMER;
showTimerChangedDialog(enabled);
} else if (sendState == ERROR) {
Toast.makeText(textInput.getContext(), R.string.message_error,
LENGTH_LONG).show();
}
}
/**
* Call whenever the user starts a new message,
* either by entering text or adding an attachment.
* This updates the expected auto-delete timer to the current value.
*/
protected void onStartingMessage() {
expectedTimer = currentTimer;
}
public void setReady(boolean ready) { public void setReady(boolean ready) {
this.ready = ready; this.ready = ready;
updateViewState(); updateViewState();
} }
/**
* Sets the current auto delete timer and updates the UI accordingly.
*/
public void setAutoDeleteTimer(long timer) {
currentTimer = timer;
updateViewState();
}
@CallSuper
protected void updateViewState() { protected void updateViewState() {
textInput.setEnabled(isTextInputEnabled()); textInput.setEnabled(ready);
textInput.setHint(getCurrentTextHint()); compositeSendButton
compositeSendButton.setEnabled(isSendButtonEnabled()); .setEnabled(ready && (!textIsEmpty || canSendEmptyText()));
if (compositeSendButton instanceof CompositeSendButton) {
CompositeSendButton sendButton =
(CompositeSendButton) compositeSendButton;
sendButton.setBombVisible(isBombVisible());
}
}
protected boolean isTextInputEnabled() {
return ready;
}
protected boolean isSendButtonEnabled() {
return ready && (!textIsEmpty || canSendEmptyText());
}
protected boolean isBombVisible() {
return currentTimer != NO_AUTO_DELETE_TIMER;
}
protected CharSequence getCurrentTextHint() {
if (currentTimer == NO_AUTO_DELETE_TIMER) {
return defaultHint;
} else {
Context ctx = textInput.getContext();
return ctx.getString(R.string.message_hint_auto_delete);
}
} }
protected final boolean canSend() { protected final boolean canSend() {
@@ -151,23 +76,6 @@ public class TextSendController implements TextInputListener {
return allowEmptyText; return allowEmptyText;
} }
private void showTimerChangedDialog(boolean enabled) {
Context ctx = textInput.getContext();
int message =
enabled ? R.string.auto_delete_changed_warning_message_enabled :
R.string.auto_delete_changed_warning_message_disabled;
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.auto_delete_changed_warning_title)
.setMessage(message)
.setPositiveButton(R.string.auto_delete_changed_warning_send,
(dialog, which) -> {
expectedTimer = currentTimer;
onSendEvent();
})
.setNegativeButton(R.string.cancel, null)
.show();
}
@Nullable @Nullable
public Parcelable onSaveInstanceState(@Nullable Parcelable superState) { public Parcelable onSaveInstanceState(@Nullable Parcelable superState) {
return superState; return superState;
@@ -178,11 +86,9 @@ public class TextSendController implements TextInputListener {
return state; return state;
} }
public enum SendState {SENT, ERROR, UNEXPECTED_TIMER} @UiThread
public interface SendListener {
public interface SendListener extends LifecycleOwner { void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
LiveData<SendState> onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer);
} }
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.briar.R;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
@UiThread @UiThread
public class TrustIndicatorView extends AppCompatImageView { public class TrustIndicatorView extends AppCompatImageView {
@@ -43,7 +44,7 @@ public class TrustIndicatorView extends AppCompatImageView {
default: default:
res = R.drawable.trust_indicator_unknown; res = R.drawable.trust_indicator_unknown;
} }
setImageResource(res); setImageDrawable(ContextCompat.getDrawable(getContext(), res));
setVisibility(VISIBLE); setVisibility(VISIBLE);
invalidate(); invalidate();

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.viewmodel;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbCallable; import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -66,7 +67,8 @@ public abstract class DbViewModel extends AndroidViewModel {
* {@link RecyclerView.Adapter}, * {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead. * use {@link #loadList(DbCallable, UiConsumer)} instead.
*/ */
protected void runOnDbThread(Runnable task) { protected void runOnDbThread(ThrowingRunnable<Exception> task,
Consumer<Exception> err) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
@@ -74,6 +76,8 @@ public abstract class DbViewModel extends AndroidViewModel {
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for database"); LOG.warning("Interrupted while waiting for database");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (Exception e) {
err.accept(e);
} }
}); });
} }
@@ -101,6 +105,34 @@ public abstract class DbViewModel extends AndroidViewModel {
}); });
} }
/**
* Waits for the DB to open and runs the given task on the
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
* it's caught and logged.
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThreadOrLogException(boolean readOnly,
DbRunnable<Exception> task) {
runOnDbThread(readOnly, task, e -> logException(LOG, WARNING, e));
}
/**
* Waits for the DB to open and runs the given task on the
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
* it's caught and logged.
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThreadOrLogException(
ThrowingRunnable<Exception> task) {
runOnDbThread(task, e -> logException(LOG, WARNING, e));
}
/** /**
* Loads a list of items on the {@link DatabaseExecutor} within a single * Loads a list of items on the {@link DatabaseExecutor} within a single
* {@link Transaction} and publishes it as a {@link LiveResult} * {@link Transaction} and publishes it as a {@link LiveResult}

View File

@@ -39,17 +39,6 @@ public class LiveEvent<T> extends LiveData<LiveEvent.ConsumableEvent<T>> {
super.observeForever(observer); super.observeForever(observer);
} }
/**
* Returns the last value of the event (even if already consumed)
* or null if there hasn't been any value so far.
*/
@Nullable
public T getLastValue() {
ConsumableEvent<T> event = getValue();
if (event == null) return null;
return event.content;
}
static class ConsumableEvent<T> { static class ConsumableEvent<T> {
private final T content; private final T content;

View File

@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="@android:color/white"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</group>
</vector>

View File

@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="#FF000000"
android:pathData="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"/>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

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