Compare commits

..

30 Commits

Author SHA1 Message Date
akwizgran
8a534b4503 Bump version numbers for 1.2.16 release. 2021-02-19 18:01:56 +00:00
akwizgran
e5b2275c82 Merge branch '1947-forum-crash' into 'master'
Don't add new thread items when the existing ones haven't loaded

Closes #1947

See merge request briar/briar!1375
2021-02-19 17:27:38 +00:00
Torsten Grote
5159593825 Don't add new item when the existing ones haven't loaded 2021-02-19 14:17:21 -03:00
akwizgran
6fbc82ee27 Merge branch '1075-1146-1317-ongoing-notification' into 'master'
Use IMPORTANCE_LOW for ongoing notification, don't show a badge

Closes #1317, #1146, and #1075

See merge request briar/briar!1369
2021-02-18 17:00:47 +00:00
akwizgran
885b03cfd7 Bump version numbers for 1.2.15 release. 2021-02-18 15:27:57 +00:00
akwizgran
f81bfcafeb Update translations. 2021-02-18 15:26:10 +00:00
akwizgran
f36f1cf3d4 Merge branch '1764-fix-change-app-language-does-not-work' into 'master'
Resolve "Change app language does not work"

Closes #1764

See merge request briar/briar!1367
2021-02-17 16:59:59 +00:00
Torsten Grote
7d6a63d866 Merge branch '1934-upgrade-obfs4proxy' into 'master'
Upgrade obfs4proxy to 0.0.12-dev

Closes #1934

See merge request briar/briar!1372
2021-02-17 16:58:22 +00:00
akwizgran
15ebdf8dd5 Upgrade obfs4proxy to 0.0.12-dev. 2021-02-17 16:41:49 +00:00
akwizgran
db2c235283 Merge branch 'private-group-disabled' into 'master'
Fix disabled groups after screen rotation

See merge request briar/briar!1371
2021-02-17 14:09:57 +00:00
Sebastian Kürten
e5bd43469e Add Javados to Localizer#setLocale() 2021-02-15 14:54:20 +01:00
Torsten Grote
9366c184d8 Fix disabled groups after screen rotation
isDissolved was reverted to LiveData that only shows a dialog when the activity was first opened
2021-02-15 09:55:59 -03:00
Sebastian Kürten
73d2c964d4 Make language switching for robust 2021-02-15 12:31:51 +01:00
akwizgran
fb2b4209cf Use IMPORTANCE_LOW for ongoing notification, don't show a badge. 2021-02-10 11:46:41 +00:00
Torsten Grote
a04b512497 Merge branch 'tor-0.3.5.13' into 'master'
Upgrade Tor to 0.3.5.13

Closes #1922

See merge request briar/briar!1363
2021-02-09 12:15:45 +00:00
akwizgran
3d9515e308 Also upgrade obfs4proxy and bramble-java's Tor. 2021-02-09 12:05:54 +00:00
akwizgran
1b19b331b1 Merge branch '1904-fragment-started-too-late' into 'master'
Don't launch fragments with back button when not started

Closes #1904

See merge request briar/briar!1365
2021-02-09 11:05:08 +00:00
akwizgran
d151a2d7f7 Merge branch '1910-state-exception-when-adding-contact' into 'master'
Restore remote handshake link when AddContactViewModel gets destroyed

Closes #1910

See merge request briar/briar!1364
2021-02-09 10:49:38 +00:00
Torsten Grote
9712a4b849 Don't launch fragments with back button when not started
Sounds strange, but apparently can happen.
2021-02-08 16:38:15 -03:00
Torsten Grote
cf1ac5e3e5 Restore remote handshake link when AddContactViewModel gets destroyed 2021-02-08 16:03:10 -03:00
Torsten Grote
cb859e998d Upgrade Tor to 0.3.5.13 2021-02-08 15:44:35 -03:00
akwizgran
0b9345f867 Merge branch '1621-link-disappearing' into 'master'
Remove monospace typeface from our briar:// link

Closes #1621

See merge request briar/briar!1362
2021-02-08 18:36:16 +00:00
Torsten Grote
12988120d1 Remove monospace typeface from our briar:// link
as this makes the text to become invisible when selecting all text on API 15-17
2021-02-08 14:45:57 -03:00
akwizgran
8d6c866e62 Merge branch '1926-cap-scrypt-cost' into 'master'
Cap the scrypt cost parameter to avoid OOM

Closes #1926

See merge request briar/briar!1360
2021-02-08 17:30:57 +00:00
akwizgran
8f82cf3c73 Merge branch '1917-logcat-process' into 'master'
Fix crash reporter to capture logs from main process

Closes #1917

See merge request briar/briar!1359
2021-02-08 16:58:12 +00:00
Torsten Grote
21112ce092 Encrypt logs before handing them to crash report process 2021-02-08 13:43:37 -03:00
akwizgran
21ee3ea00d Merge branch 'add-custom-dictionary' into 'master'
Add a custom dictionary

See merge request briar/briar!1361
2021-02-08 14:01:43 +00:00
Sebastian Kürten
bb964101b3 Add a custom dictionary
This reduces the amount of words highlighted by the spell checker and
helps focussing on words that are really misspelled.
2021-02-08 14:35:14 +01:00
akwizgran
d796eff0f6 Cap the scrypt cost parameter to avoid OOM. 2021-02-08 11:32:03 +00:00
Torsten Grote
700ea2b387 Add support for logs to StreamReader and StreamWriter
Shamelessly stolen from d9b4c013
2021-02-05 17:07:48 -03:00
252 changed files with 2188 additions and 5916 deletions

14
.idea/dictionaries/briar.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<component name="ProjectDictionaryState">
<dictionary name="briar">
<words>
<w>briar</w>
<w>briarproject</w>
<w>emoji</w>
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducer</w>
<w>onboarding</w>
</words>
</dictionary>
</component>

View File

@@ -11,8 +11,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10214
versionName "1.2.14"
versionCode 10216
versionName "1.2.16"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,8 +38,8 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
tor 'org.briarproject:tor-android:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -31,6 +31,7 @@ public class AndroidUtils {
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
private static final String STORED_REPORTS = "dev-reports";
private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>();
@@ -107,6 +108,10 @@ public class AndroidUtils {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
}
public static File getLogcatFile(Context ctx) {
return new File(ctx.getFilesDir(), STORED_LOGCAT);
}
/**
* Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported.

View File

@@ -75,8 +75,8 @@ dependencyVerification {
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.client;
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.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
@@ -120,17 +119,4 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
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

@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
*/
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey);
/**
* Creates a {@link StreamDecrypter} for decrypting a log stream.
*/
StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey);
}

View File

@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream.
*/
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamEncrypter} for encrypting a log stream.
*/
StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey);
}

View File

@@ -13,4 +13,6 @@ public interface DevConfig {
String getDevOnionAddress();
File getReportDir();
File getLogcatFile();
}

View File

@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
/**
* Creates an {@link InputStream InputStream} for reading from a contact
* exchangestream.
* exchange stream.
*/
InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey);
/**
* Creates an {@link InputStream} for reading from a log stream.
*/
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
}

View File

@@ -7,17 +7,19 @@ import java.io.OutputStream;
@NotNullByDefault
public interface StreamWriterFactory {
/**
* Creates an {@link OutputStream OutputStream} for writing to a
* transport stream
* Creates a {@link StreamWriter} for writing to a transport stream.
*/
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/**
* Creates an {@link OutputStream OutputStream} for writing to a contact
* exchange stream.
* Creates a {@link StreamWriter} for writing to a contact exchange stream.
*/
StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamWriter} for writing to a log stream.
*/
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
}

View File

@@ -6,9 +6,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ValidationUtils {
@@ -66,9 +64,4 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size)
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.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule;
@@ -28,6 +29,7 @@ import dagger.Module;
@Module(includes = {
ClientModule.class,
ClockModule.class,
ConnectionModule.class,
ContactModule.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.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
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.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -41,7 +39,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
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.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -392,27 +389,4 @@ class ClientHelperImpl implements ClientHelper {
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

@@ -9,14 +9,16 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG =
Logger.getLogger(ScryptKdf.class.getName());
getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N
@@ -33,10 +35,20 @@ class ScryptKdf implements PasswordBasedKdf {
@Override
public int chooseCostParameter() {
// Scrypt uses at least 128 * N * r bytes of memory. Don't use more
// than half of the JVM's max heap size or we may run out of memory.
// https://blog.filippo.io/the-scrypt-parameters/
long maxMemory = Runtime.getRuntime().maxMemory();
long maxCost = min(MAX_COST, maxMemory / BLOCK_SIZE / 256);
if (LOG.isLoggable(INFO) && maxCost < MAX_COST) {
LOG.info("Max cost capped at " + maxCost
+ " due to max heap size " + maxMemory);
}
// Increase the cost from min to max while measuring performance
int cost = MIN_COST;
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
while (cost * 2 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) {
cost *= 2;
}
if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost);
return cost;

View File

@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
SecretKey headerKey) {
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
}
@Override
public StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey) {
return createContactExchangeStreamDecrypter(in, headerKey);
}
}

View File

@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
}
@Override
public StreamEncrypter createContactExchangeStreamDecrypter(
public StreamEncrypter createContactExchangeStreamEncrypter(
OutputStream out, SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey);
}
@Override
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey) {
return createContactExchangeStreamEncrypter(out, headerKey);
}
}

View File

@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
@Override
public InputStream createStreamReader(InputStream in, StreamContext ctx) {
return new StreamReaderImpl(
streamDecrypterFactory.createStreamDecrypter(in, ctx));
return new StreamReaderImpl(streamDecrypterFactory
.createStreamDecrypter(in, ctx));
}
@Override
public InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(
streamDecrypterFactory.createContactExchangeStreamDecrypter(in,
headerKey));
return new StreamReaderImpl(streamDecrypterFactory
.createContactExchangeStreamDecrypter(in, headerKey));
}
@Override
public InputStream createLogStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory
.createLogStreamDecrypter(in, headerKey));
}
}

View File

@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
@Override
public StreamWriter createStreamWriter(OutputStream out,
StreamContext ctx) {
return new StreamWriterImpl(
streamEncrypterFactory.createStreamEncrypter(out, ctx));
return new StreamWriterImpl(streamEncrypterFactory
.createStreamEncrypter(out, ctx));
}
@Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(
streamEncrypterFactory.createContactExchangeStreamDecrypter(out,
headerKey));
return new StreamWriterImpl(streamEncrypterFactory
.createContactExchangeStreamEncrypter(out, headerKey));
}
}
@Override
public StreamWriter createLogStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory
.createLogStreamEncrypter(out, headerKey));
}
}

View File

@@ -5,5 +5,6 @@ interface ClientVersioningConstants {
// Metadata keys
String MSG_KEY_UPDATE_VERSION = "version";
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.SHARED;
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_UPDATE_VERSION;
@@ -160,7 +161,13 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// 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
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
@@ -222,7 +229,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// 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)) {
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
@@ -514,6 +521,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
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(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>();

View File

@@ -1,18 +1,18 @@
package org.briarproject.bramble;
import org.briarproject.bramble.system.TimeTravelModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
public interface BrambleCoreIntegrationTestEagerSingletons
extends BrambleCoreEagerSingletons {
void inject(TimeTravelModule.EagerSingletons init);
void inject(DefaultTaskSchedulerModule.EagerSingletons init);
class Helper {
public static void injectEagerSingletons(
BrambleCoreIntegrationTestEagerSingletons 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.battery.DefaultBatteryManagerModule;
import org.briarproject.bramble.event.DefaultEventExecutorModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule;
import org.briarproject.bramble.system.TimeTravelModule;
import dagger.Module;
import dagger.Provides;
@@ -12,11 +12,11 @@ import dagger.Provides;
@Module(includes = {
DefaultBatteryManagerModule.class,
DefaultEventExecutorModule.class,
DefaultTaskSchedulerModule.class,
DefaultWakefulIoExecutorModule.class,
TestDatabaseConfigModule.class,
TestPluginConfigModule.class,
TestSecureRandomModule.class,
TimeTravelModule.class
TestSecureRandomModule.class
})
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.getMessage;
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_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
@@ -59,6 +60,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
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() {
context.checking(new Expectations() {{
@@ -120,8 +123,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId());
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
groupMeta);
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -457,8 +460,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -488,9 +492,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
// Get client ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -541,6 +546,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -570,8 +577,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -611,6 +619,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -640,8 +650,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

@@ -16,8 +16,8 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip'
tor 'org.briarproject:tor:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

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

View File

@@ -23,8 +23,8 @@ dependencyVerification {
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -22,8 +22,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10214
versionName "1.2.14"
versionCode 10216
versionName "1.2.16"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
@@ -136,6 +136,7 @@ dependencies {
testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.2'

View File

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

View File

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

View File

@@ -29,12 +29,12 @@ import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
@@ -42,7 +42,6 @@ import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.attachment.AttachmentReader;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -77,7 +76,6 @@ import dagger.Component;
BriarAccountModule.class,
AppModule.class,
AttachmentModule.class,
ClockModule.class,
MediaModule.class
})
public interface AndroidComponent
@@ -182,7 +180,9 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager();
AutoDeleteManager autoDeleteManager();
CachingLogHandler logHandler();
Thread.UncaughtExceptionHandler exceptionHandler();
void inject(SignInReminderReceiver briarService);

View File

@@ -33,6 +33,7 @@ import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
@@ -68,11 +69,13 @@ import static java.util.Collections.singletonList;
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_PUBLIC_KEY_HEX;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class,
LoggingModule.class,
LoginModule.class,
NavDrawerModule.class,
ViewModelModule.class,
@@ -191,6 +194,11 @@ public class AppModule {
public File getReportDir() {
return AndroidUtils.getReportDir(app.getApplicationContext());
}
@Override
public File getLogcatFile() {
return AndroidUtils.getLogcatFile(app.getApplicationContext());
}
};
return devConfig;
}
@@ -268,12 +276,12 @@ public class AppModule {
@Override
public boolean shouldEnableImageAttachments() {
return false;
return IS_DEBUG_BUILD;
}
@Override
public boolean shouldEnableProfilePictures() {
return false;
return IS_DEBUG_BUILD;
}
};
}

View File

@@ -6,9 +6,6 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.Collection;
import java.util.logging.LogRecord;
/**
* This exists so that the Application object will not necessarily be cast
* directly to the Briar application object.
@@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication {
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
Collection<LogRecord> getRecentLogRecords();
AndroidComponent getApplicationComponent();
SharedPreferences getDefaultSharedPreferences();

View File

@@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import androidx.annotation.NonNull;
@@ -42,22 +40,18 @@ public class BriarApplicationImpl extends Application
private static final Logger LOG =
getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override
protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here.
Localizer.initialize(prefs);
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
setTheme(base, prefs);
}
@@ -67,6 +61,11 @@ public class BriarApplicationImpl extends Application
if (IS_DEBUG_BUILD) enableStrictMode();
applicationComponent = createApplicationComponent();
UncaughtExceptionHandler exceptionHandler =
applicationComponent.exceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
Logger rootLogger = getLogger("");
Handler[] handlers = rootLogger.getHandlers();
// Disable the Android logger for release builds
@@ -78,12 +77,12 @@ public class BriarApplicationImpl extends Application
// Restore the default handlers after the level raising handler
for (Handler handler : handlers) rootLogger.addHandler(handler);
}
CachingLogHandler logHandler = applicationComponent.logHandler();
rootLogger.addHandler(logHandler);
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
LOG.info("Created");
applicationComponent = createApplicationComponent();
EmojiManager.install(new GoogleEmojiProvider());
}
@@ -136,11 +135,6 @@ public class BriarApplicationImpl extends Application
return applicationComponent;
}
@Override
public Collection<LogRecord> getRecentLogRecords() {
return logHandler.getRecentLogRecords();
}
@Override
public AndroidComponent getApplicationComponent() {
return applicationComponent;

View File

@@ -37,7 +37,7 @@ import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
@@ -56,6 +56,7 @@ import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
@@ -120,11 +121,17 @@ public class BriarService extends Service {
if (SDK_INT >= 26) {
NotificationManager nm = (NotificationManager)
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
// Delete the old notification channel, which had
// IMPORTANCE_NONE and showed a badge
nm.deleteNotificationChannel(ONGOING_CHANNEL_OLD_ID);
// Use IMPORTANCE_LOW so the system doesn't show its own
// notification on API 26-27
NotificationChannel ongoingChannel = new NotificationChannel(
ONGOING_CHANNEL_ID,
getString(R.string.ongoing_notification_title),
IMPORTANCE_NONE);
IMPORTANCE_LOW);
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel);
NotificationChannel failureChannel = new NotificationChannel(
FAILURE_CHANNEL_ID,
@@ -170,6 +177,7 @@ public class BriarService extends Service {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
private void showStartupFailureNotification(StartResult result) {

View File

@@ -68,7 +68,21 @@ public class Localizer {
return new Locale(tag);
}
// Returns the localized version of context
/*
* Apply localization to the specified context.
*
* It updates the configuration of the context's resources object but can
* also return a new context derived from the context parameter. Hence
* make sure to work with the return value of this method instead of
* the context you passed as a parameter.
*
* This method also has side-effects as it calls Locale#setDefault().
*
* When using this in attachBaseContext() of Application, Service or
* Activity subclasses, it is important to not only apply this method to the
* base Context parameter received in that method, but also apply it on the
* class itself which also extends Context.
*/
public Context setLocale(Context context) {
Resources res = context.getResources();
Configuration conf = res.getConfiguration();
@@ -82,7 +96,7 @@ public class Localizer {
Locale.setDefault(locale);
if (SDK_INT >= 17) {
conf.setLocale(locale);
context.createConfigurationContext(conf);
context = context.createConfigurationContext(conf);
} else
conf.locale = locale;
//noinspection deprecation

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.conversation.AliasDialogFragment;
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.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
@@ -239,6 +238,4 @@ public interface ActivityComponent {
void inject(ConfirmAvatarDialogFragment fragment);
void inject(ConversationSettingsDialog dialog);
}

View File

@@ -109,6 +109,7 @@ public abstract class BaseActivity extends AppCompatActivity
protected void attachBaseContext(Context base) {
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
public ActivityComponent getActivityComponent() {

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

View File

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

View File

@@ -124,7 +124,7 @@ public class AddContactViewModel extends DbViewModel {
return addContactResult;
}
public void updatePendingContact(String name, PendingContact p) {
void updatePendingContact(String name, PendingContact p) {
runOnDbThread(() -> {
try {
contactManager.removePendingContact(p.getId());

View File

@@ -29,11 +29,11 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -48,6 +48,7 @@ import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
public class NicknameFragment extends BaseFragment {
private static final String TAG = NicknameFragment.class.getName();
private static final String SAVED_LINK = "savedLink";
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -67,6 +68,20 @@ public class NicknameFragment extends BaseFragment {
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(AddContactViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// When the activity (and the ViewModel) get destroyed,
// the link will not be available anymore and needs to be restored.
// TODO migrate to SavedStateViewModelFactory (once we can use it)
String savedLink = savedInstanceState.getString(SAVED_LINK);
if (savedLink != null) viewModel.setRemoteHandshakeLink(savedLink);
}
}
@Nullable
@@ -74,14 +89,9 @@ public class NicknameFragment extends BaseFragment {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
View v = inflater.inflate(R.layout.fragment_nickname,
container, false);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
contactNameLayout = v.findViewById(R.id.contactNameLayout);
contactNameInput = v.findViewById(R.id.contactNameInput);
@@ -93,6 +103,12 @@ public class NicknameFragment extends BaseFragment {
return v;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVED_LINK, viewModel.getRemoteHandshakeLink());
}
@Nullable
private String getNicknameOrNull() {
Editable text = contactNameInput.getText();

View File

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

View File

@@ -50,7 +50,6 @@ import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
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.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
@@ -60,7 +59,6 @@ import org.briarproject.briar.android.view.TextAttachmentController;
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener;
import org.briarproject.briar.android.view.TextInputView;
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.attachment.AttachmentHeader;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -140,14 +138,12 @@ import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.view.AuthorView.setAvatar;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity
implements BaseFragmentListener, EventListener, ConversationListener,
TextCache, AttachmentCache, AttachmentListener, ActionMode.Callback {
implements EventListener, ConversationListener, TextCache,
AttachmentCache, AttachmentListener, ActionMode.Callback {
public static final String CONTACT_ID = "briar.CONTACT_ID";
@@ -272,11 +268,15 @@ public class ConversationActivity extends BriarActivity
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, viewModel);
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
if (format != TEXT_ONLY) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean hasSupport) {
if (hasSupport != null && hasSupport) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().removeObserver(this);
}
}
});
} else {
@@ -286,9 +286,6 @@ public class ConversationActivity extends BriarActivity
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
textInputView.setReady(false);
textInputView.setOnKeyboardShownListener(this::scrollToBottom);
viewModel.getAutoDeleteTimer().observe(this, timer ->
sendController.setAutoDeleteTimer(timer));
}
private void scrollToBottom() {
@@ -372,12 +369,6 @@ public class ConversationActivity extends BriarActivity
// enable alias action if available
observeOnce(viewModel.getContactItem(), this, contact ->
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);
}
@@ -399,10 +390,6 @@ public class ConversationActivity extends BriarActivity
AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG);
return true;
case R.id.action_conversation_settings:
if (contactId == null) return false;
onAutoDeleteTimerNoticeClicked();
return true;
case R.id.action_delete_all_messages:
askToDeleteAllMessages();
return true;
@@ -653,8 +640,8 @@ public class ConversationActivity extends BriarActivity
supportFinishAfterTransition();
}
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
if (p.getContactId().equals(contactId)) {
LOG.info("Message received, adding");
onNewConversationMessage(p.getMessageHeader());
@@ -748,13 +735,20 @@ public class ConversationActivity extends BriarActivity
}
@Override
public LiveData<SendState> onSendClick(@Nullable String text,
List<AttachmentHeader> attachmentHeaders,
long expectedAutoDeleteTimer) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> attachmentHeaders) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError();
return viewModel
.sendMessage(text, attachmentHeaders, expectedAutoDeleteTimer);
long timestamp = System.currentTimeMillis();
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) {
@@ -964,11 +958,13 @@ public class ConversationActivity extends BriarActivity
adapter.notifyItemChanged(position, item);
}
runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try {
switch (item.getRequestType()) {
case INTRODUCTION:
respondToIntroductionRequest(item.getSessionId(),
accept);
accept, timestamp);
break;
case FORUM:
respondToForumRequest(item.getSessionId(), accept);
@@ -1042,18 +1038,11 @@ public class ConversationActivity extends BriarActivity
ActivityCompat.startActivity(this, i, options.toBundle());
}
@Override
public void onAutoDeleteTimerNoticeClicked() {
ConversationSettingsDialog dialog =
ConversationSettingsDialog.newInstance(contactId);
dialog.show(getSupportFragmentManager(),
ConversationSettingsDialog.TAG);
}
@DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, accept);
boolean accept, long time) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, time,
accept);
}
@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.ItemReturningAdapter;
import java.util.Collection;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.recyclerview.selection.SelectionTracker;
@@ -22,14 +20,13 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@NotNullByDefault
class ConversationAdapter
extends BriarAdapter<ConversationItem, ConversationItemViewHolder>
implements ItemReturningAdapter<ConversationItem> {
private final ConversationListener listener;
private ConversationListener listener;
private final RecycledViewPool imageViewPool;
private final ImageItemDecoration imageItemDecoration;
@Nullable
@@ -68,20 +65,22 @@ class ConversationAdapter
@LayoutRes int type) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
type, viewGroup, false);
if (type == R.layout.list_item_conversation_msg_in) {
return new ConversationMessageViewHolder(v, listener, true,
imageViewPool, imageItemDecoration);
} else if (type == R.layout.list_item_conversation_msg_out) {
return new ConversationMessageViewHolder(v, listener, false,
imageViewPool, imageItemDecoration);
} else if (type == R.layout.list_item_conversation_notice_in) {
return new ConversationNoticeViewHolder(v, listener, true);
} else if (type == R.layout.list_item_conversation_notice_out) {
return new ConversationNoticeViewHolder(v, listener, false);
} else if (type == R.layout.list_item_conversation_request) {
return new ConversationRequestViewHolder(v, listener, true);
switch (type) {
case R.layout.list_item_conversation_msg_in:
return new ConversationMessageViewHolder(v, listener, true,
imageViewPool, imageItemDecoration);
case R.layout.list_item_conversation_msg_out:
return new ConversationMessageViewHolder(v, listener, false,
imageViewPool, imageItemDecoration);
case R.layout.list_item_conversation_notice_in:
return new ConversationNoticeViewHolder(v, listener, true);
case R.layout.list_item_conversation_notice_out:
return new ConversationNoticeViewHolder(v, listener, false);
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
@@ -108,49 +107,22 @@ class ConversationAdapter
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) {
this.tracker = tracker;
}
@Nullable
ConversationItem getLastItem() {
if (items.size() > 0) {
return items.get(items.size() - 1);
} else {
return null;
}
}
SparseArray<ConversationItem> getOutgoingMessages() {
SparseArray<ConversationItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
if (!item.isIncoming()) {

View File

@@ -9,7 +9,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes;
import androidx.lifecycle.LiveData;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -23,25 +22,20 @@ abstract class ConversationItem {
protected String text;
private final MessageId id;
private final GroupId groupId;
private final long time, autoDeleteTimer;
private final long time;
private final boolean isIncoming;
private final LiveData<String> contactName;
private boolean read, sent, seen, showTimerNotice;
private boolean read, sent, seen;
ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h,
LiveData<String> contactName) {
ConversationItem(@LayoutRes int layoutRes, ConversationMessageHeader h) {
this.layoutRes = layoutRes;
this.text = null;
this.id = h.getId();
this.groupId = h.getGroupId();
this.time = h.getTimestamp();
this.autoDeleteTimer = h.getAutoDeleteTimer();
this.read = h.isRead();
this.sent = h.isSent();
this.seen = h.isSeen();
this.isIncoming = !h.isLocal();
this.contactName = contactName;
this.showTimerNotice = false;
}
@LayoutRes
@@ -74,10 +68,6 @@ abstract class ConversationItem {
return time;
}
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
/**
* Only useful for incoming messages.
*/
@@ -121,25 +111,4 @@ abstract class ConversationItem {
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;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -14,11 +12,8 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout;
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.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread
@NotNullByDefault
@@ -29,9 +24,8 @@ abstract class ConversationItemViewHolder extends ViewHolder {
protected final ConstraintLayout layout;
@Nullable
private final OutItemViewHolder outViewHolder;
private final TextView topNotice, text;
private final TextView text;
protected final TextView time;
protected final ImageView bomb;
@Nullable
private String itemKey = null;
@@ -39,13 +33,11 @@ abstract class ConversationItemViewHolder extends ViewHolder {
boolean isIncoming) {
super(v);
this.listener = listener;
outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
root = v;
topNotice = v.findViewById(R.id.topNotice);
layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text);
time = v.findViewById(R.id.time);
bomb = v.findViewById(R.id.bomb);
}
@CallSuper
@@ -53,8 +45,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
itemKey = item.getKey();
root.setActivated(selected);
setTopNotice(item);
if (item.getText() != null) {
text.setText(trim(item.getText()));
}
@@ -62,9 +52,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
long timestamp = item.getTime();
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);
}
@@ -77,31 +64,4 @@ abstract class ConversationItemViewHolder extends ViewHolder {
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,
AttachmentItem attachmentItem);
void onAutoDeleteTimerNoticeClicked();
}

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes;
import androidx.lifecycle.LiveData;
@NotThreadSafe
@NotNullByDefault
@@ -27,15 +26,14 @@ class ConversationRequestItem extends ConversationNoticeItem {
private boolean answered;
ConversationRequestItem(@LayoutRes int layoutRes, String text,
LiveData<String> contactName, RequestType type,
ConversationRequest<?> r) {
super(layoutRes, text, contactName, r);
RequestType type, ConversationRequest r) {
super(layoutRes, text, r);
this.requestType = type;
this.sessionId = r.getSessionId();
this.answered = r.wasAnswered();
if (r instanceof InvitationRequest) {
this.requestedGroupId = ((Shareable) r.getNameable()).getId();
this.canBeOpened = ((InvitationRequest<?>) r).canBeOpened();
this.canBeOpened = ((InvitationRequest) r).canBeOpened();
} else {
this.requestedGroupId = null;
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.DbException;
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.event.Event;
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.contact.ContactItem;
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.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
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.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
@@ -62,8 +55,6 @@ import androidx.lifecycle.MutableLiveData;
import static androidx.lifecycle.Transformations.map;
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.Logger.getLogger;
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.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
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
public class ConversationViewModel extends DbViewModel
@@ -99,8 +84,6 @@ public class ConversationViewModel extends DbViewModel
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
@Nullable
private ContactId contactId = null;
@@ -109,7 +92,7 @@ public class ConversationViewModel extends DbViewModel
private final LiveData<String> contactName = map(contactItem, c ->
UiUtils.getContactDisplayName(c.getContact()));
private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<PrivateMessageFormat> privateMessageFormat =
private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveEvent<>();
@@ -117,10 +100,8 @@ public class ConversationViewModel extends DbViewModel
new MutableLiveEvent<>();
private final MutableLiveData<Boolean> showIntroductionAction =
new MutableLiveData<>();
private final MutableLiveData<Long> autoDeleteTimer =
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>(false);
new MutableLiveData<>();
private final MutableLiveEvent<PrivateMessageHeader> addedHeader =
new MutableLiveEvent<>();
@@ -137,9 +118,7 @@ public class ConversationViewModel extends DbViewModel
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) {
AttachmentCreator attachmentCreator) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db;
this.eventBus = eventBus;
@@ -150,10 +129,10 @@ public class ConversationViewModel extends DbViewModel
this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
messagingGroupId = map(contactItem, c ->
messagingManager.getContactGroup(c.getContact()).getId());
contactDeleted.setValue(false);
eventBus.addListener(this);
}
@@ -173,11 +152,6 @@ public class ConversationViewModel extends DbViewModel
runOnDbThread(() -> attachmentRetriever
.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) {
AvatarUpdatedEvent a = (AvatarUpdatedEvent) e;
if (a.getContactId().equals(contactId)) {
@@ -227,11 +201,6 @@ public class ConversationViewModel extends DbViewModel
contactItem.postValue(new ContactItem(c, authorInfo));
logDuration(LOG, "Loading contact", start);
start = now();
long timer = db.transactionWithResult(true, txn ->
autoDeleteManager.getAutoDeleteTimer(txn, contactId));
autoDeleteTimer.postValue(timer);
logDuration(LOG, "Getting auto-delete timer", start);
start = now();
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) {
@@ -266,6 +235,20 @@ public class ConversationViewModel extends DbViewModel
});
}
@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);
});
});
}
@Override
@UiThread
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
@@ -292,12 +275,10 @@ public class ConversationViewModel extends DbViewModel
@DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images and auto-deletion are supported
PrivateMessageFormat format = db.transactionWithResult(true, txn ->
messagingManager.getContactMessageFormat(txn, c));
if (LOG.isLoggable(INFO))
LOG.info("PrivateMessageFormat loaded: " + format.name());
privateMessageFormat.postValue(format);
// check if images are supported
boolean imagesSupported = db.transactionWithResult(true, txn ->
messagingManager.contactSupportsImages(txn, c));
imageSupport.postValue(imagesSupported);
// check if introductions are supported
Collection<Contact> contacts = contactManager.getContacts();
@@ -306,7 +287,7 @@ public class ConversationViewModel extends DbViewModel
// we only show one onboarding dialog at a time
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (format != TEXT_ONLY &&
if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
onOnboardingShown(SHOW_ONBOARDING_IMAGE);
showImageOnboarding.postEvent(true);
@@ -325,82 +306,39 @@ public class ConversationViewModel extends DbViewModel
}
@UiThread
LiveData<SendState> sendMessage(@Nullable String text,
List<AttachmentHeader> headers, long expectedTimer) {
MutableLiveData<SendState> liveData = new MutableLiveData<>();
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));
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) {
try {
if (format == TEXT_ONLY) {
return privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
} else if (format == TEXT_IMAGES) {
return privateMessageFactory.createPrivateMessage(groupId,
PrivateMessage pm;
if (hasImageSupport) {
pm = privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers);
} else {
long timer = autoDeleteManager
.getAutoDeleteTimer(txn, contactId, timestamp);
if (timer != expectedTimer)
throw new UnexpectedTimerException();
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers, timer);
pm = privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
}
storeMessage(pm);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
void setAutoDeleteTimerEnabled(boolean enabled) {
final long timer = enabled ? DAYS.toMillis(7) : NO_AUTO_DELETE_TIMER;
// ContactId is set before menu gets inflated and UI interaction
final ContactId c = requireNonNull(contactId);
@UiThread
private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
runOnDbThread(() -> {
try {
db.transaction(false, txn ->
autoDeleteManager.setAutoDeleteTimer(txn, c, timer));
autoDeleteTimer.postValue(timer);
long start = now();
messagingManager.addLocalMessage(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());
// TODO add text to cache when available here
addedHeader.postEvent(h);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
@@ -419,8 +357,8 @@ public class ConversationViewModel extends DbViewModel
return contactName;
}
LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
return privateMessageFormat;
LiveData<Boolean> hasImageSupport() {
return imageSupport;
}
LiveEvent<Boolean> showImageOnboarding() {
@@ -435,10 +373,6 @@ public class ConversationViewModel extends DbViewModel
return showIntroductionAction;
}
LiveData<Long> getAutoDeleteTimer() {
return autoDeleteTimer;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.TimeZone;
import java.util.logging.Formatter;
@@ -17,6 +18,16 @@ import static java.util.Locale.US;
@NotNullByDefault
public class BriefLogFormatter extends Formatter {
public static String formatLog(Formatter formatter,
Collection<LogRecord> logRecords) {
StringBuilder sb = new StringBuilder();
for (LogRecord record : logRecords) {
String formatted = formatter.format(record);
sb.append(formatted).append('\n');
}
return sb.toString();
}
private final Object lock = new Object();
private final DateFormat dateFormat; // Locking: lock
private final Date date; // Locking: lock

View File

@@ -21,6 +21,10 @@ public class CachingLogHandler extends Handler {
// Locking: lock
private final Queue<LogRecord> recent = new LinkedList<>();
// package-private constructor
CachingLogHandler() {
}
@Override
public void publish(LogRecord record) {
synchronized (lock) {

View File

@@ -0,0 +1,16 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import androidx.annotation.Nullable;
@NotNullByDefault
public interface LogDecrypter {
/**
* Returns decrypted log records from {@link AndroidUtils#getLogcatFile}
* or null if there was an error reading the logs.
*/
@Nullable
String decryptLogs(@Nullable byte[] logKey);
}

View File

@@ -0,0 +1,61 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class LogDecrypterImpl implements LogDecrypter {
private static final Logger LOG =
getLogger(LogDecrypterImpl.class.getName());
private final DevConfig devConfig;
private final StreamReaderFactory streamReaderFactory;
@Inject
LogDecrypterImpl(DevConfig devConfig,
StreamReaderFactory streamReaderFactory) {
this.devConfig = devConfig;
this.streamReaderFactory = streamReaderFactory;
}
@Nullable
@Override
public String decryptLogs(@Nullable byte[] logKey) {
if (logKey == null) return null;
SecretKey key = new SecretKey(logKey);
File logFile = devConfig.getLogcatFile();
try (InputStream in = new FileInputStream(logFile)) {
InputStream reader =
streamReaderFactory.createLogStreamReader(in, key);
Scanner s = new Scanner(reader);
StringBuilder sb = new StringBuilder();
while (s.hasNextLine()) sb.append(s.nextLine()).append("\n");
s.close();
return sb.toString();
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
} finally {
//noinspection ResultOfMethodCallIgnored
logFile.delete();
}
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import androidx.annotation.Nullable;
@NotNullByDefault
public interface LogEncrypter {
/**
* Writes encrypted log records to {@link AndroidUtils#getLogcatFile}
* and returns the encryption key if everything went fine.
*/
@Nullable
byte[] encryptLogs();
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class LogEncrypterImpl implements LogEncrypter {
private static final Logger LOG =
getLogger(LogEncrypterImpl.class.getName());
private final DevConfig devConfig;
private final CachingLogHandler logHandler;
private final CryptoComponent crypto;
private final StreamWriterFactory streamWriterFactory;
@Inject
LogEncrypterImpl(DevConfig devConfig,
CachingLogHandler logHandler,
CryptoComponent crypto,
StreamWriterFactory streamWriterFactory) {
this.devConfig = devConfig;
this.logHandler = logHandler;
this.crypto = crypto;
this.streamWriterFactory = streamWriterFactory;
}
@Nullable
@Override
public byte[] encryptLogs() {
SecretKey logKey = crypto.generateSecretKey();
File logFile = devConfig.getLogcatFile();
try (OutputStream out = new FileOutputStream(logFile)) {
StreamWriter streamWriter =
streamWriterFactory.createLogStreamWriter(out, logKey);
Writer writer =
new OutputStreamWriter(streamWriter.getOutputStream());
writeLogString(writer);
writer.close();
return logKey.getBytes();
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
private void writeLogString(Writer writer) throws IOException {
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : logHandler.getRecentLogRecords()) {
String formatted = formatter.format(record);
writer.append(formatted).append('\n');
}
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.android.logging;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class LoggingModule {
@Provides
@Singleton
CachingLogHandler provideCachingLogHandler() {
return new CachingLogHandler();
}
@Provides
@Singleton
LogEncrypter provideLogEncrypter(LogEncrypterImpl logEncrypter) {
return logEncrypter;
}
@Provides
@Singleton
LogDecrypter provideLogDecrypter(LogDecrypterImpl logDecrypter) {
return logDecrypter;
}
}

View File

@@ -68,6 +68,7 @@ import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@@ -297,6 +298,12 @@ public class NavDrawerActivity extends BriarActivity implements
finish();
} else if (fm.getBackStackEntryCount() == 0
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
// don't start fragments in the wrong part of lifecycle (#1904)
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
LOG.warning("Tried to start contacts fragment in state " +
getLifecycle().getCurrentState().name());
return;
}
/*
* This makes sure that the first fragment (ContactListFragment) the
* user sees is the same as the last fragment the user sees before

View File

@@ -79,9 +79,10 @@ public class GroupActivity extends
// start with group disabled and enable when not dissolved
setGroupEnabled(false);
viewModel.isDissolved().observeEvent(this, dissolved -> {
viewModel.isDissolved().observe(this, dissolved -> {
setGroupEnabled(!dissolved);
if (dissolved) onGroupDissolved();
// only show dialog when no prior state
if (dissolved && state == null) onGroupDissolved();
});
}
@@ -153,7 +154,7 @@ public class GroupActivity extends
@Override
public void onReplyClick(GroupMessageItem item) {
Boolean isDissolved = viewModel.isDissolved().getLastValue();
Boolean isDissolved = viewModel.isDissolved().getValue();
if (isDissolved != null && !isDissolved) super.onReplyClick(item);
}

View File

@@ -22,8 +22,6 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.sharing.SharingController;
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.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
@@ -71,8 +69,8 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
private final MutableLiveData<PrivateGroup> privateGroup =
new MutableLiveData<>();
private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
private final MutableLiveEvent<Boolean> isDissolved =
new MutableLiveEvent<>();
private final MutableLiveData<Boolean> isDissolved =
new MutableLiveData<>();
@Inject
GroupViewModel(Application application,
@@ -129,7 +127,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} else if (e instanceof GroupDissolvedEvent) {
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
if (g.getGroupId().equals(groupId)) {
isDissolved.setEvent(true);
isDissolved.setValue(true);
}
} else {
super.eventOccurred(e);
@@ -164,7 +162,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
loadList(txn -> {
// check first if group is dissolved
isDissolved
.postEvent(privateGroupManager.isDissolved(txn, groupId));
.postValue(privateGroupManager.isDissolved(txn, groupId));
// now continue to load the items
long start = now();
List<GroupMessageHeader> headers =
@@ -282,7 +280,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
return isCreator;
}
LiveEvent<Boolean> isDissolved() {
LiveData<Boolean> isDissolved() {
return isDissolved;
}

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

View File

@@ -1,29 +1,40 @@
package org.briarproject.briar.android.reporting;
import android.content.Context;
import android.app.Application;
import android.os.Process;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.logging.LogEncrypter;
import java.lang.Thread.UncaughtExceptionHandler;
import javax.inject.Inject;
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
@NotNullByDefault
public class BriarExceptionHandler implements UncaughtExceptionHandler {
class BriarExceptionHandler implements UncaughtExceptionHandler {
private final Context ctx;
private final Application app;
private final LogEncrypter logEncrypter;
private final long appStartTime;
public BriarExceptionHandler(Context ctx) {
this.ctx = ctx;
this.appStartTime = System.currentTimeMillis();
@Inject
BriarExceptionHandler(Application app, LogEncrypter logEncrypter) {
this.app = app;
this.logEncrypter = logEncrypter;
appStartTime = System.currentTimeMillis();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// encrypt logs to disk before handing over to new process
// the intent has limited space, so we can't reliably store them there.
byte[] logKey = logEncrypter.encryptLogs();
// activity runs in its own process, so we can kill the old one
startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime);
startDevReportActivity(app.getApplicationContext(),
CrashReportActivity.class, e, appStartTime, logKey);
Process.killProcess(Process.myPid());
System.exit(10);
}

View File

@@ -25,8 +25,6 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
@@ -41,8 +39,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.Immutable;
@@ -74,8 +70,8 @@ class BriarReportCollector {
this.ctx = ctx;
}
public ReportData collectReportData(@Nullable Throwable t,
long appStartTime) {
ReportData collectReportData(@Nullable Throwable t, long appStartTime,
String logs) {
ReportData reportData = new ReportData()
.add(getBasicInfo(t))
.add(getDeviceInfo());
@@ -86,7 +82,7 @@ class BriarReportCollector {
.add(getStorage())
.add(getConnectivity())
.add(getBuildConfig())
.add(getLogcat())
.add(getLogcat(logs))
.add(getDeviceFeatures());
}
@@ -313,15 +309,8 @@ class BriarReportCollector {
buildConfig);
}
private ReportItem getLogcat() {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
StringBuilder sb = new StringBuilder();
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : app.getRecentLogRecords()) {
sb.append(formatter.format(record)).append('\n');
}
return new ReportItem("Logcat", R.string.dev_report_logcat,
sb.toString());
private ReportItem getLogcat(String logs) {
return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
}
private ReportItem getDeviceFeatures() {

View File

@@ -35,6 +35,7 @@ public class CrashReportActivity extends BaseActivity
public static final String EXTRA_THROWABLE = "throwable";
public static final String EXTRA_APP_START_TIME = "appStartTime";
public static final String EXTRA_APP_LOGCAT = "logcat";
@Inject
ViewModelProvider.Factory viewModelFactory;
@@ -56,7 +57,8 @@ public class CrashReportActivity extends BaseActivity
Intent intent = getIntent();
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
viewModel.init(t, appStartTime);
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
viewModel.init(t, appStartTime, logKey);
viewModel.getShowReport().observeEvent(this, show -> {
if (show) displayFragment(true);
});

View File

@@ -15,4 +15,8 @@ public abstract class DevReportModule {
@ViewModelKey(ReportViewModel.class)
abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel);
@Binds
abstract Thread.UncaughtExceptionHandler bindUncaughtExceptionHandler(
BriarExceptionHandler handler);
}

View File

@@ -11,6 +11,9 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.logging.LogDecrypter;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
@@ -19,6 +22,7 @@ import org.json.JSONException;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.UUID;
import java.util.logging.Formatter;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -36,13 +40,16 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
@NotNullByDefault
public class ReportViewModel extends AndroidViewModel {
class ReportViewModel extends AndroidViewModel {
private static final Logger LOG =
getLogger(ReportViewModel.class.getName());
private final CachingLogHandler logHandler;
private final LogDecrypter logDecrypter;
private final BriarReportCollector collector;
private final DevReporter reporter;
private final PluginManager pluginManager;
@@ -58,18 +65,39 @@ public class ReportViewModel extends AndroidViewModel {
private boolean isFeedback;
@Inject
public ReportViewModel(@NonNull Application application,
DevReporter reporter, PluginManager pluginManager) {
ReportViewModel(@NonNull Application application,
CachingLogHandler logHandler,
LogDecrypter logDecrypter,
DevReporter reporter,
PluginManager pluginManager) {
super(application);
this.collector = new BriarReportCollector(application);
collector = new BriarReportCollector(application);
this.logHandler = logHandler;
this.logDecrypter = logDecrypter;
this.reporter = reporter;
this.pluginManager = pluginManager;
}
void init(@Nullable Throwable t, long appStartTime) {
void init(@Nullable Throwable t, long appStartTime,
@Nullable byte[] logKey) {
isFeedback = t == null;
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
ReportData data = collector.collectReportData(t, appStartTime);
String decryptedLogs;
if (isFeedback) {
Formatter formatter = new BriefLogFormatter();
decryptedLogs =
formatLog(formatter, logHandler.getRecentLogRecords());
} else {
decryptedLogs = logDecrypter.decryptLogs(logKey);
if (decryptedLogs == null) {
// error decrypting logs, get logs from this process
Formatter formatter = new BriefLogFormatter();
decryptedLogs = formatLog(formatter,
logHandler.getRecentLogRecords());
}
}
ReportData data =
collector.collectReportData(t, appStartTime, decryptedLogs);
reportData.postValue(data);
}).start();
}
@@ -110,8 +138,8 @@ public class ReportViewModel extends AndroidViewModel {
}
/**
* The content of the report
* that will be loaded after {@link #init(Throwable, long)} was called.
* The content of the report that will be loaded after
* {@link #init(Throwable, long, byte[])} was called.
*/
LiveData<ReportData> getReportData() {
return reportData;

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.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextSendController.SendState;
import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List;
@@ -23,10 +22,6 @@ import java.util.List;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static org.briarproject.briar.android.view.TextSendController.SendState.SENT;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -84,14 +79,13 @@ public abstract class BaseMessageFragment extends BaseFragment
}
@Override
public LiveData<SendState> onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer) {
public void onSendClick(@Nullable String text,
List<AttachmentHeader> headers) {
// disable button to prevent accidental double actions
sendController.setReady(false);
message.hideSoftKeyboard();
listener.onButtonClick(text);
return new MutableLiveData<>(SENT);
}
@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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.identity.AuthorManager;
import java.util.Collection;
@@ -24,7 +26,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -33,17 +34,22 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
implements ShareBlogController {
private final static Logger LOG =
getLogger(ShareBlogControllerImpl.class.getName());
Logger.getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
BlogSharingManager blogSharingManager) {
ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager;
this.clock = clock;
}
@Override
@@ -58,7 +64,10 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
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) {
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager;
@@ -24,7 +26,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -33,17 +34,22 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
implements ShareForumController {
private final static Logger LOG =
getLogger(ShareForumControllerImpl.class.getName());
Logger.getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
AuthorManager authorManager,
ForumSharingManager forumSharingManager) {
ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager, authorManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager;
this.clock = clock;
}
@Override
@@ -58,7 +64,10 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
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) {
logException(LOG, WARNING, e);
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.splash;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -7,6 +8,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import androidx.appcompat.app.AppCompatActivity;
@@ -27,6 +29,13 @@ public class ExpiredActivity extends AppCompatActivity
findViewById(R.id.download_briar_button).setOnClickListener(this);
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
@Override
public void onClick(View v) {
Uri uri = Uri.parse("https://briarproject.org/download.html");

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

View File

@@ -197,6 +197,10 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
*/
@UiThread
protected void addItem(I item, boolean scrollToItem) {
// If items haven't loaded, we need to wait until they have.
// Since this was a R/W DB transaction, the load will pick up this item.
if (items.getValue() == null) return;
messageTree.add(item);
if (scrollToItem) this.scrollToItem.set(item.getId());
items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));

View File

@@ -97,6 +97,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
@@ -357,16 +358,17 @@ public class UiUtils {
}
public static void triggerFeedback(Context ctx) {
startDevReportActivity(ctx, FeedbackActivity.class, null, null);
startDevReportActivity(ctx, FeedbackActivity.class, null, null, null);
}
public static void startDevReportActivity(Context ctx,
Class<? extends FragmentActivity> activity, @Nullable Throwable t,
@Nullable Long appStartTime) {
@Nullable Long appStartTime, @Nullable byte[] logKey) {
final Intent dialogIntent = new Intent(ctx, activity);
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra(EXTRA_THROWABLE, t);
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
ctx.startActivity(dialogIntent);
}

View File

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

View File

@@ -26,6 +26,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.customview.view.AbsSavedState;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
@@ -38,7 +39,6 @@ import static androidx.core.content.ContextCompat.getColor;
import static androidx.customview.view.AbsSavedState.EMPTY_STATE;
import static androidx.lifecycle.Lifecycle.State.DESTROYED;
import static org.briarproject.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;
@UiThread
@@ -52,6 +52,7 @@ public class TextAttachmentController extends TextSendController
private final AttachmentManager attachmentManager;
private final List<Uri> imageUris = new ArrayList<>();
private final CharSequence textHint;
private boolean loadingUris = false;
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
@@ -65,44 +66,23 @@ public class TextAttachmentController extends TextSendController
sendButton = (CompositeSendButton) compositeSendButton;
sendButton.setOnImageClickListener(view -> onImageButtonClicked());
textHint = textInput.getHint();
}
@Override
protected void updateViewState() {
super.updateViewState();
textInput.setEnabled(ready && !loadingUris);
boolean sendEnabled = ready && !loadingUris &&
(!textIsEmpty || canSendEmptyText());
if (loadingUris) {
sendButton.showProgress(true);
} else if (imageUris.isEmpty()) {
sendButton.showProgress(false);
sendButton.showImageButton(textIsEmpty, isSendButtonEnabled());
sendButton.showImageButton(textIsEmpty, sendEnabled);
} else {
sendButton.showProgress(false);
sendButton.showImageButton(false, isSendButtonEnabled());
}
}
@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);
sendButton.showImageButton(false, sendEnabled);
}
}
@@ -111,17 +91,11 @@ public class TextAttachmentController extends TextSendController
if (canSend()) {
if (loadingUris) throw new AssertionError();
listener.onSendClick(textInput.getText(),
attachmentManager.getAttachmentHeadersForSending(),
expectedTimer).observe(listener, this::onSendStateChanged);
attachmentManager.getAttachmentHeadersForSending());
reset();
}
}
@Override
protected void onSendStateChanged(SendState sendState) {
super.onSendStateChanged(sendState);
if (sendState == SENT) reset();
}
@Override
protected boolean canSendEmptyText() {
return !imageUris.isEmpty();
@@ -180,7 +154,6 @@ public class TextAttachmentController extends TextSendController
private void onNewUris(boolean restart, List<Uri> newUris) {
if (newUris.isEmpty()) return;
if (loadingUris) throw new AssertionError();
if (textIsEmpty) onStartingMessage();
loadingUris = true;
if (newUris.size() > MAX_ATTACHMENTS_PER_MESSAGE) {
newUris = newUris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE);
@@ -188,6 +161,7 @@ public class TextAttachmentController extends TextSendController
}
imageUris.addAll(newUris);
updateViewState();
textInput.setHint(R.string.image_caption_hint);
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
imagePreview.showPreview(items);
// store attachments and show preview when successful
@@ -233,6 +207,8 @@ public class TextAttachmentController extends TextSendController
}
private void reset() {
// restore hint
textInput.setHint(textHint);
// hide image layout
imagePreview.setVisibility(GONE);
// reset image URIs
@@ -327,7 +303,7 @@ public class TextAttachmentController extends TextSendController
}
@UiThread
public interface AttachmentListener extends SendListener {
public interface AttachmentListener extends SendListener, LifecycleOwner {
void onAttachImage(Intent intent);

View File

@@ -1,9 +1,7 @@
package org.briarproject.briar.android.view;
import android.content.Context;
import android.os.Parcelable;
import android.view.View;
import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
@@ -14,20 +12,11 @@ import org.briarproject.briar.api.attachment.AttachmentHeader;
import java.util.List;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
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 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
@NotNullByDefault
@@ -37,12 +26,8 @@ public class TextSendController implements TextInputListener {
protected final View compositeSendButton;
protected final SendListener listener;
protected boolean textIsEmpty = true;
private boolean ready = true;
private long currentTimer = NO_AUTO_DELETE_TIMER;
protected long expectedTimer = NO_AUTO_DELETE_TIMER;
protected boolean ready = true, textIsEmpty = true;
private final CharSequence defaultHint;
private final boolean allowEmptyText;
public TextSendController(TextInputView v, SendListener listener,
@@ -51,91 +36,31 @@ public class TextSendController implements TextInputListener {
this.compositeSendButton.setOnClickListener(view -> onSendEvent());
this.listener = listener;
this.textInput = v.getEmojiTextInputView();
this.defaultHint = textInput.getHint();
this.allowEmptyText = allowEmptyText;
}
@Override
public void onTextIsEmptyChanged(boolean isEmpty) {
textIsEmpty = isEmpty;
if (!isEmpty) onStartingMessage();
updateViewState();
}
@Override
public void onSendEvent() {
if (canSend()) {
listener.onSendClick(textInput.getText(), emptyList(),
expectedTimer).observe(listener, this::onSendStateChanged);
listener.onSendClick(textInput.getText(), emptyList());
}
}
@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) {
this.ready = ready;
updateViewState();
}
/**
* Sets the current auto delete timer and updates the UI accordingly.
*/
public void setAutoDeleteTimer(long timer) {
currentTimer = timer;
updateViewState();
}
@CallSuper
protected void updateViewState() {
textInput.setEnabled(isTextInputEnabled());
textInput.setHint(getCurrentTextHint());
compositeSendButton.setEnabled(isSendButtonEnabled());
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);
}
textInput.setEnabled(ready);
compositeSendButton
.setEnabled(ready && (!textIsEmpty || canSendEmptyText()));
}
protected final boolean canSend() {
@@ -151,23 +76,6 @@ public class TextSendController implements TextInputListener {
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
public Parcelable onSaveInstanceState(@Nullable Parcelable superState) {
return superState;
@@ -178,11 +86,9 @@ public class TextSendController implements TextInputListener {
return state;
}
public enum SendState {SENT, ERROR, UNEXPECTED_TIMER}
public interface SendListener extends LifecycleOwner {
LiveData<SendState> onSendClick(@Nullable String text,
List<AttachmentHeader> headers, long expectedAutoDeleteTimer);
@UiThread
public interface SendListener {
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
}
}

View File

@@ -39,7 +39,8 @@ public interface AndroidNotificationManager {
String BLOG_CHANNEL_ID = "blogs";
// Channels are sorted by channel ID in the Settings app, so use IDs
// that will sort below the main channels such as contacts
String ONGOING_CHANNEL_ID = "zForegroundService";
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
String ONGOING_CHANNEL_ID = "zForegroundService2";
String FAILURE_CHANNEL_ID = "zStartupFailure";
String REMINDER_CHANNEL_ID = "zSignInReminder";

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M11.25,6A3.25,3.25 0 0,1 14.5,2.75A3.25,3.25 0 0,1 17.75,6C17.75,6.42 18.08,6.75 18.5,6.75C18.92,6.75 19.25,6.42 19.25,6V5.25H20.75V6A2.25,2.25 0 0,1 18.5,8.25A2.25,2.25 0 0,1 16.25,6A1.75,1.75 0 0,0 14.5,4.25A1.75,1.75 0 0,0 12.75,6H14V7.29C16.89,8.15 19,10.83 19,14A7,7 0 0,1 12,21A7,7 0 0,1 5,14C5,10.83 7.11,8.15 10,7.29V6H11.25M22,6H24V7H22V6M19,4V2H20V4H19M20.91,4.38L22.33,2.96L23.04,3.67L21.62,5.09L20.91,4.38Z" />
</vector>

View File

@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/BriarToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/abc_ic_ab_back_material"
app:title="@string/disappearing_messages_title" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingHorizontal="@dimen/margin_large"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageViewBomb"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_bomb"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switchDisappearingMessages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:enabled="false"
android:text="@string/disappearing_messages_summary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageViewBomb" />
<TextView
android:id="@+id/buttonLearnMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:clickable="true"
android:focusable="true"
android:text="@string/learn_more"
android:textColor="@color/briar_text_link"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/switchDisappearingMessages" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="?dialogPreferredPadding">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disappearing_messages_explanation_long"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>

View File

@@ -112,7 +112,6 @@
android:textColor="@color/briar_primary"
android:textIsSelectable="true"
android:textSize="18sp"
android:typeface="monospace"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"

View File

@@ -139,10 +139,10 @@
<Space
android:id="@+id/space"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_default="wrap"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" />
@@ -173,4 +173,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</ScrollView>

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is only used indirectly by cloning and setting its ConstraintSet -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -70,16 +68,6 @@
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:srcCompat="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout is only used indirectly by cloning and setting its ConstraintSet -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -60,8 +58,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text"
tools:ignore="UseCompoundDrawables">
app:layout_constraintTop_toBottomOf="@+id/text">
<TextView
android:id="@+id/time"
@@ -70,16 +67,6 @@
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
app:srcCompat="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,19 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_background_selectable"
android:orientation="vertical">
android:background="@drawable/list_item_background_selectable">
<!--
We need to wrap the actual layout, because we want to
* clone the ConstraintLayout's constraints in the ViewHolder
* have a selectable frame around the message bubble
* insert a top notice with its own independent width
We need to wrap the actual layout, because
* we want to clone the ConstraintLayout's constraints in the ViewHolder
* we want to have a selectable frame around the message bubble
-->
<include layout="@layout/list_item_conversation_top_notice_in" />
<include layout="@layout/list_item_conversation_msg_in_content" />
</LinearLayout>
</FrameLayout>

View File

@@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This layout gets wrapped in *_msg_in.xml -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@@ -70,17 +68,6 @@
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_background_selectable"
android:orientation="vertical"
android:paddingTop="@dimen/message_bubble_margin">
<include layout="@layout/list_item_conversation_top_notice_out" />
android:background="@drawable/list_item_background_selectable">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
@@ -77,26 +73,15 @@
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:textColor="@color/private_message_date_inverse"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
app:srcCompat="@drawable/ic_bomb"
app:tint="@color/private_message_date_inverse"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:tint="@color/private_message_date_inverse"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered" />
@@ -104,4 +89,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</FrameLayout>

View File

@@ -5,9 +5,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_background_selectable"
android:orientation="vertical">
<include layout="@layout/list_item_conversation_top_notice_in" />
android:orientation="vertical"
android:paddingTop="@dimen/message_bubble_margin">
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/msgText"
@@ -16,13 +15,11 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/message_bubble_margin_tail"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginTop="@dimen/message_bubble_margin"
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
android:background="@drawable/msg_in_top"
android:elevation="@dimen/message_bubble_elevation"
tools:text="Short message"
tools:visibility="visible" />
tools:text="Short message" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
@@ -51,28 +48,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
app:layout_constraintEnd_toStartOf="@+id/bomb"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="@+id/text"
app:layout_constraintTop_toBottomOf="@+id/text"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
app:layout_constraintEnd_toEndOf="@+id/text"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toBottomOf="@+id/text"
app:srcCompat="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -8,8 +8,6 @@
android:orientation="vertical"
android:paddingTop="@dimen/message_bubble_margin">
<include layout="@layout/list_item_conversation_top_notice_out" />
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/msgText"
style="@style/TextMessage"
@@ -17,14 +15,12 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/message_bubble_margin_non_tail"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
android:layout_marginTop="@dimen/message_bubble_margin"
android:layout_marginEnd="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_tail"
android:background="@drawable/msg_out_top"
android:elevation="@dimen/message_bubble_elevation"
android:textColor="@color/briar_text_primary_inverse"
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."
tools:visibility="visible" />
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here." />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
@@ -59,29 +55,15 @@
app:layout_constraintTop_toBottomOf="@+id/text"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toTopOf="@+id/time"
app:srcCompat="@drawable/ic_bomb"
app:tint="@color/private_message_date_inverse"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginLeft="@dimen/margin_medium"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/bomb"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toTopOf="@+id/time"
app:tint="@color/private_message_date_inverse"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered" />

View File

@@ -5,9 +5,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_background_selectable"
android:orientation="vertical">
<include layout="@layout/list_item_conversation_top_notice_in" />
android:orientation="vertical"
android:paddingTop="@dimen/message_bubble_margin">
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/msgText"
@@ -16,7 +15,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/message_bubble_margin_tail"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginTop="@dimen/message_bubble_margin"
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
android:background="@drawable/msg_in_top"
@@ -70,27 +68,10 @@
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@+id/bomb"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
tools:text="Dec 24, 13:37" />
<ImageView
android:id="@+id/bomb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toBottomOf="@+id/acceptButton"
app:srcCompat="@drawable/ic_bomb"
app:tint="?android:attr/textColorSecondary"
tools:ignore="ContentDescription"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>

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