Compare commits

..

42 Commits

Author SHA1 Message Date
akwizgran
56c1fef4db Count 7 bits at a time. 2018-12-13 10:58:19 +00:00
akwizgran
c2f96580b8 Add utility methods for variable-length integers. 2018-12-05 11:31:34 +00:00
akwizgran
a5c9e7c74d Merge branch '1242-display-image-attachments-fullscreen' into 'master'
Add ImageActivity to show image attachment in full-screen

See merge request briar/briar!999
2018-11-30 18:04:55 +00:00
Torsten Grote
8a4a343147 [android] Move image to the top if it is overlapping the toolbar 2018-11-30 15:53:38 -02:00
Torsten Grote
7b22d3b84d [android] Address review issues for image fullscreen view 2018-11-28 17:26:01 -02:00
Torsten Grote
c8fa23273f [android] support pull down to dismiss pattern for ImageActivity 2018-11-28 17:26:01 -02:00
Torsten Grote
fbe5df8938 [android] Add ImageActivity to show images in full-screen 2018-11-28 17:26:01 -02:00
akwizgran
008cf95741 Merge branch '1467-conversation-scrolling' into 'master'
Only scroll conversation list to bottom, when already at bottom

Closes #1467

See merge request briar/briar!1000
2018-11-27 09:32:05 +00:00
Torsten Grote
3eb066a836 [android] Use new IoUtils to close InputStreams 2018-11-26 16:28:06 -02:00
Torsten Grote
674b29af25 [android] static constant all caps 2018-11-26 16:23:51 -02:00
Torsten Grote
b8ca5ab557 [android] Only scroll conversation list to bottom, when already at bottom
Closes #1467
2018-11-26 16:23:17 -02:00
Torsten Grote
6e17709f46 Merge branch 'try-to-close' into 'master'
Move tryToClose() methods into utility classes

See merge request briar/briar!1002
2018-11-26 18:22:24 +00:00
akwizgran
726d90145c Merge branch '1242-display-image-attachments' into 'master'
[android] display image attachments for conversation messages

See merge request briar/briar!997
2018-11-26 17:19:37 +00:00
Torsten Grote
165211eb9b Merge branch '1259-headless-mac-os' into 'master'
Enable headless app to start on MacOS

See merge request briar/briar!1003
2018-11-26 12:01:27 +00:00
akwizgran
868c61e5d6 Move tryToClose() methods into utility classes. 2018-11-23 15:02:27 +00:00
Torsten Grote
798bb6d4f7 [android] scale thumbnails to minimum size, don't upscale to maximum size 2018-11-23 11:25:18 -02:00
akwizgran
bc352a2dc6 Enable Tor on Mac OS once binaries are available. 2018-11-23 13:07:12 +00:00
akwizgran
ce7d6d3db5 Code cleanup. 2018-11-23 12:56:34 +00:00
akwizgran
61276c81d2 Make it possible to start the headless app on MacOS.
The app is still non-functional because we don't have a Tor plugin.
2018-11-23 12:52:40 +00:00
Torsten Grote
c09abdb088 Merge branch 'location-permission-sdk-23' into 'master'
Change location permission to uses-permission-sdk-23

See merge request briar/briar!1001
2018-11-22 12:03:07 +00:00
akwizgran
45a11badd5 Change location permission to uses-permission-sdk-23. 2018-11-20 16:16:47 +00:00
Torsten Grote
152ac3df43 [android] improve bitmap transformation hashKey and DiskCacheKey 2018-11-20 11:49:21 -02:00
Torsten Grote
dd5ad86db8 [android] Use DataFetcherFactory to create data fetchers and allow cancelling loads 2018-11-20 11:49:21 -02:00
Torsten Grote
10e9fb308d [android] Display Image Attachements: Address first round of review comments 2018-11-19 20:35:07 -02:00
Torsten Grote
de8e95692a [android] support RTL languages when rounding thumbnail corners 2018-11-19 20:35:07 -02:00
Torsten Grote
d6b52cf4ec [android] Use our own BitmapTransformation for rounded image corners 2018-11-19 20:35:07 -02:00
Torsten Grote
8a839fb5e4 [android] display image attachments for conversation messages 2018-11-19 20:35:07 -02:00
akwizgran
fbf8642edb Merge branch '1464-message-status-mixed' into 'master'
[core] fix wrong order of message status flags in conversation headers

Closes #1464

See merge request briar/briar!998
2018-11-16 13:44:39 +00:00
Torsten Grote
ade6a14342 Merge branch 'validation-refactoring' into 'master'
Reorganise validation code

See merge request briar/briar!991
2018-11-15 17:18:15 +00:00
Torsten Grote
d500ff81c3 Merge branch 'require-non-null' into 'master'
Add static requireNonNull() method

See merge request briar/briar!996
2018-11-15 16:50:16 +00:00
Torsten Grote
3053e3cfa7 [core] fix wrong order of message status flags in conversation headers 2018-11-15 14:39:05 -02:00
akwizgran
6964a67ca3 Add static requireNonNull() method. 2018-11-15 11:13:15 +00:00
Torsten Grote
f4b06e1fb3 Merge branch 'load-latest-message-eagerly' into 'master'
Load latest message eagerly

See merge request briar/briar!995
2018-11-14 16:01:59 +00:00
akwizgran
4db075d643 Only consider the latest item for preloading. 2018-11-14 15:13:25 +00:00
akwizgran
78a8ae6b8e Sort headers and eagerly load text of latest message. 2018-11-14 15:01:54 +00:00
Torsten Grote
7866037d02 Merge branch '1460-introduction-request-text' into 'master'
Show correct text when an existing contact is introduced

Closes #1460

See merge request briar/briar!994
2018-11-14 11:23:26 +00:00
akwizgran
35716051fb Show correct text when an existing contact is introduced. 2018-11-14 11:05:46 +00:00
Torsten Grote
6cafea836f Merge branch 'eager-singletons' into 'master'
Singletons that call registration methods must be eager

See merge request briar/briar!993
2018-11-13 18:03:28 +00:00
akwizgran
bd0fd229c6 Merge branch '1242-attachment-input-stream' into 'master'
Attachments will use InputStream rather than ByteBuffer

See merge request briar/briar!992
2018-11-13 17:41:39 +00:00
akwizgran
ea05a5c703 Singletons that call registration methods must be eager. 2018-11-13 17:40:06 +00:00
akwizgran
4103eaf639 Reorganise validation code (no functional changes). 2018-11-13 17:16:47 +00:00
Torsten Grote
753a25bc2a [core] Attachments will use InputStream rather than ByteBuffer 2018-11-13 15:12:34 -02:00
153 changed files with 3071 additions and 919 deletions

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble;
import org.briarproject.bramble.battery.AndroidBatteryModule;
import org.briarproject.bramble.network.AndroidNetworkModule;
public interface BrambleAndroidEagerSingletons {
void inject(AndroidBatteryModule.EagerSingletons init);
void inject(AndroidNetworkModule.EagerSingletons init);
}

View File

@@ -15,4 +15,8 @@ import dagger.Module;
}) })
public class BrambleAndroidModule { public class BrambleAndroidModule {
public static void initEagerSingletons(BrambleAndroidEagerSingletons c) {
c.inject(new AndroidBatteryModule.EagerSingletons());
c.inject(new AndroidNetworkModule.EagerSingletons());
}
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.battery;
import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -11,6 +12,11 @@ import dagger.Provides;
@Module @Module
public class AndroidBatteryModule { public class AndroidBatteryModule {
public static class EagerSingletons {
@Inject
BatteryManager batteryManager;
}
@Provides @Provides
@Singleton @Singleton
BatteryManager provideBatteryManager(LifecycleManager lifecycleManager, BatteryManager provideBatteryManager(LifecycleManager lifecycleManager,

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.network;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -11,6 +12,11 @@ import dagger.Provides;
@Module @Module
public class AndroidNetworkModule { public class AndroidNetworkModule {
public static class EagerSingletons {
@Inject
NetworkManager networkManager;
}
@Provides @Provides
@Singleton @Singleton
NetworkManager provideNetworkManager(LifecycleManager lifecycleManager, NetworkManager provideNetworkManager(LifecycleManager lifecycleManager,

View File

@@ -18,8 +18,8 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
@@ -51,7 +51,6 @@ import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -161,11 +160,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override @Override
void tryToClose(@Nullable BluetoothServerSocket ss) { void tryToClose(@Nullable BluetoothServerSocket ss) {
try { IoUtils.tryToClose(ss, LOG, WARNING);
if (ss != null) ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
} }
@Override @Override
@@ -195,7 +190,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
s.connect(); s.connect();
return wrapSocket(s); return wrapSocket(s);
} catch (IOException e) { } catch (IOException e) {
tryToClose(s); IoUtils.tryToClose(s, LOG, WARNING);
throw e; throw e;
} }
} }
@@ -268,14 +263,6 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return addresses; return addresses;
} }
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override @Override

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
@@ -26,7 +27,7 @@ import static android.provider.Settings.Secure.ANDROID_ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class AndroidSecureRandomProvider extends LinuxSecureRandomProvider { class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
private static final int SEED_LENGTH = 32; private static final int SEED_LENGTH = 32;
@@ -37,6 +38,7 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
appContext = app.getApplicationContext(); appContext = app.getApplicationContext();
} }
@SuppressLint("HardwareIds")
@Override @Override
protected void writeToEntropyPool(DataOutputStream out) throws IOException { protected void writeToEntropyPool(DataOutputStream out) throws IOException {
super.writeToEntropyPool(out); super.writeToEntropyPool(out);
@@ -49,12 +51,14 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
String id = Settings.Secure.getString(contentResolver, ANDROID_ID); String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id); if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
WifiManager wm = WifiManager wm = (WifiManager) appContext.getApplicationContext()
(WifiManager) appContext.getSystemService(WIFI_SERVICE); .getSystemService(WIFI_SERVICE);
List<WifiConfiguration> configs = wm.getConfiguredNetworks(); if (wm != null) {
if (configs != null) { List<WifiConfiguration> configs = wm.getConfiguredNetworks();
for (WifiConfiguration config : configs) if (configs != null) {
parcel.writeParcelable(config, 0); for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
} }
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) { if (bt != null) {
@@ -77,13 +81,13 @@ class AndroidSecureRandomProvider extends LinuxSecureRandomProvider {
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html // Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private void applyOpenSslFix() { private void applyOpenSslFix() {
byte[] seed = new LinuxSecureRandomSpi().engineGenerateSeed( byte[] seed = new UnixSecureRandomSpi().engineGenerateSeed(
SEED_LENGTH); SEED_LENGTH);
try { try {
// Seed the OpenSSL PRNG // Seed the OpenSSL PRNG
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
.getMethod("RAND_seed", byte[].class) .getMethod("RAND_seed", byte[].class)
.invoke(null, seed); .invoke(null, (Object) seed);
// Mix the output of the Linux PRNG into the OpenSSL PRNG // Mix the output of the Linux PRNG into the OpenSSL PRNG
int bytesRead = (Integer) Class.forName( int bytesRead = (Integer) Class.forName(
"org.apache.harmony.xnet.provider.jsse.NativeCrypto") "org.apache.harmony.xnet.provider.jsse.NativeCrypto")

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.client; package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser; import org.briarproject.bramble.api.data.MetadataParser;
@@ -12,7 +11,7 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -62,5 +61,4 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
} }
} }

View File

@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext; import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator; import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@@ -19,6 +19,7 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.KeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
@@ -28,8 +29,6 @@ import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.api.sync.ValidationManager.State;
/** /**
* Encapsulates the database implementation and exposes high-level operations * Encapsulates the database implementation and exposes high-level operations
* to other components. * to other components.
@@ -374,12 +373,12 @@ public interface DatabaseComponent {
/** /**
* Returns the IDs and states of all dependencies of the given message. * Returns the IDs and states of all dependencies of the given message.
* For missing dependencies and dependencies in other groups, the state * For missing dependencies and dependencies in other groups, the state
* {@link State UNKNOWN} is returned. * {@link MessageState UNKNOWN} is returned.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<MessageId, State> getMessageDependencies(Transaction txn, MessageId m) Map<MessageId, MessageState> getMessageDependencies(Transaction txn,
throws DbException; MessageId m) throws DbException;
/** /**
* Returns the IDs and states of all dependents of the given message. * Returns the IDs and states of all dependents of the given message.
@@ -388,15 +387,16 @@ public interface DatabaseComponent {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<MessageId, State> getMessageDependents(Transaction txn, MessageId m) Map<MessageId, MessageState> getMessageDependents(Transaction txn,
throws DbException; MessageId m) throws DbException;
/** /**
* Gets the validation and delivery state of the given message. * Gets the validation and delivery state of the given message.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
State getMessageState(Transaction txn, MessageId m) throws DbException; MessageState getMessageState(Transaction txn, MessageId m)
throws DbException;
/** /**
* Returns the status of the given delivered message with respect to the * Returns the status of the given delivered message with respect to the
@@ -543,7 +543,7 @@ public interface DatabaseComponent {
/** /**
* Sets the validation and delivery state of the given message. * Sets the validation and delivery state of the given message.
*/ */
void setMessageState(Transaction txn, MessageId m, State state) void setMessageState(Transaction txn, MessageId m, MessageState state)
throws DbException; throws DbException;
/** /**

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.nullsafety;
import javax.annotation.Nullable;
@NotNullByDefault
public class NullSafety {
/**
* Stand-in for `Objects.requireNonNull()`.
*/
public static <T> T requireNonNull(@Nullable T t) {
if (t == null) throw new NullPointerException();
return t;
}
}

View File

@@ -1,87 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Responsible for managing message validators and passing them messages to
* validate.
*/
@NotNullByDefault
public interface ValidationManager {
enum State {
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
private final int value;
State(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static State fromValue(int value) {
for (State s : values()) if (s.value == value) return s;
throw new IllegalArgumentException();
}
}
/**
* Registers the message validator for the given client. This method
* should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerMessageValidator(ClientId c, int majorVersion,
MessageValidator v);
/**
* Registers the incoming message hook for the given client. The hook will
* be called once for each incoming message that passes validation. This
* method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerIncomingMessageHook(ClientId c, int majorVersion,
IncomingMessageHook hook);
interface MessageValidator {
/**
* Validates the given message and returns its metadata and
* dependencies.
*/
MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException;
}
interface IncomingMessageHook {
/**
* Called once for each incoming message that passes validation.
*
* @return whether or not this message should be shared
* @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup,
* whereas if an InvalidMessageException is thrown,
* the message will be permanently invalidated.
* @throws InvalidMessageException for any non-database error
* that occurs while handling remotely created data.
* This includes errors that occur while handling locally created data
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Throwing this will delete the incoming message and its metadata
* marking it as invalid in the database.
* Never rethrow DbException as InvalidMessageException!
*/
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException;
}
}

View File

@@ -3,11 +3,10 @@ package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.validation.MessageState;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.sync.ValidationManager.State;
/** /**
* An event that is broadcast when a message state changed. * An event that is broadcast when a message state changed.
*/ */
@@ -17,10 +16,10 @@ public class MessageStateChangedEvent extends Event {
private final MessageId messageId; private final MessageId messageId;
private final boolean local; private final boolean local;
private final State state; private final MessageState state;
public MessageStateChangedEvent(MessageId messageId, boolean local, public MessageStateChangedEvent(MessageId messageId, boolean local,
State state) { MessageState state) {
this.messageId = messageId; this.messageId = messageId;
this.local = local; this.local = local;
this.state = state; this.state = state;
@@ -34,7 +33,7 @@ public class MessageStateChangedEvent extends Event {
return local; return local;
} }
public State getState() { public MessageState getState() {
return state; return state;
} }

View File

@@ -0,0 +1,31 @@
package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
public interface IncomingMessageHook {
/**
* Called once for each incoming message that passes validation.
*
* @return whether or not this message should be shared
* @throws DbException Should only be used for real database errors.
* If this is thrown, delivery will be attempted again at next startup,
* whereas if an InvalidMessageException is thrown,
* the message will be permanently invalidated.
* @throws InvalidMessageException for any non-database error
* that occurs while handling remotely created data.
* This includes errors that occur while handling locally created data
* in a context controlled by remotely created data
* (for example, parsing the metadata of a dependency
* of an incoming message).
* Throwing this will delete the incoming message and its metadata
* marking it as invalid in the database.
* Never rethrow DbException as InvalidMessageException!
*/
boolean incomingMessage(Transaction txn, Message m, Metadata meta)
throws DbException, InvalidMessageException;
}

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.api.sync.validation;
public enum MessageState {
UNKNOWN(0), INVALID(1), PENDING(2), DELIVERED(3);
private final int value;
MessageState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static MessageState fromValue(int value) {
for (MessageState s : values()) if (s.value == value) return s;
throw new IllegalArgumentException();
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
public interface MessageValidator {
/**
* Validates the given message and returns its metadata and
* dependencies.
*/
MessageContext validateMessage(Message m, Group g)
throws InvalidMessageException;
}

View File

@@ -0,0 +1,31 @@
package org.briarproject.bramble.api.sync.validation;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId;
/**
* Responsible for managing message validators and passing them messages to
* validate.
*/
@NotNullByDefault
public interface ValidationManager {
/**
* Registers the {@link MessageValidator} for the given client. This method
* should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerMessageValidator(ClientId c, int majorVersion,
MessageValidator v);
/**
* Registers the {@link IncomingMessageHook} for the given client. The hook
* will be called once for each incoming message that passes validation.
* This method should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/
void registerIncomingMessageHook(ClientId c, int majorVersion,
IncomingMessageHook hook);
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
public class ByteUtils { public class ByteUtils {
/** /**
@@ -12,15 +14,26 @@ public class ByteUtils {
*/ */
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1 public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
/** The number of bytes needed to encode a 16-bit integer. */ /**
* The number of bytes needed to encode a 16-bit integer.
*/
public static final int INT_16_BYTES = 2; public static final int INT_16_BYTES = 2;
/** The number of bytes needed to encode a 32-bit integer. */ /**
* The number of bytes needed to encode a 32-bit integer.
*/
public static final int INT_32_BYTES = 4; public static final int INT_32_BYTES = 4;
/** The number of bytes needed to encode a 64-bit integer. */ /**
* The number of bytes needed to encode a 64-bit integer.
*/
public static final int INT_64_BYTES = 8; public static final int INT_64_BYTES = 8;
/**
* The maximum number of bytes needed to encode a variable-length integer.
*/
public static final int MAX_VARINT_BYTES = 9;
public static void writeUint16(int src, byte[] dest, int offset) { public static void writeUint16(int src, byte[] dest, int offset) {
if (src < 0) throw new IllegalArgumentException(); if (src < 0) throw new IllegalArgumentException();
if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException(); if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
@@ -55,6 +68,42 @@ public class ByteUtils {
dest[offset + 7] = (byte) (src & 0xFF); dest[offset + 7] = (byte) (src & 0xFF);
} }
/**
* Returns the number of bytes needed to represent 'src' as a
* variable-length integer.
* <p>
* 'src' must not be negative.
*/
public static int getVarIntBytes(long src) {
if (src < 0) throw new IllegalArgumentException();
int len = 1;
while ((src >>= 7) > 0) len++;
return len;
}
/**
* Writes 'src' to 'dest' as a variable-length integer, starting at
* 'offset', and returns the number of bytes written.
* <p>
* `src` must not be negative.
*/
public static int writeVarInt(long src, byte[] dest, int offset) {
if (src < 0) throw new IllegalArgumentException();
int len = getVarIntBytes(src);
if (dest.length < offset + len) throw new IllegalArgumentException();
// Work backwards from the end
int end = offset + len - 1;
for (int i = end; i >= offset; i--) {
// Encode 7 bits
dest[i] = (byte) (src & 0x7F);
// Raise the continuation flag, except for the last byte
if (i < end) dest[i] |= (byte) 0x80;
// Shift out the bits that were encoded
src >>= 7;
}
return len;
}
public static int readUint16(byte[] src, int offset) { public static int readUint16(byte[] src, int offset) {
if (src.length < offset + INT_16_BYTES) if (src.length < offset + INT_16_BYTES)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -83,14 +132,46 @@ public class ByteUtils {
| (src[offset + 7] & 0xFFL); | (src[offset + 7] & 0xFFL);
} }
public static int readUint(byte[] src, int bits) { /**
if (src.length << 3 < bits) throw new IllegalArgumentException(); * Returns the length in bytes of a variable-length integer encoded in
int dest = 0; * 'src' starting at 'offset'.
for (int i = 0; i < bits; i++) { *
if ((src[i >> 3] & 128 >> (i & 7)) != 0) dest |= 1 << bits - i - 1; * @throws FormatException if there is not a valid variable-length integer
* at the specified position.
*/
public static int getVarIntBytes(byte[] src, int offset)
throws FormatException {
if (src.length < offset) throw new IllegalArgumentException();
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
// If the continuation flag is lowered, this is the last byte
if ((src[offset + i] & 0x80) == 0) return i + 1;
} }
if (dest < 0) throw new AssertionError(); // We've read 9 bytes or reached the end of the input without finding
if (dest >= 1 << bits) throw new AssertionError(); // the last byte
return dest; throw new FormatException();
}
/**
* Reads a variable-length integer from 'src' starting at 'offset' and
* returns it.
*
* @throws FormatException if there is not a valid variable-length integer
* at the specified position.
*/
public static long readVarInt(byte[] src, int offset)
throws FormatException {
if (src.length < offset) throw new IllegalArgumentException();
long dest = 0;
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
// Decode 7 bits
dest |= src[offset + i] & 0x7F;
// If the continuation flag is lowered, this is the last byte
if ((src[offset + i] & 0x80) == 0) return dest;
// Make room for the next 7 bits
dest <<= 7;
}
// We've read 9 bytes or reached the end of the input without finding
// the last byte
throw new FormatException();
} }
} }

View File

@@ -8,12 +8,15 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class IoUtils { public class IoUtils {
@@ -54,16 +57,35 @@ public class IoUtils {
out.flush(); out.flush();
out.close(); out.close();
} catch (IOException e) { } catch (IOException e) {
tryToClose(in); tryToClose(in, LOG, WARNING);
tryToClose(out); tryToClose(out, LOG, WARNING);
} }
} }
private static void tryToClose(@Nullable Closeable c) { public static void tryToClose(@Nullable Closeable c, Logger logger,
Level level) {
try { try {
if (c != null) c.close(); if (c != null) c.close();
} catch (IOException e) { } catch (IOException e) {
// We did our best logException(logger, level, e);
}
}
public static void tryToClose(@Nullable Socket s, Logger logger,
Level level) {
try {
if (s != null) s.close();
} catch (IOException e) {
logException(logger, level, e);
}
}
public static void tryToClose(@Nullable ServerSocket ss, Logger logger,
Level level) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
logException(logger, level, e);
} }
} }

View File

@@ -10,8 +10,6 @@ public class OsUtils {
@Nullable @Nullable
private static final String os = System.getProperty("os.name"); private static final String os = System.getProperty("os.name");
@Nullable @Nullable
private static final String version = System.getProperty("os.version");
@Nullable
private static final String vendor = System.getProperty("java.vendor"); private static final String vendor = System.getProperty("java.vendor");
public static boolean isWindows() { public static boolean isWindows() {

View File

@@ -8,7 +8,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.reporting.ReportingModule; import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
@@ -31,11 +31,11 @@ public interface BrambleCoreEagerSingletons {
void inject(ReportingModule.EagerSingletons init); void inject(ReportingModule.EagerSingletons init);
void inject(SyncModule.EagerSingletons init);
void inject(SystemModule.EagerSingletons init); void inject(SystemModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init); void inject(TransportModule.EagerSingletons init);
void inject(ValidationModule.EagerSingletons init);
void inject(VersioningModule.EagerSingletons init); void inject(VersioningModule.EagerSingletons init);
} }

View File

@@ -19,6 +19,7 @@ import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.settings.SettingsModule; import org.briarproject.bramble.settings.SettingsModule;
import org.briarproject.bramble.socks.SocksModule; import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.sync.SyncModule; import org.briarproject.bramble.sync.SyncModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
import org.briarproject.bramble.versioning.VersioningModule; import org.briarproject.bramble.versioning.VersioningModule;
@@ -47,6 +48,7 @@ import dagger.Module;
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
TransportModule.class, TransportModule.class,
ValidationModule.class,
VersioningModule.class VersioningModule.class
}) })
public class BrambleCoreModule { public class BrambleCoreModule {
@@ -60,9 +62,9 @@ public class BrambleCoreModule {
c.inject(new PluginModule.EagerSingletons()); c.inject(new PluginModule.EagerSingletons());
c.inject(new PropertiesModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons());
c.inject(new ReportingModule.EagerSingletons()); c.inject(new ReportingModule.EagerSingletons());
c.inject(new SyncModule.EagerSingletons());
c.inject(new SystemModule.EagerSingletons()); c.inject(new SystemModule.EagerSingletons());
c.inject(new TransportModule.EagerSingletons()); c.inject(new TransportModule.EagerSingletons());
c.inject(new ValidationModule.EagerSingletons());
c.inject(new VersioningModule.EagerSingletons()); c.inject(new VersioningModule.EagerSingletons());
} }
} }

View File

@@ -22,7 +22,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.ValidationManager.State; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.KeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
@@ -106,7 +106,7 @@ interface Database<T> {
* @param sender the contact from whom the message was received, or null * @param sender the contact from whom the message was received, or null
* if the message was created locally. * if the message was created locally.
*/ */
void addMessage(T txn, Message m, State state, boolean shared, void addMessage(T txn, Message m, MessageState state, boolean shared,
@Nullable ContactId sender) throws DbException; @Nullable ContactId sender) throws DbException;
/** /**
@@ -114,7 +114,7 @@ interface Database<T> {
* in the given state. * in the given state.
*/ */
void addMessageDependency(T txn, Message dependent, MessageId dependency, void addMessageDependency(T txn, Message dependent, MessageId dependency,
State dependentState) throws DbException; MessageState dependentState) throws DbException;
/** /**
* Records that a message has been offered by the given contact. * Records that a message has been offered by the given contact.
@@ -310,11 +310,11 @@ interface Database<T> {
/** /**
* Returns the IDs and states of all dependencies of the given message. * Returns the IDs and states of all dependencies of the given message.
* For missing dependencies and dependencies in other groups, the state * For missing dependencies and dependencies in other groups, the state
* {@link State UNKNOWN} is returned. * {@link MessageState UNKNOWN} is returned.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<MessageId, State> getMessageDependencies(T txn, MessageId m) Map<MessageId, MessageState> getMessageDependencies(T txn, MessageId m)
throws DbException; throws DbException;
/** /**
@@ -324,7 +324,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Map<MessageId, State> getMessageDependents(T txn, MessageId m) Map<MessageId, MessageState> getMessageDependents(T txn, MessageId m)
throws DbException; throws DbException;
/** /**
@@ -383,7 +383,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
State getMessageState(T txn, MessageId m) throws DbException; MessageState getMessageState(T txn, MessageId m) throws DbException;
/** /**
* Returns the status of all delivered messages in the given group with * Returns the status of all delivered messages in the given group with
@@ -637,7 +637,8 @@ interface Database<T> {
/** /**
* Sets the validation and delivery state of the given message. * Sets the validation and delivery state of the given message.
*/ */
void setMessageState(T txn, MessageId m, State state) throws DbException; void setMessageState(T txn, MessageId m, MessageState state)
throws DbException;
/** /**
* Sets the reordering window for the given key set and transport in the * Sets the reordering window for the given key set and transport in the

View File

@@ -43,7 +43,6 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
import org.briarproject.bramble.api.sync.Request; import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.ValidationManager.State;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent; import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent; import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
@@ -55,6 +54,7 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent; import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.transport.KeySet; import org.briarproject.bramble.api.transport.KeySet;
import org.briarproject.bramble.api.transport.KeySetId; import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
@@ -75,8 +75,8 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -579,7 +579,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public State getMessageState(Transaction transaction, MessageId m) public MessageState getMessageState(Transaction transaction, MessageId m)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
@@ -619,8 +619,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Map<MessageId, State> getMessageDependencies(Transaction transaction, public Map<MessageId, MessageState> getMessageDependencies(
MessageId m) throws DbException { Transaction transaction, MessageId m) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
@@ -628,8 +628,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Map<MessageId, State> getMessageDependents(Transaction transaction, public Map<MessageId, MessageState> getMessageDependents(
MessageId m) throws DbException { Transaction transaction, MessageId m) throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
@@ -918,7 +918,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public void setMessageState(Transaction transaction, MessageId m, public void setMessageState(Transaction transaction, MessageId m,
State state) throws DbException { MessageState state) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException(); if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, m)) if (!db.containsMessage(txn, m))
@@ -935,10 +935,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
T txn = unbox(transaction); T txn = unbox(transaction);
if (!db.containsMessage(txn, dependent.getId())) if (!db.containsMessage(txn, dependent.getId()))
throw new NoSuchMessageException(); throw new NoSuchMessageException();
State dependentState = db.getMessageState(txn, dependent.getId()); MessageState dependentState =
db.getMessageState(txn, dependent.getId());
for (MessageId dependency : dependencies) { for (MessageId dependency : dependencies) {
db.addMessageDependency(txn, dependent, dependency, db.addMessageDependency(txn, dependent, dependency, dependentState);
dependentState);
} }
} }

View File

@@ -15,16 +15,23 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
/** /**
* Contains all the H2-specific code for the database. * Contains all the H2-specific code for the database.
*/ */
@NotNullByDefault @NotNullByDefault
class H2Database extends JdbcDatabase { class H2Database extends JdbcDatabase {
private static final Logger LOG = getLogger(H2Database.class.getName());
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
@@ -121,8 +128,8 @@ class H2Database extends JdbcDatabase {
s.close(); s.close();
c.close(); c.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
tryToClose(c); tryToClose(c, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }

View File

@@ -14,16 +14,24 @@ import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
/** /**
* Contains all the HSQLDB-specific code for the database. * Contains all the HSQLDB-specific code for the database.
*/ */
@NotNullByDefault @NotNullByDefault
class HyperSqlDatabase extends JdbcDatabase { class HyperSqlDatabase extends JdbcDatabase {
private static final Logger LOG =
getLogger(HyperSqlDatabase.class.getName());
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
@@ -72,8 +80,8 @@ class HyperSqlDatabase extends JdbcDatabase {
s.close(); s.close();
c.close(); c.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
tryToClose(c); tryToClose(c, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -122,8 +130,8 @@ class HyperSqlDatabase extends JdbcDatabase {
s.close(); s.close();
c.close(); c.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
tryToClose(c); tryToClose(c, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }

View File

@@ -24,7 +24,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.ValidationManager.State; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySet; import org.briarproject.bramble.api.transport.KeySet;
@@ -64,14 +64,15 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY; import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS; import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY; import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry; import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -458,30 +459,6 @@ abstract class JdbcDatabase implements Database<Connection> {
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE); mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
} }
private void tryToClose(@Nullable ResultSet rs) {
try {
if (rs != null) rs.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
protected void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
protected void tryToClose(@Nullable Connection c) {
try {
if (c != null) c.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
private void createTables(Connection txn) throws DbException { private void createTables(Connection txn) throws DbException {
Statement s = null; Statement s = null;
try { try {
@@ -502,7 +479,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS)); s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -519,7 +496,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP); s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -566,11 +543,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} catch (SQLException e) { } catch (SQLException e) {
// Try to close the connection // Try to close the connection
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
try { tryToClose(txn, LOG, WARNING);
txn.close();
} catch (SQLException e1) {
logException(LOG, WARNING, e1);
}
// Whatever happens, allow the database to close // Whatever happens, allow the database to close
connectionsLock.lock(); connectionsLock.lock();
try { try {
@@ -659,8 +632,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return c; return c;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -681,7 +654,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -704,7 +677,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Create a status row for each message in the group // Create a status row for each message in the group
addStatus(txn, c, g, groupShared); addStatus(txn, c, g, groupShared);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -724,7 +697,7 @@ abstract class JdbcDatabase implements Database<Connection> {
while (rs.next()) { while (rs.next()) {
MessageId id = new MessageId(rs.getBytes(1)); MessageId id = new MessageId(rs.getBytes(1));
long timestamp = rs.getLong(2); long timestamp = rs.getLong(2);
State state = State.fromValue(rs.getInt(3)); MessageState state = MessageState.fromValue(rs.getInt(3));
boolean messageShared = rs.getBoolean(4); boolean messageShared = rs.getBoolean(4);
int length = rs.getInt(5); int length = rs.getInt(5);
boolean deleted = rs.getBoolean(6); boolean deleted = rs.getBoolean(6);
@@ -735,8 +708,8 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close(); rs.close();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -761,13 +734,13 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public void addMessage(Connection txn, Message m, State state, public void addMessage(Connection txn, Message m, MessageState state,
boolean messageShared, @Nullable ContactId sender) boolean messageShared, @Nullable ContactId sender)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -810,7 +783,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -840,14 +813,14 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g, private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g,
long timestamp, int length, State state, boolean groupShared, long timestamp, int length, MessageState state, boolean groupShared,
boolean messageShared, boolean deleted, boolean seen) boolean messageShared, boolean deleted, boolean seen)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
@@ -873,14 +846,15 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public void addMessageDependency(Connection txn, Message dependent, public void addMessageDependency(Connection txn, Message dependent,
MessageId dependency, State dependentState) throws DbException { MessageId dependency, MessageState dependentState)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -891,9 +865,9 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(1, dependency.getBytes()); ps.setBytes(1, dependency.getBytes());
ps.setBytes(2, dependent.getGroupId().getBytes()); ps.setBytes(2, dependent.getGroupId().getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
State dependencyState = null; MessageState dependencyState = null;
if (rs.next()) { if (rs.next()) {
dependencyState = State.fromValue(rs.getInt(1)); dependencyState = MessageState.fromValue(rs.getInt(1));
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
} }
rs.close(); rs.close();
@@ -914,8 +888,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -934,7 +908,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1014,8 +988,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return keySetId; return keySetId;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1038,8 +1012,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1060,8 +1034,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1082,8 +1056,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1104,8 +1078,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1126,8 +1100,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1148,8 +1122,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1173,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return found; return found;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1197,8 +1171,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return count; return count;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1222,7 +1196,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1239,7 +1213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1272,8 +1246,8 @@ abstract class JdbcDatabase implements Database<Connection> {
return new Contact(c, author, localAuthorId, alias, verified, return new Contact(c, author, localAuthorId, alias, verified,
active); active);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1309,8 +1283,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return contacts; return contacts;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1332,8 +1306,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1370,8 +1344,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return contacts; return contacts;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1394,8 +1368,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return new Group(g, clientId, majorVersion, descriptor); return new Group(g, clientId, majorVersion, descriptor);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1422,8 +1396,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return groups; return groups;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1448,8 +1422,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return v; return v;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1472,8 +1446,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return visible; return visible;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1504,8 +1478,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return localAuthor; return localAuthor;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1536,8 +1510,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return authors; return authors;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1565,8 +1539,8 @@ abstract class JdbcDatabase implements Database<Connection> {
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length); System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
return new Message(m, g, timestamp, body); return new Message(m, g, timestamp, body);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1589,8 +1563,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1627,8 +1601,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (intersection == null) throw new AssertionError(); if (intersection == null) throw new AssertionError();
return intersection; return intersection;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1660,8 +1634,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return all; return all;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1695,8 +1669,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return metadata; return metadata;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1719,8 +1693,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return metadata; return metadata;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1745,8 +1719,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return metadata; return metadata;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1775,8 +1749,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return statuses; return statuses;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1806,14 +1780,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return status; return status;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public Map<MessageId, State> getMessageDependencies(Connection txn, public Map<MessageId, MessageState> getMessageDependencies(Connection txn,
MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1824,10 +1798,10 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependencies = new HashMap<>(); Map<MessageId, MessageState> dependencies = new HashMap<>();
while (rs.next()) { while (rs.next()) {
MessageId dependency = new MessageId(rs.getBytes(1)); MessageId dependency = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); MessageState state = MessageState.fromValue(rs.getInt(2));
if (rs.wasNull()) if (rs.wasNull())
state = UNKNOWN; // Missing or in a different group state = UNKNOWN; // Missing or in a different group
dependencies.put(dependency, state); dependencies.put(dependency, state);
@@ -1836,14 +1810,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return dependencies; return dependencies;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public Map<MessageId, State> getMessageDependents(Connection txn, public Map<MessageId, MessageState> getMessageDependents(Connection txn,
MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1857,24 +1831,24 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<MessageId, State> dependents = new HashMap<>(); Map<MessageId, MessageState> dependents = new HashMap<>();
while (rs.next()) { while (rs.next()) {
MessageId dependent = new MessageId(rs.getBytes(1)); MessageId dependent = new MessageId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); MessageState state = MessageState.fromValue(rs.getInt(2));
dependents.put(dependent, state); dependents.put(dependent, state);
} }
rs.close(); rs.close();
ps.close(); ps.close();
return dependents; return dependents;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public State getMessageState(Connection txn, MessageId m) public MessageState getMessageState(Connection txn, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -1884,14 +1858,14 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException(); if (!rs.next()) throw new DbStateException();
State state = State.fromValue(rs.getInt(1)); MessageState state = MessageState.fromValue(rs.getInt(1));
if (rs.next()) throw new DbStateException(); if (rs.next()) throw new DbStateException();
rs.close(); rs.close();
ps.close(); ps.close();
return state; return state;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1915,8 +1889,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1949,8 +1923,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -1974,8 +1948,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2013,8 +1987,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2032,7 +2006,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
private Collection<MessageId> getMessagesInState(Connection txn, private Collection<MessageId> getMessagesInState(Connection txn,
State state) throws DbException { MessageState state) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -2047,8 +2021,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2075,8 +2049,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2105,8 +2079,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return nextSendTime; return nextSendTime;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2144,8 +2118,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return ids; return ids;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2167,8 +2141,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return s; return s;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2233,8 +2207,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return keys; return keys;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2253,7 +2227,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2280,7 +2254,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2307,7 +2281,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2337,7 +2311,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException(); if (rows != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2359,7 +2333,7 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery(); rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException(); if (!rs.next()) throw new DbStateException();
GroupId g = new GroupId(rs.getBytes(1)); GroupId g = new GroupId(rs.getBytes(1));
State state = State.fromValue(rs.getInt(2)); MessageState state = MessageState.fromValue(rs.getInt(2));
rs.close(); rs.close();
ps.close(); ps.close();
// Insert any keys that don't already exist // Insert any keys that don't already exist
@@ -2382,8 +2356,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException(); if (rows != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2449,7 +2423,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
return added; return added;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2496,7 +2470,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException(); if (rows != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2515,7 +2489,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2534,7 +2508,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2553,7 +2527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2570,7 +2544,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2586,7 +2560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2614,7 +2588,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2631,7 +2605,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2647,7 +2621,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2666,7 +2640,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close(); ps.close();
return affected == 1; return affected == 1;
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2691,7 +2665,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException(); if (rows != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2708,7 +2682,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2729,7 +2703,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2748,7 +2722,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2766,7 +2740,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2784,7 +2758,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2803,7 +2777,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2833,7 +2807,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2859,13 +2833,13 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@Override @Override
public void setMessageState(Connection txn, MessageId m, State state) public void setMessageState(Connection txn, MessageId m, MessageState state)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
@@ -2912,7 +2886,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2935,7 +2909,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2954,7 +2928,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -2990,8 +2964,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(rs); tryToClose(rs, LOG, WARNING);
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
@@ -3058,7 +3032,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows < 0 || rows > 1) throw new DbStateException(); if (rows < 0 || rows > 1) throw new DbStateException();
ps.close(); ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class JdbcUtils {
static void tryToClose(@Nullable ResultSet rs, Logger logger, Level level) {
try {
if (rs != null) rs.close();
} catch (SQLException e) {
logException(logger, level, e);
}
}
static void tryToClose(@Nullable Statement s, Logger logger, Level level) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(logger, level, e);
}
}
static void tryToClose(@Nullable Connection c, Logger logger, Level level) {
try {
if (c != null) c.close();
} catch (SQLException e) {
logException(logger, level, e);
}
}
}

View File

@@ -7,10 +7,8 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration38_39 implements Migration<Connection> { class Migration38_39 implements Migration<Connection> {
@@ -40,16 +38,8 @@ class Migration38_39 implements Migration<Connection> {
+ " ALTER COLUMN contactId" + " ALTER COLUMN contactId"
+ " SET NOT NULL"); + " SET NOT NULL");
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
} }

View File

@@ -7,10 +7,8 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration39_40 implements Migration<Connection> { class Migration39_40 implements Migration<Connection> {
@@ -39,16 +37,8 @@ class Migration39_40 implements Migration<Connection> {
+ " ALTER COLUMN eta" + " ALTER COLUMN eta"
+ " SET NOT NULL"); + " SET NOT NULL");
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
} }

View File

@@ -7,11 +7,9 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
class Migration40_41 implements Migration<Connection> { class Migration40_41 implements Migration<Connection> {
@@ -41,16 +39,8 @@ class Migration40_41 implements Migration<Connection> {
s.execute("ALTER TABLE contacts" s.execute("ALTER TABLE contacts"
+ dbTypes.replaceTypes(" ADD alias _STRING")); + dbTypes.replaceTypes(" ADD alias _STRING"));
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw new DbException(e); throw new DbException(e);
} }
} }
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
logException(LOG, WARNING, e);
}
}
} }

View File

@@ -4,12 +4,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault @NotNullByDefault
class FileTransportReader implements TransportConnectionReader { class FileTransportReader implements TransportConnectionReader {
@@ -34,11 +33,7 @@ class FileTransportReader implements TransportConnectionReader {
@Override @Override
public void dispose(boolean exception, boolean recognised) { public void dispose(boolean exception, boolean recognised) {
try { tryToClose(in, LOG, WARNING);
in.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
plugin.readerFinished(file, exception, recognised); plugin.readerFinished(file, exception, recognised);
} }
} }

View File

@@ -4,12 +4,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault @NotNullByDefault
class FileTransportWriter implements TransportConnectionWriter { class FileTransportWriter implements TransportConnectionWriter {
@@ -44,11 +43,7 @@ class FileTransportWriter implements TransportConnectionWriter {
@Override @Override
public void dispose(boolean exception) { public void dispose(boolean exception) {
try { tryToClose(out, LOG, WARNING);
out.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
plugin.writerFinished(file, exception); plugin.writerFinished(file, exception);
} }
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import java.io.IOException; import java.io.IOException;
@@ -35,7 +36,6 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
@NotNullByDefault @NotNullByDefault
@@ -293,11 +293,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public void close() { public void close() {
try { IoUtils.tryToClose(ss, LOG, WARNING);
ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
} }
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import java.io.IOException; import java.io.IOException;
@@ -153,13 +154,8 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
protected void tryToClose(@Nullable ServerSocket ss) { protected void tryToClose(@Nullable ServerSocket ss) {
try { IoUtils.tryToClose(ss, LOG, WARNING);
if (ss != null) ss.close(); callback.transportDisabled();
} catch (IOException e) {
logException(LOG, WARNING, e);
} finally {
callback.transportDisabled();
}
} }
String getIpPortString(InetSocketAddress a) { String getIpPortString(InetSocketAddress a) {

View File

@@ -31,7 +31,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider; import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.Closeable;
import java.io.EOFException; import java.io.EOFException;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -303,8 +302,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
IoUtils.copyAndClose(in, out); IoUtils.copyAndClose(in, out);
doneFile.createNewFile(); doneFile.createNewFile();
} catch (IOException e) { } catch (IOException e) {
tryToClose(in); IoUtils.tryToClose(in, LOG, WARNING);
tryToClose(out); IoUtils.tryToClose(out, LOG, WARNING);
throw new PluginException(e); throw new PluginException(e);
} }
} }
@@ -341,22 +340,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return getClass().getClassLoader().getResourceAsStream("torrc"); return getClass().getClassLoader().getResourceAsStream("torrc");
} }
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
private void tryToClose(@Nullable Socket s) {
try {
if (s != null) s.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
private void listFiles(File f) { private void listFiles(File f) {
if (f.isDirectory()) { if (f.isDirectory()) {
File[] children = f.listFiles(); File[] children = f.listFiles();
@@ -378,7 +361,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
return b; return b;
} finally { } finally {
tryToClose(in); IoUtils.tryToClose(in, LOG, WARNING);
} }
} }
@@ -418,13 +401,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void tryToClose(@Nullable ServerSocket ss) { private void tryToClose(@Nullable ServerSocket ss) {
try { IoUtils.tryToClose(ss, LOG, WARNING);
if (ss != null) ss.close(); callback.transportDisabled();
} catch (IOException e) {
logException(LOG, WARNING, e);
} finally {
callback.transportDisabled();
}
} }
private void publishHiddenService(String port) { private void publishHiddenService(String port) {
@@ -593,7 +571,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Could not connect to " + scrubOnion(bestOnion) LOG.info("Could not connect to " + scrubOnion(bestOnion)
+ ": " + e.toString()); + ": " + e.toString());
} }
tryToClose(s); IoUtils.tryToClose(s, LOG, WARNING);
return null; return null;
} }
} }

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.properties.TransportPropertyManager; import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;

View File

@@ -24,7 +24,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook;

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -26,13 +25,12 @@ import java.net.Socket;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -66,7 +64,7 @@ class DevReporterImpl implements DevReporter, EventListener {
s.setSoTimeout(SOCKET_TIMEOUT); s.setSoTimeout(SOCKET_TIMEOUT);
return s; return s;
} catch (IOException e) { } catch (IOException e) {
tryToClose(s); tryToClose(s, LOG, WARNING);
throw e; throw e;
} }
} }
@@ -88,8 +86,7 @@ class DevReporterImpl implements DevReporter, EventListener {
writer.append(armoured); writer.append(armoured);
writer.flush(); writer.flush();
} finally { } finally {
if (writer != null) tryToClose(writer, LOG, WARNING);
writer.close();
} }
} }
@@ -121,27 +118,11 @@ class DevReporterImpl implements DevReporter, EventListener {
f.delete(); f.delete();
} catch (IOException e) { } catch (IOException e) {
LOG.log(WARNING, "Failed to send reports", e); LOG.log(WARNING, "Failed to send reports", e);
tryToClose(out); tryToClose(out, LOG, WARNING);
tryToClose(in); tryToClose(in, LOG, WARNING);
return; return;
} }
} }
LOG.info("Reports sent"); LOG.info("Reports sent");
} }
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
private void tryToClose(@Nullable Socket s) {
try {
if (s != null) s.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
} }

View File

@@ -1,23 +1,11 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.GroupFactory; import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory; import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.SyncRecordWriterFactory; import org.briarproject.bramble.api.sync.SyncRecordWriterFactory;
import org.briarproject.bramble.api.sync.SyncSessionFactory; import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -26,29 +14,14 @@ import dagger.Provides;
@Module @Module
public class SyncModule { public class SyncModule {
public static class EagerSingletons {
@Inject
ValidationManager validationManager;
}
/**
* The maximum number of validation tasks to delegate to the crypto
* executor concurrently.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_CONCURRENT_VALIDATION_TASKS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
@Provides @Provides
GroupFactory provideGroupFactory(CryptoComponent crypto) { GroupFactory provideGroupFactory(GroupFactoryImpl groupFactory) {
return new GroupFactoryImpl(crypto); return groupFactory;
} }
@Provides @Provides
MessageFactory provideMessageFactory(CryptoComponent crypto) { MessageFactory provideMessageFactory(MessageFactoryImpl messageFactory) {
return new MessageFactoryImpl(crypto); return messageFactory;
} }
@Provides @Provides
@@ -65,30 +38,8 @@ public class SyncModule {
@Provides @Provides
@Singleton @Singleton
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db, SyncSessionFactory provideSyncSessionFactory(
@DatabaseExecutor Executor dbExecutor, EventBus eventBus, SyncSessionFactoryImpl syncSessionFactory) {
Clock clock, SyncRecordReaderFactory recordReaderFactory, return syncSessionFactory;
SyncRecordWriterFactory recordWriterFactory) {
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
recordReaderFactory, recordWriterFactory);
}
@Provides
@Singleton
ValidationManager provideValidationManager(
LifecycleManager lifecycleManager, EventBus eventBus,
ValidationManagerImpl validationManager) {
lifecycleManager.registerService(validationManager);
eventBus.addListener(validationManager);
return validationManager;
}
@Provides
@Singleton
@ValidationExecutor
Executor provideValidationExecutor(
@CryptoExecutor Executor cryptoExecutor) {
return new PoliteExecutor("ValidationExecutor", cryptoExecutor,
MAX_CONCURRENT_VALIDATION_TASKS);
} }
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync.validation;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync.validation;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -18,8 +18,11 @@ import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext; import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.versioning.ClientMajorVersion; import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import java.util.Collection; import java.util.Collection;
@@ -37,9 +40,9 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe @ThreadSafe
@@ -166,9 +169,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
// Check if message is still pending // Check if message is still pending
if (db.getMessageState(txn, id) == PENDING) { if (db.getMessageState(txn, id) == PENDING) {
// Check if dependencies are valid and delivered // Check if dependencies are valid and delivered
Map<MessageId, State> states = Map<MessageId, MessageState> states =
db.getMessageDependencies(txn, id); db.getMessageDependencies(txn, id);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, MessageState> e : states.entrySet()) {
if (e.getValue() == INVALID) anyInvalid = true; if (e.getValue() == INVALID) anyInvalid = true;
if (e.getValue() != DELIVERED) allDelivered = false; if (e.getValue() != DELIVERED) allDelivered = false;
} }
@@ -256,9 +259,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
if (!dependencies.isEmpty()) { if (!dependencies.isEmpty()) {
db.addMessageDependencies(txn, m, dependencies); db.addMessageDependencies(txn, m, dependencies);
// Check if dependencies are valid and delivered // Check if dependencies are valid and delivered
Map<MessageId, State> states = Map<MessageId, MessageState> states =
db.getMessageDependencies(txn, id); db.getMessageDependencies(txn, id);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, MessageState> e : states.entrySet()) {
if (e.getValue() == INVALID) anyInvalid = true; if (e.getValue() == INVALID) anyInvalid = true;
if (e.getValue() != DELIVERED) allDelivered = false; if (e.getValue() != DELIVERED) allDelivered = false;
} }
@@ -322,8 +325,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
@DatabaseExecutor @DatabaseExecutor
private void addPendingDependents(Transaction txn, MessageId m, private void addPendingDependents(Transaction txn, MessageId m,
Queue<MessageId> pending) throws DbException { Queue<MessageId> pending) throws DbException {
Map<MessageId, State> states = db.getMessageDependents(txn, m); Map<MessageId, MessageState> states = db.getMessageDependents(txn, m);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, MessageState> e : states.entrySet()) {
if (e.getValue() == PENDING) pending.add(e.getKey()); if (e.getValue() == PENDING) pending.add(e.getKey());
} }
} }
@@ -411,8 +414,8 @@ class ValidationManagerImpl implements ValidationManager, Service,
@DatabaseExecutor @DatabaseExecutor
private void addDependentsToInvalidate(Transaction txn, private void addDependentsToInvalidate(Transaction txn,
MessageId m, Queue<MessageId> invalidate) throws DbException { MessageId m, Queue<MessageId> invalidate) throws DbException {
Map<MessageId, State> states = db.getMessageDependents(txn, m); Map<MessageId, MessageState> states = db.getMessageDependents(txn, m);
for (Entry<MessageId, State> e : states.entrySet()) { for (Entry<MessageId, MessageState> e : states.entrySet()) {
if (e.getValue() != INVALID) invalidate.add(e.getKey()); if (e.getValue() != INVALID) invalidate.add(e.getKey());
} }
} }

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.sync.validation;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.validation.ValidationManager;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ValidationModule {
public static class EagerSingletons {
@Inject
ValidationManager validationManager;
}
/**
* The maximum number of validation tasks to delegate to the crypto
* executor concurrently.
* <p>
* The number of available processors can change during the lifetime of the
* JVM, so this is just a reasonable guess.
*/
private static final int MAX_CONCURRENT_VALIDATION_TASKS =
Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
@Provides
@Singleton
ValidationManager provideValidationManager(
LifecycleManager lifecycleManager, EventBus eventBus,
ValidationManagerImpl validationManager) {
lifecycleManager.registerService(validationManager);
eventBus.addListener(validationManager);
return validationManager;
}
@Provides
@Singleton
@ValidationExecutor
Executor provideValidationExecutor(
@CryptoExecutor Executor cryptoExecutor) {
return new PoliteExecutor("ValidationExecutor", cryptoExecutor,
MAX_CONCURRENT_VALIDATION_TASKS);
}
}

View File

@@ -13,32 +13,33 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class LinuxSecureRandomProvider extends AbstractSecureRandomProvider { class UnixSecureRandomProvider extends AbstractSecureRandomProvider {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomProvider.class.getName()); getLogger(UnixSecureRandomProvider.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom"); private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final AtomicBoolean seeded = new AtomicBoolean(false); private final AtomicBoolean seeded = new AtomicBoolean(false);
private final File outputDevice; private final File outputDevice;
LinuxSecureRandomProvider() { UnixSecureRandomProvider() {
this(RANDOM_DEVICE); this(RANDOM_DEVICE);
} }
LinuxSecureRandomProvider(File outputDevice) { UnixSecureRandomProvider(File outputDevice) {
this.outputDevice = outputDevice; this.outputDevice = outputDevice;
} }
@Override @Override
public Provider getProvider() { public Provider getProvider() {
if (!seeded.getAndSet(true)) writeSeed(); if (!seeded.getAndSet(true)) writeSeed();
return new LinuxProvider(); return new UnixProvider();
} }
protected void writeSeed() { protected void writeSeed() {
@@ -55,15 +56,15 @@ class LinuxSecureRandomProvider extends AbstractSecureRandomProvider {
} }
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html // Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
private static class LinuxProvider extends Provider { private static class UnixProvider extends Provider {
private LinuxProvider() { private UnixProvider() {
super("LinuxPRNG", 1.1, "A Linux-specific PRNG using /dev/urandom"); super("UnixPRNG", 1.0, "A Unix-specific PRNG using /dev/urandom");
// Although /dev/urandom is not a SHA-1 PRNG, some callers // Although /dev/urandom is not a SHA-1 PRNG, some callers
// explicitly request a SHA1PRNG SecureRandom and we need to // explicitly request a SHA1PRNG SecureRandom and we need to
// prevent them from getting the default implementation whose // prevent them from getting the default implementation whose
// output may have low entropy. // output may have low entropy.
put("SecureRandom.SHA1PRNG", LinuxSecureRandomSpi.class.getName()); put("SecureRandom.SHA1PRNG", UnixSecureRandomSpi.class.getName());
put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
} }
} }

View File

@@ -10,22 +10,24 @@ import java.security.SecureRandomSpi;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
public class LinuxSecureRandomSpi extends SecureRandomSpi { public class UnixSecureRandomSpi extends SecureRandomSpi {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LinuxSecureRandomSpi.class.getName()); getLogger(UnixSecureRandomSpi.class.getName());
private static final File RANDOM_DEVICE = new File("/dev/urandom"); private static final File RANDOM_DEVICE = new File("/dev/urandom");
private final File inputDevice, outputDevice; private final File inputDevice, outputDevice;
public LinuxSecureRandomSpi() { @SuppressWarnings("WeakerAccess")
public UnixSecureRandomSpi() {
this(RANDOM_DEVICE, RANDOM_DEVICE); this(RANDOM_DEVICE, RANDOM_DEVICE);
} }
LinuxSecureRandomSpi(File inputDevice, File outputDevice) { UnixSecureRandomSpi(File inputDevice, File outputDevice) {
this.inputDevice = inputDevice; this.inputDevice = inputDevice;
this.outputDevice = outputDevice; this.outputDevice = outputDevice;
} }

View File

@@ -23,7 +23,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.InvalidMessageException; import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook; import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientMajorVersion; import org.briarproject.bramble.api.versioning.ClientMajorVersion;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;

View File

@@ -4,7 +4,7 @@ import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.MetadataEncoder; import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.sync.ValidationManager; import org.briarproject.bramble.api.sync.validation.ValidationManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager; import org.briarproject.bramble.api.versioning.ClientVersioningManager;

View File

@@ -64,8 +64,8 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE; import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.State; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.UTest; import org.briarproject.bramble.test.UTest;
import org.junit.After; import org.junit.After;
@@ -33,7 +33,7 @@ import java.util.logging.Logger;
import static java.util.logging.Level.OFF; import static java.util.logging.Level.OFF;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
@@ -565,7 +565,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
for (int k = 0; k < MESSAGES_PER_GROUP; k++) { for (int k = 0; k < MESSAGES_PER_GROUP; k++) {
Message m = getMessage(g.getId()); Message m = getMessage(g.getId());
messages.add(m); messages.add(m);
State state = State.fromValue(random.nextInt(4)); MessageState state =
MessageState.fromValue(random.nextInt(4));
boolean shared = random.nextBoolean(); boolean shared = random.nextBoolean();
ContactId sender = random.nextBoolean() ? c : null; ContactId sender = random.nextBoolean() ? c : null;
db.addMessage(txn, m, state, shared, sender); db.addMessage(txn, m, state, shared, sender);

View File

@@ -18,7 +18,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory; import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus; import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.sync.ValidationManager.State; import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.KeySet; import org.briarproject.bramble.api.transport.KeySet;
@@ -58,10 +58,10 @@ import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY; import static org.briarproject.bramble.db.DatabaseConstants.LAST_COMPACTED_KEY;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS; import static org.briarproject.bramble.db.DatabaseConstants.MAX_COMPACTION_INTERVAL_MS;
@@ -1309,7 +1309,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessageDependency(txn, message1, messageId3, PENDING); db.addMessageDependency(txn, message1, messageId3, PENDING);
db.addMessageDependency(txn, message2, messageId4, INVALID); db.addMessageDependency(txn, message2, messageId4, INVALID);
Map<MessageId, State> dependencies; Map<MessageId, MessageState> dependencies;
// Retrieve dependencies for root // Retrieve dependencies for root
dependencies = db.getMessageDependencies(txn, messageId); dependencies = db.getMessageDependencies(txn, messageId);
@@ -1333,7 +1333,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
dependencies = db.getMessageDependencies(txn, messageId4); dependencies = db.getMessageDependencies(txn, messageId4);
assertEquals(0, dependencies.size()); assertEquals(0, dependencies.size());
Map<MessageId, State> dependents; Map<MessageId, MessageState> dependents;
// Root message does not have dependents // Root message does not have dependents
dependents = db.getMessageDependents(txn, messageId); dependents = db.getMessageDependents(txn, messageId);
@@ -1408,7 +1408,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessageDependency(txn, message, messageId3, PENDING); db.addMessageDependency(txn, message, messageId3, PENDING);
// Retrieve the dependencies for the root // Retrieve the dependencies for the root
Map<MessageId, State> dependencies; Map<MessageId, MessageState> dependencies;
dependencies = db.getMessageDependencies(txn, messageId); dependencies = db.getMessageDependencies(txn, messageId);
// The cross-group dependency should have state UNKNOWN // The cross-group dependency should have state UNKNOWN
@@ -1421,7 +1421,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
assertEquals(DELIVERED, dependencies.get(messageId3)); assertEquals(DELIVERED, dependencies.get(messageId3));
// Retrieve the dependents for the message in the second group // Retrieve the dependents for the message in the second group
Map<MessageId, State> dependents; Map<MessageId, MessageState> dependents;
dependents = db.getMessageDependents(txn, messageId1); dependents = db.getMessageDependents(txn, messageId1);
// The cross-group dependent should be excluded // The cross-group dependent should be excluded

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.sync;
import org.briarproject.bramble.crypto.CryptoModule; import org.briarproject.bramble.crypto.CryptoModule;
import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.sync.validation.ValidationModule;
import org.briarproject.bramble.system.SystemModule; import org.briarproject.bramble.system.SystemModule;
import org.briarproject.bramble.test.TestSecureRandomModule; import org.briarproject.bramble.test.TestSecureRandomModule;
import org.briarproject.bramble.transport.TransportModule; import org.briarproject.bramble.transport.TransportModule;
@@ -17,6 +18,7 @@ import dagger.Component;
RecordModule.class, RecordModule.class,
SyncModule.class, SyncModule.class,
SystemModule.class, SystemModule.class,
ValidationModule.class,
TransportModule.class TransportModule.class
}) })
interface SyncIntegrationTestComponent { interface SyncIntegrationTestComponent {

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync.validation;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -13,10 +13,10 @@ import org.briarproject.bramble.api.sync.InvalidMessageException;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext; import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator;
import org.briarproject.bramble.api.sync.ValidationManager.State;
import org.briarproject.bramble.api.sync.event.MessageAddedEvent; import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
import org.briarproject.bramble.api.sync.validation.IncomingMessageHook;
import org.briarproject.bramble.api.sync.validation.MessageState;
import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
@@ -32,10 +32,10 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.validation.MessageState.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.validation.MessageState.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.validation.MessageState.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.validation.MessageState.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getClientId; import static org.briarproject.bramble.test.TestUtils.getClientId;
import static org.briarproject.bramble.test.TestUtils.getGroup; import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage; import static org.briarproject.bramble.test.TestUtils.getMessage;
@@ -559,7 +559,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
public void testRecursiveInvalidation() throws Exception { public void testRecursiveInvalidation() throws Exception {
MessageId messageId3 = new MessageId(getRandomId()); MessageId messageId3 = new MessageId(getRandomId());
MessageId messageId4 = new MessageId(getRandomId()); MessageId messageId4 = new MessageId(getRandomId());
Map<MessageId, State> twoDependents = new LinkedHashMap<>(); Map<MessageId, MessageState> twoDependents = new LinkedHashMap<>();
twoDependents.put(messageId1, PENDING); twoDependents.put(messageId1, PENDING);
twoDependents.put(messageId2, PENDING); twoDependents.put(messageId2, PENDING);
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
@@ -643,10 +643,10 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
Message message4 = getMessage(groupId); Message message4 = getMessage(groupId);
MessageId messageId3 = message3.getId(); MessageId messageId3 = message3.getId();
MessageId messageId4 = message4.getId(); MessageId messageId4 = message4.getId();
Map<MessageId, State> twoDependents = new LinkedHashMap<>(); Map<MessageId, MessageState> twoDependents = new LinkedHashMap<>();
twoDependents.put(messageId1, PENDING); twoDependents.put(messageId1, PENDING);
twoDependents.put(messageId2, PENDING); twoDependents.put(messageId2, PENDING);
Map<MessageId, State> twoDependencies = new LinkedHashMap<>(); Map<MessageId, MessageState> twoDependencies = new LinkedHashMap<>();
twoDependencies.put(messageId1, DELIVERED); twoDependencies.put(messageId1, DELIVERED);
twoDependencies.put(messageId2, DELIVERED); twoDependencies.put(messageId2, DELIVERED);
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);
@@ -765,7 +765,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testOnlyReadyPendingDependentsGetDelivered() throws Exception { public void testOnlyReadyPendingDependentsGetDelivered() throws Exception {
Map<MessageId, State> twoDependencies = new LinkedHashMap<>(); Map<MessageId, MessageState> twoDependencies = new LinkedHashMap<>();
twoDependencies.put(messageId, DELIVERED); twoDependencies.put(messageId, DELIVERED);
twoDependencies.put(messageId2, UNKNOWN); twoDependencies.put(messageId2, UNKNOWN);
Transaction txn = new Transaction(null, true); Transaction txn = new Transaction(null, true);

View File

@@ -1,8 +1,6 @@
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -10,48 +8,50 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import java.security.Provider; import java.security.Provider;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class LinuxSecureRandomProviderTest extends BrambleTestCase { public class UnixSecureRandomProviderTest extends BrambleTestCase {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = getTestDirectory();
@Before @Before
public void setUp() { public void setUp() {
testDir.mkdirs(); assumeTrue(isLinux() || isMac());
assertTrue(testDir.mkdirs());
} }
@Test @Test
public void testGetProviderWritesToRandomDeviceOnFirstCall() public void testGetProviderWritesToRandomDeviceOnFirstCall()
throws Exception { throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the provider's output to a file // Redirect the provider's output to a file
File urandom = new File(testDir, "urandom"); File urandom = new File(testDir, "urandom");
urandom.delete(); if (urandom.exists()) assertTrue(urandom.delete());
assertTrue(urandom.createNewFile()); assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length()); assertEquals(0, urandom.length());
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom); UnixSecureRandomProvider p = new UnixSecureRandomProvider(urandom);
// Getting a provider should write entropy to the file // Getting a provider should write entropy to the file
Provider provider = p.getProvider(); Provider provider = p.getProvider();
assertNotNull(provider); assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName()); assertEquals("UnixPRNG", provider.getName());
// There should be at least 16 bytes from the clock, 8 from the runtime // There should be at least 16 bytes from the clock, 8 from the runtime
long length = urandom.length(); long length = urandom.length();
assertTrue(length >= 24); assertTrue(length >= 24);
// Getting another provider should not write to the file again // Getting another provider should not write to the file again
provider = p.getProvider(); provider = p.getProvider();
assertNotNull(provider); assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName()); assertEquals("UnixPRNG", provider.getName());
assertEquals(length, urandom.length()); assertEquals(length, urandom.length());
} }
@After @After
public void tearDown() { public void tearDown() {
TestUtils.deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
} }

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -15,31 +14,33 @@ import java.io.FileOutputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class LinuxSecureRandomSpiTest extends BrambleTestCase { public class UnixSecureRandomSpiTest extends BrambleTestCase {
private static final File RANDOM_DEVICE = new File("/dev/urandom"); private static final File RANDOM_DEVICE = new File("/dev/urandom");
private static final int SEED_BYTES = 32; private static final int SEED_BYTES = 32;
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = getTestDirectory();
@Before @Before
public void setUp() { public void setUp() {
testDir.mkdirs(); assumeTrue(isLinux() || isMac());
assertTrue(testDir.mkdirs());
} }
@Test @Test
public void testSeedsAreDistinct() { public void testSeedsAreDistinct() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<>(); Set<Bytes> seeds = new HashSet<>();
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(); UnixSecureRandomSpi engine = new UnixSecureRandomSpi();
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
byte[] seed = engine.engineGenerateSeed(SEED_BYTES); byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
assertEquals(SEED_BYTES, seed.length); assertEquals(SEED_BYTES, seed.length);
@@ -49,19 +50,15 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test @Test
public void testEngineSetSeedWritesToRandomDevice() throws Exception { public void testEngineSetSeedWritesToRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Redirect the engine's output to a file // Redirect the engine's output to a file
File urandom = new File(testDir, "urandom"); File urandom = new File(testDir, "urandom");
urandom.delete(); if (urandom.exists()) assertTrue(urandom.delete());
assertTrue(urandom.createNewFile()); assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length()); assertEquals(0, urandom.length());
// Generate a seed // Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES); byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Check that the engine writes the seed to the file // Check that the engine writes the seed to the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE, UnixSecureRandomSpi engine = new UnixSecureRandomSpi(RANDOM_DEVICE,
urandom); urandom);
engine.engineSetSeed(seed); engine.engineSetSeed(seed);
assertEquals(SEED_BYTES, urandom.length()); assertEquals(SEED_BYTES, urandom.length());
@@ -74,15 +71,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test @Test
public void testEngineNextBytesReadsFromRandomDevice() throws Exception { public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy // Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES); byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file // Write the entropy to a file
File urandom = new File(testDir, "urandom"); File urandom = new File(testDir, "urandom");
urandom.delete(); if (urandom.exists()) assertTrue(urandom.delete());
FileOutputStream out = new FileOutputStream(urandom); FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy); out.write(entropy);
out.flush(); out.flush();
@@ -90,7 +83,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
assertTrue(urandom.exists()); assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length()); assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file // Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom, UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
RANDOM_DEVICE); RANDOM_DEVICE);
byte[] b = new byte[SEED_BYTES]; byte[] b = new byte[SEED_BYTES];
engine.engineNextBytes(b); engine.engineNextBytes(b);
@@ -99,15 +92,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test @Test
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception { public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy // Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES); byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file // Write the entropy to a file
File urandom = new File(testDir, "urandom"); File urandom = new File(testDir, "urandom");
urandom.delete(); if (urandom.exists()) assertTrue(urandom.delete());
FileOutputStream out = new FileOutputStream(urandom); FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy); out.write(entropy);
out.flush(); out.flush();
@@ -115,7 +104,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
assertTrue(urandom.exists()); assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length()); assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file // Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom, UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
RANDOM_DEVICE); RANDOM_DEVICE);
byte[] b = engine.engineGenerateSeed(SEED_BYTES); byte[] b = engine.engineGenerateSeed(SEED_BYTES);
assertArrayEquals(entropy, b); assertArrayEquals(entropy, b);
@@ -123,6 +112,6 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@After @After
public void tearDown() { public void tearDown() {
TestUtils.deleteTestDirectory(testDir); deleteTestDirectory(testDir);
} }
} }

View File

@@ -1,118 +1,135 @@
package org.briarproject.bramble.util; package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test; import org.junit.Test;
import java.util.Random;
import static java.util.Arrays.fill;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_VARINT_BYTES;
import static org.briarproject.bramble.util.ByteUtils.getVarIntBytes;
import static org.briarproject.bramble.util.ByteUtils.readUint16;
import static org.briarproject.bramble.util.ByteUtils.readUint32;
import static org.briarproject.bramble.util.ByteUtils.readUint64;
import static org.briarproject.bramble.util.ByteUtils.readVarInt;
import static org.briarproject.bramble.util.ByteUtils.writeUint16;
import static org.briarproject.bramble.util.ByteUtils.writeUint32;
import static org.briarproject.bramble.util.ByteUtils.writeUint64;
import static org.briarproject.bramble.util.ByteUtils.writeVarInt;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@SuppressWarnings("ResultOfMethodCallIgnored")
public class ByteUtilsTest extends BrambleTestCase { public class ByteUtilsTest extends BrambleTestCase {
@Test @Test
public void testReadUint16() { public void testReadUint16() {
byte[] b = StringUtils.fromHexString("00000000"); byte[] b = fromHexString("00000000");
assertEquals(0, ByteUtils.readUint16(b, 1)); assertEquals(0, readUint16(b, 1));
b = StringUtils.fromHexString("00000100"); b = fromHexString("00000100");
assertEquals(1, ByteUtils.readUint16(b, 1)); assertEquals(1, readUint16(b, 1));
b = StringUtils.fromHexString("007FFF00"); b = fromHexString("007FFF00");
assertEquals(Short.MAX_VALUE, ByteUtils.readUint16(b, 1)); assertEquals(Short.MAX_VALUE, readUint16(b, 1));
b = StringUtils.fromHexString("00FFFF00"); b = fromHexString("00FFFF00");
assertEquals(65535, ByteUtils.readUint16(b, 1)); assertEquals(65535, readUint16(b, 1));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint16ValidatesArguments1() { public void testReadUint16ValidatesArguments1() {
ByteUtils.readUint16(new byte[1], 0); readUint16(new byte[1], 0);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint16ValidatesArguments2() { public void testReadUint16ValidatesArguments2() {
ByteUtils.readUint16(new byte[2], 1); readUint16(new byte[2], 1);
} }
@Test @Test
public void testReadUint32() { public void testReadUint32() {
byte[] b = StringUtils.fromHexString("000000000000"); byte[] b = fromHexString("000000000000");
assertEquals(0, ByteUtils.readUint32(b, 1)); assertEquals(0, readUint32(b, 1));
b = StringUtils.fromHexString("000000000100"); b = fromHexString("000000000100");
assertEquals(1, ByteUtils.readUint32(b, 1)); assertEquals(1, readUint32(b, 1));
b = StringUtils.fromHexString("007FFFFFFF00"); b = fromHexString("007FFFFFFF00");
assertEquals(Integer.MAX_VALUE, ByteUtils.readUint32(b, 1)); assertEquals(Integer.MAX_VALUE, readUint32(b, 1));
b = StringUtils.fromHexString("00FFFFFFFF00"); b = fromHexString("00FFFFFFFF00");
assertEquals(4294967295L, ByteUtils.readUint32(b, 1)); assertEquals(4294967295L, readUint32(b, 1));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint32ValidatesArguments1() { public void testReadUint32ValidatesArguments1() {
ByteUtils.readUint32(new byte[3], 0); readUint32(new byte[3], 0);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint32ValidatesArguments2() { public void testReadUint32ValidatesArguments2() {
ByteUtils.readUint32(new byte[4], 1); readUint32(new byte[4], 1);
} }
@Test @Test
public void testReadUint64() { public void testReadUint64() {
byte[] b = StringUtils.fromHexString("00000000000000000000"); byte[] b = fromHexString("00000000000000000000");
assertEquals(0L, ByteUtils.readUint64(b, 1)); assertEquals(0L, readUint64(b, 1));
b = StringUtils.fromHexString("00000000000000000100"); b = fromHexString("00000000000000000100");
assertEquals(1L, ByteUtils.readUint64(b, 1)); assertEquals(1L, readUint64(b, 1));
b = StringUtils.fromHexString("007FFFFFFFFFFFFFFF00"); b = fromHexString("007FFFFFFFFFFFFFFF00");
assertEquals(Long.MAX_VALUE, ByteUtils.readUint64(b, 1)); assertEquals(Long.MAX_VALUE, readUint64(b, 1));
b = StringUtils.fromHexString("00800000000000000000"); b = fromHexString("00800000000000000000");
assertEquals(Long.MIN_VALUE, ByteUtils.readUint64(b, 1)); assertEquals(Long.MIN_VALUE, readUint64(b, 1));
b = StringUtils.fromHexString("00FFFFFFFFFFFFFFFF00"); b = fromHexString("00FFFFFFFFFFFFFFFF00");
assertEquals(-1L, ByteUtils.readUint64(b, 1)); assertEquals(-1L, readUint64(b, 1));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint64ValidatesArguments1() { public void testReadUint64ValidatesArguments1() {
ByteUtils.readUint64(new byte[7], 0); readUint64(new byte[7], 0);
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testReadUint64ValidatesArguments2() { public void testReadUint64ValidatesArguments2() {
ByteUtils.readUint64(new byte[8], 1); readUint64(new byte[8], 1);
} }
@Test @Test
public void testWriteUint16() { public void testWriteUint16() {
byte[] b = new byte[4]; byte[] b = new byte[4];
ByteUtils.writeUint16(0, b, 1); writeUint16(0, b, 1);
assertEquals("00000000", StringUtils.toHexString(b)); assertEquals("00000000", toHexString(b));
ByteUtils.writeUint16(1, b, 1); writeUint16(1, b, 1);
assertEquals("00000100", StringUtils.toHexString(b)); assertEquals("00000100", toHexString(b));
ByteUtils.writeUint16(Short.MAX_VALUE, b, 1); writeUint16(Short.MAX_VALUE, b, 1);
assertEquals("007FFF00", StringUtils.toHexString(b)); assertEquals("007FFF00", toHexString(b));
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1); writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
assertEquals("00FFFF00", StringUtils.toHexString(b)); assertEquals("00FFFF00", toHexString(b));
} }
@Test @Test
public void testWriteUint16ValidatesArguments() { public void testWriteUint16ValidatesArguments() {
try { try {
ByteUtils.writeUint16(0, new byte[1], 0); writeUint16(0, new byte[1], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint16(0, new byte[2], 1); writeUint16(0, new byte[2], 1);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint16(-1, new byte[2], 0); writeUint16(-1, new byte[2], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0); writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
@@ -122,38 +139,38 @@ public class ByteUtilsTest extends BrambleTestCase {
@Test @Test
public void testWriteUint32() { public void testWriteUint32() {
byte[] b = new byte[6]; byte[] b = new byte[6];
ByteUtils.writeUint32(0, b, 1); writeUint32(0, b, 1);
assertEquals("000000000000", StringUtils.toHexString(b)); assertEquals("000000000000", toHexString(b));
ByteUtils.writeUint32(1, b, 1); writeUint32(1, b, 1);
assertEquals("000000000100", StringUtils.toHexString(b)); assertEquals("000000000100", toHexString(b));
ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1); writeUint32(Integer.MAX_VALUE, b, 1);
assertEquals("007FFFFFFF00", StringUtils.toHexString(b)); assertEquals("007FFFFFFF00", toHexString(b));
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1); writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
assertEquals("00FFFFFFFF00", StringUtils.toHexString(b)); assertEquals("00FFFFFFFF00", toHexString(b));
} }
@Test @Test
public void testWriteUint32ValidatesArguments() { public void testWriteUint32ValidatesArguments() {
try { try {
ByteUtils.writeUint32(0, new byte[3], 0); writeUint32(0, new byte[3], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint32(0, new byte[4], 1); writeUint32(0, new byte[4], 1);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint32(-1, new byte[4], 0); writeUint32(-1, new byte[4], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0); writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
@@ -163,30 +180,30 @@ public class ByteUtilsTest extends BrambleTestCase {
@Test @Test
public void testWriteUint64() { public void testWriteUint64() {
byte[] b = new byte[10]; byte[] b = new byte[10];
ByteUtils.writeUint64(0, b, 1); writeUint64(0, b, 1);
assertEquals("00000000000000000000", StringUtils.toHexString(b)); assertEquals("00000000000000000000", toHexString(b));
ByteUtils.writeUint64(1, b, 1); writeUint64(1, b, 1);
assertEquals("00000000000000000100", StringUtils.toHexString(b)); assertEquals("00000000000000000100", toHexString(b));
ByteUtils.writeUint64(Long.MAX_VALUE, b, 1); writeUint64(Long.MAX_VALUE, b, 1);
assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b)); assertEquals("007FFFFFFFFFFFFFFF00", toHexString(b));
} }
@Test @Test
public void testWriteUint64ValidatesArguments() { public void testWriteUint64ValidatesArguments() {
try { try {
ByteUtils.writeUint64(0, new byte[7], 0); writeUint64(0, new byte[7], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint64(0, new byte[8], 1); writeUint64(0, new byte[8], 1);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
} }
try { try {
ByteUtils.writeUint64(-1, new byte[8], 0); writeUint64(-1, new byte[8], 0);
fail(); fail();
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
// Expected // Expected
@@ -194,17 +211,170 @@ public class ByteUtilsTest extends BrambleTestCase {
} }
@Test @Test
public void testReadUint() { public void testGetVarIntBytesToWrite() {
byte[] b = new byte[1]; assertEquals(1, getVarIntBytes(0));
b[0] = (byte) 128; assertEquals(1, getVarIntBytes(0x7F)); // Max 7-bit int
for (int i = 0; i < 8; i++) { assertEquals(2, getVarIntBytes(0x7F + 1));
assertEquals(1 << i, ByteUtils.readUint(b, i + 1)); assertEquals(2, getVarIntBytes(0x3FFF)); // Max 14-bit int
} assertEquals(3, getVarIntBytes(0x3FFF + 1));
b = new byte[2]; assertEquals(3, getVarIntBytes(0x1FFFFF)); // Max 21-bit int
for (int i = 0; i < 65535; i++) { assertEquals(4, getVarIntBytes(0x1FFFFF + 1));
ByteUtils.writeUint16(i, b, 0); assertEquals(4, getVarIntBytes(0xFFFFFFF)); // Max 28-bit int
assertEquals(i, ByteUtils.readUint(b, 16)); assertEquals(5, getVarIntBytes(0xFFFFFFF + 1));
assertEquals(i >> 1, ByteUtils.readUint(b, 15)); assertEquals(5, getVarIntBytes(0x7FFFFFFFFL)); // Max 35-bit int
assertEquals(6, getVarIntBytes(0x7FFFFFFFFL + 1));
assertEquals(6, getVarIntBytes(0x3FFFFFFFFFFL)); // Max 42-bit int
assertEquals(7, getVarIntBytes(0x3FFFFFFFFFFL + 1));
assertEquals(7, getVarIntBytes(0x1FFFFFFFFFFFFL)); // Max 49-bit int
assertEquals(8, getVarIntBytes(0x1FFFFFFFFFFFFL + 1));
assertEquals(8, getVarIntBytes(0xFFFFFFFFFFFFFFL)); // Max 56-bit int
assertEquals(9, getVarIntBytes(0xFFFFFFFFFFFFFFL + 1));
assertEquals(9, getVarIntBytes(0x7FFFFFFFFFFFFFFFL)); // Max 63-bit int
assertEquals(MAX_VARINT_BYTES, getVarIntBytes(Long.MAX_VALUE)); // Same
}
@Test
public void testWriteVarInt() {
testWriteVarInt(0, 1, "00");
testWriteVarInt(1, 1, "01");
testWriteVarInt(0x7F, 1, "7F"); // Max 7-bit int
testWriteVarInt(0x7F + 1, 2, "8100");
testWriteVarInt(0x3FFF, 2, "FF7F"); // Max 14-bit int
testWriteVarInt(0x3FFF + 1, 3, "818000");
testWriteVarInt(0x1FFFFF, 3, "FFFF7F"); // Max 21-bit int
testWriteVarInt(0x1FFFFF + 1, 4, "81808000");
testWriteVarInt(0xFFFFFFF, 4, "FFFFFF7F"); // Max 28-bit int
testWriteVarInt(0xFFFFFFF + 1, 5, "8180808000");
testWriteVarInt(0x7FFFFFFFFL, 5, "FFFFFFFF7F"); // Max 35-bit int
testWriteVarInt(0x7FFFFFFFFL + 1, 6, "818080808000");
testWriteVarInt(0x3FFFFFFFFFFL, 6, "FFFFFFFFFF7F"); // Max 42-bit int
testWriteVarInt(0x3FFFFFFFFFFL + 1, 7, "81808080808000");
testWriteVarInt(0x1FFFFFFFFFFFFL, 7, "FFFFFFFFFFFF7F"); // Max 49
testWriteVarInt(0x1FFFFFFFFFFFFL + 1, 8, "8180808080808000");
testWriteVarInt(0xFFFFFFFFFFFFFFL, 8, "FFFFFFFFFFFFFF7F"); // Max 56
testWriteVarInt(0xFFFFFFFFFFFFFFL + 1, 9, "818080808080808000");
testWriteVarInt(0x7FFFFFFFFFFFFFFFL, 9, "FFFFFFFFFFFFFFFF7F"); // Max 63
testWriteVarInt(Long.MAX_VALUE, MAX_VARINT_BYTES, "FFFFFFFFFFFFFFFF7F");
}
private void testWriteVarInt(long src, int len, String destHex) {
byte[] dest = new byte[9];
assertEquals(len, writeVarInt(src, dest, 0));
assertEquals(destHex, toHexString(dest).substring(0, len * 2));
}
@Test
public void testGetVarIntBytesToRead() throws FormatException {
testGetVarIntBytesToRead(1, "00", 0);
testGetVarIntBytesToRead(1, "01", 0);
testGetVarIntBytesToRead(1, "7F", 0); // Max 7-bit int
testGetVarIntBytesToRead(2, "8100", 0);
testGetVarIntBytesToRead(2, "FF7F", 0); // Max 14-bit int
testGetVarIntBytesToRead(3, "818000", 0);
testGetVarIntBytesToRead(3, "FFFF7F", 0); // Max 21-bit int
testGetVarIntBytesToRead(4, "81808000", 0);
testGetVarIntBytesToRead(4, "FFFFFF7F", 0); // Max 28-bit int
testGetVarIntBytesToRead(5, "8180808000", 0);
testGetVarIntBytesToRead(5, "FFFFFFFF7F", 0); // Max 35-bit int
testGetVarIntBytesToRead(6, "818080808000", 0);
testGetVarIntBytesToRead(6, "FFFFFFFFFF7F", 0); // Max 42-bit int
testGetVarIntBytesToRead(7, "81808080808000", 0);
testGetVarIntBytesToRead(7, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
testGetVarIntBytesToRead(8, "8180808080808000", 0);
testGetVarIntBytesToRead(8, "FFFFFFFFFFFFFF7F", 0); // Max 56-bit int
testGetVarIntBytesToRead(9, "818080808080808000", 0);
testGetVarIntBytesToRead(9, "FFFFFFFFFFFFFFFF7F", 0); // Max 63-bit int
// Start at offset, ignore trailing data
testGetVarIntBytesToRead(1, "FF0000", 1);
testGetVarIntBytesToRead(9, "00FFFFFFFFFFFFFFFF7F00", 1);
}
private void testGetVarIntBytesToRead(int len, String srcHex, int offset)
throws FormatException {
assertEquals(len, getVarIntBytes(fromHexString(srcHex), offset));
}
@Test(expected = FormatException.class)
public void testGetVarIntBytesToReadThrowsExceptionAtEndOfInput()
throws FormatException {
byte[] src = new byte[MAX_VARINT_BYTES - 1];
fill(src, (byte) 0xFF);
// Reaches end of input without finding lowered continuation flag
getVarIntBytes(src, 0);
}
@Test(expected = FormatException.class)
public void testGetVarIntBytesToReadThrowsExceptionAfterNineBytes()
throws FormatException {
byte[] src = new byte[MAX_VARINT_BYTES];
fill(src, (byte) 0xFF);
// Reaches max length without finding lowered continuation flag
getVarIntBytes(src, 0);
}
@Test
public void testReadVarInt() throws FormatException {
testReadVarInt(0, "00", 0);
testReadVarInt(1, "01", 0);
testReadVarInt(0x7F, "7F", 0); // Max 7-bit int
testReadVarInt(0x7F + 1, "8100", 0);
testReadVarInt(0x3FFF, "FF7F", 0); // Max 14-bit int
testReadVarInt(0x3FFF + 1, "818000", 0);
testReadVarInt(0x1FFFFF, "FFFF7F", 0); // Max 21-bit int
testReadVarInt(0x1FFFFF + 1, "81808000", 0);
testReadVarInt(0xFFFFFFF, "FFFFFF7F", 0); // Max 28-bit int
testReadVarInt(0xFFFFFFF + 1, "8180808000", 0);
testReadVarInt(0x7FFFFFFFFL, "FFFFFFFF7F", 0); // Max 35-bit int
testReadVarInt(0x7FFFFFFFFL + 1, "818080808000", 0);
testReadVarInt(0x3FFFFFFFFFFL, "FFFFFFFFFF7F", 0); // Max 42-bit int
testReadVarInt(0x3FFFFFFFFFFL + 1, "81808080808000", 0);
testReadVarInt(0x1FFFFFFFFFFFFL, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
testReadVarInt(0x1FFFFFFFFFFFFL + 1, "8180808080808000", 0);
testReadVarInt(0xFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFF7F", 0); // Max 56
testReadVarInt(0xFFFFFFFFFFFFFFL + 1, "818080808080808000", 0);
testReadVarInt(0x7FFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFFFF7F", 0); // Max 63
testReadVarInt(Long.MAX_VALUE, "FFFFFFFFFFFFFFFF7F", 0);
// Start at offset, ignore trailing data
testReadVarInt(0, "FF0000", 1);
testReadVarInt(Long.MAX_VALUE, "00FFFFFFFFFFFFFFFF7F00", 1);
}
private void testReadVarInt(long dest, String srcHex, int offset)
throws FormatException {
assertEquals(dest, readVarInt(fromHexString(srcHex), offset));
}
@Test(expected = FormatException.class)
public void testReadVarIntThrowsExceptionAtEndOfInput()
throws FormatException {
byte[] src = new byte[MAX_VARINT_BYTES - 1];
fill(src, (byte) 0xFF);
// Reaches end of input without finding lowered continuation flag
readVarInt(src, 0);
}
@Test(expected = FormatException.class)
public void testReadVarIntThrowsExceptionAfterNineBytes()
throws FormatException {
byte[] src = new byte[MAX_VARINT_BYTES];
fill(src, (byte) 0xFF);
// Reaches max length without finding lowered continuation flag
readVarInt(src, 0);
}
@Test
public void testWriteAndReadVarInt() throws FormatException {
Random random = new Random();
int padding = 10;
byte[] buf = new byte[MAX_VARINT_BYTES + padding];
for (int i = 0; i < 1000; i++) {
long src = random.nextLong() & 0x7FFFFFFFFFFFFFFFL; // Non-negative
int offset = random.nextInt(padding);
int len = getVarIntBytes(src);
assertEquals(len, writeVarInt(src, buf, offset));
assertEquals(len, getVarIntBytes(buf, offset));
assertEquals(src, readVarInt(buf, offset));
fill(buf, (byte) 0);
} }
} }
} }

View File

@@ -1,20 +1,21 @@
package org.briarproject.bramble.lifecycle; package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Module @Module
public class DesktopLifecycleModule extends LifecycleModule { public class DesktopLifecycleModule extends LifecycleModule {
@Provides @Provides
@Singleton @Singleton
ShutdownManager provideDesktopShutdownManager() { ShutdownManager provideDesktopShutdownManager() {
if (OsUtils.isWindows()) return new WindowsShutdownManagerImpl(); if (isWindows()) return new WindowsShutdownManagerImpl();
else return new ShutdownManagerImpl(); else return new ShutdownManagerImpl();
} }
} }

View File

@@ -15,7 +15,6 @@ import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper; import com.sun.jna.win32.W32APITypeMapper;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.OsUtils;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@@ -29,6 +28,7 @@ import static com.sun.jna.Library.OPTION_FUNCTION_MAPPER;
import static com.sun.jna.Library.OPTION_TYPE_MAPPER; import static com.sun.jna.Library.OPTION_TYPE_MAPPER;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -71,7 +71,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
// Locking: lock // Locking: lock
private void initialise() { private void initialise() {
if (OsUtils.isWindows()) { if (isWindows()) {
new EventLoop().start(); new EventLoop().start();
} else { } else {
LOG.warning("Windows shutdown manager used on non-Windows OS"); LOG.warning("Windows shutdown manager used on non-Windows OS");
@@ -111,7 +111,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
public void run() { public void run() {
try { try {
// Load user32.dll // Load user32.dll
User32 user32 = (User32) Native.loadLibrary("user32", User32 user32 = Native.loadLibrary("user32",
User32.class, options); User32.class, options);
// Create a callback to handle the WM_QUERYENDSESSION message // Create a callback to handle the WM_QUERYENDSESSION message
WindowProc proc = (hwnd, msg, wp, lp) -> { WindowProc proc = (hwnd, msg, wp, lp) -> {

View File

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider; import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.CodeSource; import java.security.CodeSource;
@@ -19,9 +18,9 @@ import java.util.concurrent.Executor;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@NotNullByDefault @NotNullByDefault
class LinuxTorPlugin extends TorPlugin { abstract class JavaTorPlugin extends TorPlugin {
LinuxTorPlugin(Executor ioExecutor, NetworkManager networkManager, JavaTorPlugin(Executor ioExecutor, NetworkManager networkManager,
LocationUtils locationUtils, SocketFactory torSocketFactory, LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider, Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, CircumventionProvider circumventionProvider,
@@ -34,17 +33,6 @@ class LinuxTorPlugin extends TorPlugin {
torDirectory); torDirectory);
} }
@Override
protected int getProcessId() {
try {
// Java 9: ProcessHandle.current().pid()
return Integer.parseInt(
new File("/proc/self").getCanonicalFile().getName());
} catch (IOException e) {
throw new AssertionError(e);
}
}
@Override @Override
protected long getLastUpdateTime() { protected long getLastUpdateTime() {
CodeSource codeSource = CodeSource codeSource =
@@ -58,5 +46,4 @@ class LinuxTorPlugin extends TorPlugin {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
} }

View File

@@ -0,0 +1,47 @@
package org.briarproject.bramble.plugin.tor;
import com.sun.jna.Library;
import com.sun.jna.Native;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.util.concurrent.Executor;
import javax.net.SocketFactory;
@NotNullByDefault
class UnixTorPlugin extends JavaTorPlugin {
UnixTorPlugin(Executor ioExecutor, NetworkManager networkManager,
LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Backoff backoff,
DuplexPluginCallback callback, String architecture, int maxLatency,
int maxIdleTime, File torDirectory) {
super(ioExecutor, networkManager, locationUtils, torSocketFactory,
clock, resourceProvider, circumventionProvider, batteryManager,
backoff, callback, architecture, maxLatency, maxIdleTime,
torDirectory);
}
@Override
protected int getProcessId() {
return CLibrary.INSTANCE.getpid();
}
private interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("c", CLibrary.class);
int getpid();
}
}

View File

@@ -22,14 +22,15 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux; import static org.briarproject.bramble.util.OsUtils.isLinux;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class LinuxTorPluginFactory implements DuplexPluginFactory { public class UnixTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(LinuxTorPluginFactory.class.getName()); getLogger(UnixTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
@@ -49,7 +50,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
private final Clock clock; private final Clock clock;
private final File torDirectory; private final File torDirectory;
public LinuxTorPluginFactory(Executor ioExecutor, public UnixTorPluginFactory(Executor ioExecutor,
NetworkManager networkManager, LocationUtils locationUtils, NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory, EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider, BackoffFactory backoffFactory, ResourceProvider resourceProvider,
@@ -95,7 +96,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
LinuxTorPlugin plugin = new LinuxTorPlugin(ioExecutor, networkManager, UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager,
locationUtils, torSocketFactory, clock, resourceProvider, locationUtils, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff, callback, circumventionProvider, batteryManager, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory); architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory);

View File

@@ -1,19 +1,24 @@
package org.briarproject.bramble.system; package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
@Module @Module
public class DesktopSecureRandomModule { public class DesktopSecureRandomModule {
@Provides @Provides
@Singleton @Singleton
SecureRandomProvider provideSecureRandomProvider() { SecureRandomProvider provideSecureRandomProvider() {
return OsUtils.isLinux() ? new LinuxSecureRandomProvider() : null; if (isLinux() || isMac())
return new UnixSecureRandomProvider();
// TODO: Create a secure random provider for Windows
throw new UnsupportedOperationException();
} }
} }

View File

@@ -70,7 +70,7 @@ public class BridgeTest extends BrambleTestCase {
private final File torDir = getTestDirectory(); private final File torDir = getTestDirectory();
private final String bridge; private final String bridge;
private LinuxTorPluginFactory factory; private UnixTorPluginFactory factory;
public BridgeTest(String bridge) { public BridgeTest(String bridge) {
this.bridge = bridge; this.bridge = bridge;
@@ -108,7 +108,7 @@ public class BridgeTest extends BrambleTestCase {
return singletonList(bridge); return singletonList(bridge);
} }
}; };
factory = new LinuxTorPluginFactory(ioExecutor, networkManager, factory = new UnixTorPluginFactory(ioExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory, locationUtils, eventBus, torSocketFactory, backoffFactory,
resourceProvider, bridgeProvider, batteryManager, clock, resourceProvider, bridgeProvider, batteryManager, clock,
torDir); torDir);
@@ -124,7 +124,7 @@ public class BridgeTest extends BrambleTestCase {
DuplexPlugin duplexPlugin = DuplexPlugin duplexPlugin =
factory.createPlugin(new TorPluginCallBack()); factory.createPlugin(new TorPluginCallBack());
assertNotNull(duplexPlugin); assertNotNull(duplexPlugin);
LinuxTorPlugin plugin = (LinuxTorPlugin) duplexPlugin; UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
LOG.warning("Testing " + bridge); LOG.warning("Testing " + bridge);
try { try {

View File

@@ -104,6 +104,7 @@ dependencies {
} }
implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-annotations:$supportVersion" implementation "com.android.support:support-annotations:$supportVersion"
implementation "com.android.support:exifinterface:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "android.arch.lifecycle:extensions:1.1.1" implementation "android.arch.lifecycle:extensions:1.1.1"
@@ -117,8 +118,15 @@ dependencies {
implementation 'com.google.zxing:core:3.3.3' implementation 'com.google.zxing:core:3.3.3'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4' implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
implementation 'com.vanniktech:emoji-google:0.5.1' implementation 'com.vanniktech:emoji-google:0.5.1'
def glideVersion = '4.8.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
}
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'

View File

@@ -30,3 +30,6 @@
# Emoji # Emoji
-keep class com.vanniktech.emoji.** -keep class com.vanniktech.emoji.**
# Glide
-dontwarn com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
@@ -12,6 +13,7 @@ public class BriarTestComponentApplication extends BriarApplicationImpl {
// We need to load the eager singletons directly after making the // We need to load the eager singletons directly after making the
// dependency graphs // dependency graphs
BrambleCoreModule.initEagerSingletons(component); BrambleCoreModule.initEagerSingletons(component);
BrambleAndroidModule.initEagerSingletons(component);
BriarCoreModule.initEagerSingletons(component); BriarCoreModule.initEagerSingletons(component);
AndroidEagerSingletons.initEagerSingletons(component); AndroidEagerSingletons.initEagerSingletons(component);
return component; return component;

View File

@@ -7,7 +7,6 @@
<uses-feature android:name="android.hardware.camera" android:required="false"/> <uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false" /> <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
@@ -18,6 +17,7 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" /> <uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
@@ -113,6 +113,15 @@
/> />
</activity> </activity>
<activity
android:name=".android.conversation.ImageActivity"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
android:theme="@style/BriarTheme.Transparent.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
</activity>
<activity <activity
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity" android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title" android:label="@string/groups_create_group_title"

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
@@ -28,6 +29,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender; import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextInputView;
@@ -39,11 +41,11 @@ import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
@@ -68,7 +70,8 @@ import dagger.Component;
AppModule.class AppModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
extends BrambleCoreEagerSingletons, BriarCoreEagerSingletons { extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
BriarCoreEagerSingletons {
// Exposed objects // Exposed objects
@CryptoExecutor @CryptoExecutor
@@ -168,6 +171,8 @@ public interface AndroidComponent
void inject(TextInputView textInputView); void inject(TextInputView textInputView);
void inject(BriarModelLoader briarModelLoader);
// Eager singleton load // Eager singleton load
void inject(AppModule.EagerSingletons init); void inject(AppModule.EagerSingletons init);
} }

View File

@@ -66,10 +66,14 @@ public class AppModule {
@Inject @Inject
AndroidNotificationManager androidNotificationManager; AndroidNotificationManager androidNotificationManager;
@Inject @Inject
ScreenFilterMonitor screenFilterMonitor;
@Inject
NetworkUsageLogger networkUsageLogger; NetworkUsageLogger networkUsageLogger;
@Inject @Inject
DozeWatchdog dozeWatchdog; DozeWatchdog dozeWatchdog;
@Inject @Inject
LockManager lockManager;
@Inject
RecentEmoji recentEmoji; RecentEmoji recentEmoji;
} }

View File

@@ -15,6 +15,7 @@ import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.acra.ACRA; import org.acra.ACRA;
import org.acra.ReportingInteractionMode; import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes; import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -124,6 +125,7 @@ public class BriarApplicationImpl extends Application
// We need to load the eager singletons directly after making the // We need to load the eager singletons directly after making the
// dependency graphs // dependency graphs
BrambleCoreModule.initEagerSingletons(androidComponent); BrambleCoreModule.initEagerSingletons(androidComponent);
BrambleAndroidModule.initEagerSingletons(androidComponent);
BriarCoreModule.initEagerSingletons(androidComponent); BriarCoreModule.initEagerSingletons(androidComponent);
AndroidEagerSingletons.initEagerSingletons(androidComponent); AndroidEagerSingletons.initEagerSingletons(androidComponent);
return androidComponent; return androidComponent;

View File

@@ -15,10 +15,11 @@ import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity; import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity; import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity; import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.contact.ContactListFragment; import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule; import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity;
import org.briarproject.briar.android.forum.CreateForumActivity; import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment; import org.briarproject.briar.android.forum.ForumListFragment;
@@ -110,6 +111,8 @@ public interface ActivityComponent {
void inject(ConversationActivity activity); void inject(ConversationActivity activity);
void inject(ImageActivity activity);
void inject(ForumInvitationActivity activity); void inject(ForumInvitationActivity activity);
void inject(BlogInvitationActivity activity); void inject(BlogInvitationActivity activity);

View File

@@ -2,12 +2,11 @@ package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.RequiresApi;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition; import android.transition.Transition;
import android.view.Gravity;
import android.view.Window; import android.view.Window;
import android.widget.CheckBox; import android.widget.CheckBox;
@@ -33,6 +32,7 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent; import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7; import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@@ -111,21 +111,28 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop(); lockManager.onActivityStop();
} }
public void setSceneTransitionAnimation() { /**
if (SDK_INT < 21) return; * Sets the transition animations.
* @param enterTransition used to move views into initial positions
* @param exitTransition used to move views out when starting a <b>new</b> activity.
* @param returnTransition used when window is closing, because the activity is finishing.
*/
@RequiresApi(api = 21)
public void setSceneTransitionAnimation(
@Nullable Transition enterTransition,
@Nullable Transition exitTransition,
@Nullable Transition returnTransition) {
// workaround for #1007 // workaround for #1007
if (isSamsung7()) { if (isSamsung7()) {
return; return;
} }
Transition slide = new Slide(Gravity.RIGHT); if (enterTransition != null) excludeSystemUi(enterTransition);
slide.excludeTarget(android.R.id.statusBarBackground, true); if (exitTransition != null) excludeSystemUi(exitTransition);
slide.excludeTarget(android.R.id.navigationBarBackground, true); if (returnTransition != null) excludeSystemUi(returnTransition);
Window window = getWindow(); Window window = getWindow();
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); window.setEnterTransition(enterTransition);
window.setExitTransition(slide); window.setExitTransition(exitTransition);
window.setEnterTransition(slide); window.setReturnTransition(returnTransition);
window.setTransitionBackgroundFadeDuration(getResources()
.getInteger(android.R.integer.config_longAnimTime));
} }
/** /**

View File

@@ -18,7 +18,6 @@ public class ReblogActivity extends BriarActivity implements
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
setSceneTransitionAnimation();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Intent intent = getIntent(); Intent intent = getIntent();

View File

@@ -0,0 +1,209 @@
package org.briarproject.briar.android.conversation;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.support.annotation.Nullable;
import android.support.media.ExifInterface;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_270;
import static android.support.media.ExifInterface.ORIENTATION_ROTATE_90;
import static android.support.media.ExifInterface.ORIENTATION_TRANSPOSE;
import static android.support.media.ExifInterface.ORIENTATION_TRANSVERSE;
import static android.support.media.ExifInterface.TAG_IMAGE_LENGTH;
import static android.support.media.ExifInterface.TAG_IMAGE_WIDTH;
import static android.support.media.ExifInterface.TAG_ORIENTATION;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
class AttachmentController {
private static final Logger LOG =
getLogger(AttachmentController.class.getName());
private final MessagingManager messagingManager;
private final int defaultSize;
private final int minWidth, maxWidth;
private final int minHeight, maxHeight;
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>();
AttachmentController(MessagingManager messagingManager, Resources res) {
this.messagingManager = messagingManager;
defaultSize =
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
minWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_width);
maxWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_width);
minHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_height);
maxHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_height);
}
void put(MessageId messageId, List<AttachmentItem> attachments) {
attachmentCache.put(messageId, attachments);
}
@Nullable
List<AttachmentItem> get(MessageId messageId) {
return attachmentCache.get(messageId);
}
@DatabaseExecutor
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
List<AttachmentHeader> headers) throws DbException {
long start = now();
List<Pair<AttachmentHeader, Attachment>> attachments =
new ArrayList<>(headers.size());
for (AttachmentHeader h : headers) {
Attachment a =
messagingManager.getAttachment(h.getMessageId());
attachments.add(new Pair<>(h, a));
}
logDuration(LOG, "Loading attachment", start);
return attachments;
}
List<AttachmentItem> getAttachmentItems(
List<Pair<AttachmentHeader, Attachment>> attachments) {
List<AttachmentItem> items = new ArrayList<>(attachments.size());
for (Pair<AttachmentHeader, Attachment> a : attachments) {
AttachmentItem item =
getAttachmentItem(a.getFirst(), a.getSecond());
items.add(item);
}
return items;
}
private AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a) {
MessageId messageId = h.getMessageId();
Size size = new Size();
InputStream is = a.getStream();
is.mark(Integer.MAX_VALUE);
try {
// use exif to get size
if (h.getContentType().equals("image/jpeg")) {
size = getSizeFromExif(is);
}
} catch (IOException e) {
logException(LOG, WARNING, e);
}
try {
// use BitmapFactory to get size
if (size.error) {
is.reset();
size = getSizeFromBitmap(is);
}
} catch (IOException e) {
logException(LOG, WARNING, e);
} finally {
tryToClose(is, LOG, WARNING);
}
// calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize);
if (!size.error) {
thumbnailSize = getThumbnailSize(size.width, size.height);
}
return new AttachmentItem(messageId, size.width, size.height,
thumbnailSize.width, thumbnailSize.height, size.error);
}
/**
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
*/
private static Size getSizeFromExif(InputStream is)
throws IOException {
ExifInterface exif = new ExifInterface(is);
// these can return 0 independent of default value
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
int height = exif.getAttributeInt(TAG_IMAGE_LENGTH, 0);
if (width == 0 || height == 0) return new Size();
int orientation = exif.getAttributeInt(TAG_ORIENTATION, 0);
if (orientation == ORIENTATION_ROTATE_90 ||
orientation == ORIENTATION_ROTATE_270 ||
orientation == ORIENTATION_TRANSVERSE ||
orientation == ORIENTATION_TRANSPOSE) {
//noinspection SuspiciousNameCombination
return new Size(height, width);
}
return new Size(width, height);
}
/**
* Gets the size of any image {@link InputStream}.
*/
private static Size getSizeFromBitmap(InputStream is) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
if (options.outWidth < 1 || options.outHeight < 1)
return new Size();
return new Size(options.outWidth, options.outHeight);
}
private Size getThumbnailSize(int width, int height) {
float widthPercentage = maxWidth / (float) width;
float heightPercentage = maxHeight / (float) height;
float scaleFactor = Math.min(widthPercentage, heightPercentage);
if (scaleFactor > 1) scaleFactor = 1f;
int thumbnailWidth = (int) (width * scaleFactor);
int thumbnailHeight = (int) (height * scaleFactor);
if (thumbnailWidth < minWidth || thumbnailHeight < minHeight) {
widthPercentage = minWidth / (float) width;
heightPercentage = minHeight / (float) height;
scaleFactor = Math.max(widthPercentage, heightPercentage);
thumbnailWidth = (int) (width * scaleFactor);
thumbnailHeight = (int) (height * scaleFactor);
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
}
return new Size(thumbnailWidth, thumbnailHeight);
}
private static class Size {
private final int width;
private final int height;
private final boolean error;
private Size(int width, int height) {
this.width = width;
this.height = height;
this.error = false;
}
private Size() {
this.width = 0;
this.height = 0;
this.error = true;
}
}
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.briar.android.conversation;
import android.os.Parcel;
import android.os.Parcelable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class AttachmentItem implements Parcelable {
private final MessageId messageId;
private final int width, height;
private final int thumbnailWidth, thumbnailHeight;
private final boolean hasError;
public static final Creator<AttachmentItem> CREATOR =
new Creator<AttachmentItem>() {
@Override
public AttachmentItem createFromParcel(Parcel in) {
return new AttachmentItem(in);
}
@Override
public AttachmentItem[] newArray(int size) {
return new AttachmentItem[size];
}
};
AttachmentItem(MessageId messageId, int width, int height,
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
this.messageId = messageId;
this.width = width;
this.height = height;
this.thumbnailWidth = thumbnailWidth;
this.thumbnailHeight = thumbnailHeight;
this.hasError = hasError;
}
protected AttachmentItem(Parcel in) {
byte[] messageIdByte = new byte[MessageId.LENGTH];
in.readByteArray(messageIdByte);
messageId = new MessageId(messageIdByte);
width = in.readInt();
height = in.readInt();
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
hasError = in.readByte() != 0;
}
public MessageId getMessageId() {
return messageId;
}
int getWidth() {
return width;
}
int getHeight() {
return height;
}
int getThumbnailWidth() {
return thumbnailWidth;
}
int getThumbnailHeight() {
return thumbnailHeight;
}
boolean hasError() {
return hasError;
}
// TODO use counter instead, because in theory one attachment can appear in more than one messages
String getTransitionName() {
return String.valueOf(messageId.hashCode());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(messageId.getBytes());
dest.writeInt(width);
dest.writeInt(height);
dest.writeInt(thumbnailWidth);
dest.writeInt(thumbnailHeight);
dest.writeByte((byte) (hasError ? 1 : 0));
}
}

View File

@@ -9,11 +9,15 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.widget.ActionMenuView; import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -24,6 +28,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
@@ -51,6 +56,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogActivity; 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.conversation.ConversationVisitor.TextCache;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
@@ -69,6 +75,8 @@ import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent; import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager; import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage; import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory; import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -92,10 +100,14 @@ import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.setTransitionName; import static android.support.v4.view.ViewCompat.setTransitionName;
import static android.support.v7.util.SortedList.INVALID_POSITION; import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.view.Gravity.END;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -103,6 +115,9 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT;
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName; import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName; import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
@@ -115,7 +130,7 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity public class ConversationActivity extends BriarActivity
implements EventListener, ConversationListener, TextInputListener, implements EventListener, ConversationListener, TextInputListener,
TextCache { TextCache, AttachmentCache {
public static final String CONTACT_ID = "briar.CONTACT_ID"; public static final String CONTACT_ID = "briar.CONTACT_ID";
@@ -133,6 +148,7 @@ public class ConversationActivity extends BriarActivity
Executor cryptoExecutor; Executor cryptoExecutor;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>(); private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private AttachmentController attachmentController;
private ConversationViewModel viewModel; private ConversationViewModel viewModel;
private ConversationVisitor visitor; private ConversationVisitor visitor;
@@ -142,6 +158,7 @@ public class ConversationActivity extends BriarActivity
private ImageView toolbarStatus; private ImageView toolbarStatus;
private TextView toolbarTitle; private TextView toolbarTitle;
private BriarRecyclerView list; private BriarRecyclerView list;
private LinearLayoutManager layoutManager;
private TextInputView textInputView; private TextInputView textInputView;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@@ -179,7 +196,10 @@ public class ConversationActivity extends BriarActivity
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
setSceneTransitionAnimation(); if (SDK_INT >= 21) {
Transition slide = new Slide(END);
setSceneTransitionAnimation(slide, null, slide);
}
super.onCreate(state); super.onCreate(state);
Intent i = getIntent(); Intent i = getIntent();
@@ -190,6 +210,7 @@ public class ConversationActivity extends BriarActivity
viewModel = ViewModelProviders.of(this, viewModelFactory) viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class); .get(ConversationViewModel.class);
viewModel.setContactId(contactId); viewModel.setContactId(contactId);
attachmentController = viewModel.getAttachmentController();
setContentView(R.layout.activity_conversation); setContentView(R.layout.activity_conversation);
@@ -216,11 +237,12 @@ public class ConversationActivity extends BriarActivity
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId)); setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId)); setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, visitor = new ConversationVisitor(this, this, this,
viewModel.getContactDisplayName()); viewModel.getContactDisplayName());
adapter = new ConversationAdapter(this, this); adapter = new ConversationAdapter(this, this);
list = findViewById(R.id.conversationView); list = findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this)); layoutManager = new LinearLayoutManager(this);
list.setLayoutManager(layoutManager);
list.setAdapter(adapter); list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_private_messages)); list.setEmptyText(getString(R.string.no_private_messages));
@@ -330,7 +352,43 @@ public class ConversationActivity extends BriarActivity
Collection<ConversationMessageHeader> headers = Collection<ConversationMessageHeader> headers =
conversationManager.getMessageHeaders(contactId); conversationManager.getMessageHeaders(contactId);
logDuration(LOG, "Loading messages", start); logDuration(LOG, "Loading messages", start);
displayMessages(revision, headers); // Sort headers by timestamp in *descending* order
List<ConversationMessageHeader> sorted =
new ArrayList<>(headers);
sort(sorted, (a, b) ->
Long.compare(b.getTimestamp(), a.getTimestamp()));
if (!sorted.isEmpty()) {
// If the latest header is a private message, eagerly load
// its text so we can set the scroll position correctly
ConversationMessageHeader latest = sorted.get(0);
if (latest instanceof PrivateMessageHeader) {
MessageId id = latest.getId();
PrivateMessageHeader h = (PrivateMessageHeader) latest;
if (h.hasText()) {
String text = textCache.get(id);
if (text == null) {
LOG.info(
"Eagerly loading text of latest message");
text = messagingManager.getMessageText(id);
textCache.put(id, text);
}
}
if (!h.getAttachmentHeaders().isEmpty()) {
List<AttachmentItem> items =
attachmentController.get(id);
if (items == null) {
LOG.info(
"Eagerly loading image size for latest message");
items = attachmentController.getAttachmentItems(
attachmentController
.getMessageAttachments(
h.getAttachmentHeaders()));
attachmentController.put(id, items);
}
}
}
}
displayMessages(revision, sorted);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } catch (DbException e) {
@@ -387,16 +445,44 @@ public class ConversationActivity extends BriarActivity
private void displayMessageText(MessageId m, String text) { private void displayMessageText(MessageId m, String text) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
textCache.put(m, text); textCache.put(m, text);
SparseArray<ConversationMessageItem> messages = Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItems(); adapter.getMessageItem(m);
for (int i = 0; i < messages.size(); i++) { if (pair != null) {
ConversationItem item = messages.valueAt(i); pair.getSecond().setText(text);
if (item.getId().equals(m)) { boolean bottom = adapter.isScrolledToBottom(layoutManager);
item.setText(text); adapter.notifyItemChanged(pair.getFirst());
adapter.notifyItemChanged(messages.keyAt(i)); if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
list.scrollToPosition(adapter.getItemCount() - 1); }
return; });
} }
private void loadMessageAttachments(MessageId messageId,
List<AttachmentHeader> headers) {
runOnDbThread(() -> {
try {
List<Pair<AttachmentHeader, Attachment>> attachments =
attachmentController.getMessageAttachments(headers);
// TODO move getting the items off to the IoExecutor
List<AttachmentItem> items =
attachmentController.getAttachmentItems(attachments);
displayMessageAttachments(messageId, items);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayMessageAttachments(MessageId m,
List<AttachmentItem> items) {
runOnUiThreadUnlessDestroyed(() -> {
attachmentController.put(m, items);
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
pair.getSecond().setAttachments(items);
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst());
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
} }
}); });
} }
@@ -445,10 +531,10 @@ public class ConversationActivity extends BriarActivity
private void addConversationItem(ConversationItem item) { private void addConversationItem(ConversationItem item) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.incrementRevision(); adapter.incrementRevision();
adapter.add(item); adapter.add(item);
// Scroll to the bottom if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
list.scrollToPosition(adapter.getItemCount() - 1);
}); });
} }
@@ -729,6 +815,31 @@ public class ConversationActivity extends BriarActivity
startActivity(i); startActivity(i);
} }
@Override
public void onAttachmentClicked(View view,
ConversationMessageItem messageItem, AttachmentItem item) {
String name;
if (messageItem.isIncoming()) {
// must be available when items are being displayed
name = viewModel.getContactDisplayName().getValue();
} else {
name = getString(R.string.you);
}
Intent i = new Intent(this, ImageActivity.class);
i.putExtra(ATTACHMENT, item);
i.putExtra(NAME, name);
i.putExtra(DATE, messageItem.getTime());
if (SDK_INT >= 23) {
String transitionName = item.getTransitionName();
ActivityOptionsCompat options =
makeSceneTransitionAnimation(this, view, transitionName);
ActivityCompat.startActivity(this, i, options.toBundle());
} else {
// work-around for android bug #224270
startActivity(i);
}
}
@DatabaseExecutor @DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId, private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException { boolean accept, long time) throws DbException {
@@ -761,4 +872,16 @@ public class ConversationActivity extends BriarActivity
if (text == null) loadMessageText(m); if (text == null) loadMessageText(m);
return text; return text;
} }
@Override
public List<AttachmentItem> getAttachmentItems(MessageId m,
List<AttachmentHeader> headers) {
List<AttachmentItem> attachments = attachmentController.get(m);
if (attachments == null) {
loadMessageAttachments(m, headers);
return emptyList();
}
return attachments;
}
} }

View File

@@ -2,12 +2,15 @@ package org.briarproject.briar.android.conversation;
import android.content.Context; import android.content.Context;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
import android.support.v7.widget.LinearLayoutManager;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.BriarAdapter;
@@ -98,16 +101,20 @@ class ConversationAdapter
return messages; return messages;
} }
SparseArray<ConversationMessageItem> getMessageItems() { @Nullable
SparseArray<ConversationMessageItem> messages = new SparseArray<>(); Pair<Integer, ConversationMessageItem> getMessageItem(MessageId messageId) {
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i); ConversationItem item = items.get(i);
if (item instanceof ConversationMessageItem) { if (item instanceof ConversationMessageItem &&
messages.put(i, (ConversationMessageItem) item); item.getId().equals(messageId)) {
return new Pair<>(i, (ConversationMessageItem) item);
} }
} }
return messages; return null;
}
boolean isScrolledToBottom(LinearLayoutManager layoutManager) {
return layoutManager.findLastVisibleItemPosition() == items.size() - 1;
} }
} }

View File

@@ -3,9 +3,9 @@ package org.briarproject.briar.android.conversation;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.constraint.ConstraintLayout;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -18,11 +18,11 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault @NotNullByDefault
abstract class ConversationItemViewHolder extends ViewHolder { abstract class ConversationItemViewHolder extends ViewHolder {
protected final ViewGroup layout; protected final ConstraintLayout layout;
@Nullable @Nullable
private final OutItemViewHolder outViewHolder; private final OutItemViewHolder outViewHolder;
private final TextView text; private final TextView text;
private final TextView time; protected final TextView time;
ConversationItemViewHolder(View v, boolean isIncoming) { ConversationItemViewHolder(View v, boolean isIncoming) {
super(v); super(v);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -14,4 +15,7 @@ interface ConversationListener {
void openRequestedShareable(ConversationRequestItem item); void openRequestedShareable(ConversationRequestItem item);
void onAttachmentClicked(View view, ConversationMessageItem messageItem,
AttachmentItem attachmentItem);
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.conversation;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.util.List; import java.util.List;
@@ -14,15 +13,20 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotNullByDefault @NotNullByDefault
class ConversationMessageItem extends ConversationItem { class ConversationMessageItem extends ConversationItem {
private final List<AttachmentHeader> attachments; private List<AttachmentItem> attachments;
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h) { ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
List<AttachmentItem> attachments) {
super(layoutRes, h); super(layoutRes, h);
this.attachments = h.getAttachmentHeaders(); this.attachments = attachments;
} }
List<AttachmentHeader> getAttachments() { List<AttachmentItem> getAttachments() {
return attachments; return attachments;
} }
void setAttachments(List<AttachmentItem> attachments) {
this.attachments = attachments;
}
} }

View File

@@ -1,18 +1,161 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.support.annotation.DrawableRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.constraint.ConstraintSet;
import android.support.v4.content.ContextCompat;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.bumptech.glide.load.Transformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
class ConversationMessageViewHolder extends ConversationItemViewHolder { class ConversationMessageViewHolder extends ConversationItemViewHolder {
// image support will be added here (#1242) @DrawableRes
private static final int ERROR_RES = R.drawable.ic_image_broken;
private final ImageView imageView;
private final ViewGroup statusLayout;
private final int timeColor, timeColorBubble;
private final int radiusBig, radiusSmall;
private final boolean isRtl;
private final ConstraintSet textConstraints = new ConstraintSet();
private final ConstraintSet imageConstraints = new ConstraintSet();
private final ConstraintSet imageTextConstraints = new ConstraintSet();
ConversationMessageViewHolder(View v, boolean isIncoming) { ConversationMessageViewHolder(View v, boolean isIncoming) {
super(v, isIncoming); super(v, isIncoming);
imageView = v.findViewById(R.id.imageView);
statusLayout = v.findViewById(R.id.statusLayout);
radiusBig = v.getContext().getResources()
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
radiusSmall = v.getContext().getResources()
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
// remember original status text color
timeColor = time.getCurrentTextColor();
timeColorBubble =
ContextCompat.getColor(v.getContext(), R.color.briar_white);
// find out if we are showing a RTL language, Use the configuration,
// because getting the layout direction of views is not reliable
Configuration config =
imageView.getContext().getResources().getConfiguration();
isRtl = SDK_INT >= 17 &&
config.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
// clone constraint sets from layout files
textConstraints
.clone(v.getContext(), R.layout.list_item_conversation_msg_in);
imageConstraints.clone(v.getContext(),
R.layout.list_item_conversation_msg_image);
imageTextConstraints.clone(v.getContext(),
R.layout.list_item_conversation_msg_image_text);
// in/out are different layouts, so we need to do this only once
textConstraints
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
imageConstraints
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
imageTextConstraints
.setHorizontalBias(R.id.statusLayout, isIncoming() ? 1 : 0);
}
@Override
void bind(ConversationItem conversationItem,
ConversationListener listener) {
super.bind(conversationItem, listener);
ConversationMessageItem item =
(ConversationMessageItem) conversationItem;
if (item.getAttachments().isEmpty()) {
bindTextItem();
} else {
bindImageItem(item, listener);
}
}
private void bindTextItem() {
clearImage();
statusLayout.setBackgroundResource(0);
// also reset padding (the background drawable defines some)
statusLayout.setPadding(0, 0, 0, 0);
time.setTextColor(timeColor);
textConstraints.applyTo(layout);
}
private void bindImageItem(ConversationMessageItem item,
ConversationListener listener) {
// TODO show more than just the first image
AttachmentItem attachment = item.getAttachments().get(0);
ConstraintSet constraintSet;
if (item.getText() == null) {
statusLayout
.setBackgroundResource(R.drawable.msg_status_bubble);
time.setTextColor(timeColorBubble);
constraintSet = imageConstraints;
} else {
statusLayout.setBackgroundResource(0);
// also reset padding (the background drawable defines some)
statusLayout.setPadding(0, 0, 0, 0);
time.setTextColor(timeColor);
constraintSet = imageTextConstraints;
}
// apply image size constraints, so glides picks them up for scaling
int width = attachment.getThumbnailWidth();
int height = attachment.getThumbnailHeight();
constraintSet.constrainWidth(R.id.imageView, width);
constraintSet.constrainHeight(R.id.imageView, height);
constraintSet.applyTo(layout);
if (attachment.hasError()) {
clearImage();
imageView.setImageResource(ERROR_RES);
} else {
loadImage(item, attachment, listener);
}
}
private void clearImage() {
GlideApp.with(imageView)
.clear(imageView);
imageView.setOnClickListener(null);
}
private void loadImage(ConversationMessageItem item,
AttachmentItem attachment, ConversationListener listener) {
boolean leftCornerSmall =
(isIncoming() && !isRtl) || (!isIncoming() && isRtl);
boolean bottomRound = item.getText() == null;
Transformation<Bitmap> transformation = new BriarImageTransformation(
radiusSmall, radiusBig, leftCornerSmall, bottomRound);
GlideApp.with(imageView)
.load(attachment)
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.transform(transformation)
.transition(withCrossFade())
.into(imageView)
.waitForLayout();
imageView.setOnClickListener(
view -> listener.onAttachmentClicked(view, item, attachment));
} }
} }

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.conversation;
import org.briarproject.briar.android.activity.ActivityScope;
import org.briarproject.briar.android.conversation.glide.BriarDataFetcherFactory;
import dagger.Module;
import dagger.Provides;
@Module
public class ConversationModule {
@ActivityScope
@Provides
BriarDataFetcherFactory provideBriarDataFetcherFactory(
BriarDataFetcherFactory dataFetcherFactory) {
return dataFetcherFactory;
}
}

View File

@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -38,6 +39,7 @@ public class ConversationViewModel extends AndroidViewModel {
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
private final ContactManager contactManager; private final ContactManager contactManager;
private final AttachmentController attachmentController;
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
@@ -52,10 +54,12 @@ public class ConversationViewModel extends AndroidViewModel {
@Inject @Inject
ConversationViewModel(Application application, ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
ContactManager contactManager) { ContactManager contactManager, MessagingManager messagingManager) {
super(application); super(application);
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.contactManager = contactManager; this.contactManager = contactManager;
this.attachmentController = new AttachmentController(messagingManager,
application.getResources());
contactDeleted.setValue(false); contactDeleted.setValue(false);
} }
@@ -96,6 +100,10 @@ public class ConversationViewModel extends AndroidViewModel {
}); });
} }
AttachmentController getAttachmentController() {
return attachmentController;
}
LiveData<Contact> getContact() { LiveData<Contact> getContact() {
return contact; return contact;
} }

View File

@@ -9,17 +9,21 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.api.blog.BlogInvitationRequest; import org.briarproject.briar.api.blog.BlogInvitationRequest;
import org.briarproject.briar.api.blog.BlogInvitationResponse; import org.briarproject.briar.api.blog.BlogInvitationResponse;
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
import org.briarproject.briar.api.forum.ForumInvitationRequest; import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.introduction.IntroductionRequest; import org.briarproject.briar.api.introduction.IntroductionRequest;
import org.briarproject.briar.api.introduction.IntroductionResponse; import org.briarproject.briar.api.introduction.IntroductionResponse;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader; import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.Collections.emptyList;
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG; import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.BLOG;
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM; import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.FORUM;
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP; import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
@@ -33,24 +37,33 @@ class ConversationVisitor implements
private final Context ctx; private final Context ctx;
private final TextCache textCache; private final TextCache textCache;
private final AttachmentCache attachmentCache;
private final LiveData<String> contactName; private final LiveData<String> contactName;
ConversationVisitor(Context ctx, TextCache textCache, ConversationVisitor(Context ctx, TextCache textCache,
LiveData<String> contactName) { AttachmentCache attachmentCache, LiveData<String> contactName) {
this.ctx = ctx; this.ctx = ctx;
this.textCache = textCache; this.textCache = textCache;
this.attachmentCache = attachmentCache;
this.contactName = contactName; this.contactName = contactName;
} }
@Override @Override
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) { public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
ConversationItem item; ConversationItem item;
List<AttachmentItem> attachments;
if (h.getAttachmentHeaders().isEmpty()) {
attachments = emptyList();
} else {
attachments = attachmentCache
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
}
if (h.isLocal()) { if (h.isLocal()) {
item = new ConversationMessageItem( item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_out, h); R.layout.list_item_conversation_msg_out, h, attachments);
} else { } else {
item = new ConversationMessageItem( item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_in, h); R.layout.list_item_conversation_msg_in, h, attachments);
} }
if (h.hasText()) { if (h.hasText()) {
String text = textCache.getText(h.getId()); String text = textCache.getText(h.getId());
@@ -216,11 +229,18 @@ class ConversationVisitor implements
return new ConversationNoticeItem( return new ConversationNoticeItem(
R.layout.list_item_conversation_notice_out, text, r); R.layout.list_item_conversation_notice_out, text, r);
} else { } else {
String text = ctx.getString(R.string.introduction_request_received, String text;
contactName.getValue(), name); if (r.isContact()) {
text = ctx.getString(
R.string.introduction_request_exists_received,
contactName.getValue(), name);
} else {
text = ctx.getString(R.string.introduction_request_received,
contactName.getValue(), name);
}
return new ConversationRequestItem( return new ConversationRequestItem(
R.layout.list_item_conversation_request, text, INTRODUCTION, R.layout.list_item_conversation_request, text,
r); INTRODUCTION, r);
} }
} }
@@ -272,4 +292,9 @@ class ConversationVisitor implements
@Nullable @Nullable
String getText(MessageId m); String getText(MessageId m);
} }
interface AttachmentCache {
List<AttachmentItem> getAttachmentItems(MessageId m,
List<AttachmentHeader> headers);
}
} }

View File

@@ -0,0 +1,233 @@
package org.briarproject.briar.android.conversation;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.AppBarLayout;
import android.support.v7.widget.Toolbar;
import android.transition.Fade;
import android.transition.Transition;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.github.chrisbanes.photoview.PhotoView;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import org.briarproject.briar.android.view.PullDownLayout;
import static android.graphics.Color.TRANSPARENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.widget.ImageView.ScaleType.FIT_START;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
public class ImageActivity extends BriarActivity
implements PullDownLayout.Callback {
final static String ATTACHMENT = "attachment";
final static String NAME = "name";
final static String DATE = "date";
private PullDownLayout layout;
private AppBarLayout appBarLayout;
private PhotoView photoView;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
// Transitions
supportPostponeEnterTransition();
Window window = getWindow();
if (SDK_INT >= 21) {
Transition transition = new Fade();
setSceneTransitionAnimation(transition, null, transition);
}
// inflate layout
setContentView(R.layout.activity_image);
layout = findViewById(R.id.layout);
layout.getBackground().setAlpha(255);
layout.setCallback(this);
// Status Bar
if (SDK_INT >= 21) {
window.setStatusBarColor(TRANSPARENT);
} else if (SDK_INT >= 19) {
// we can't make the status bar transparent, but translucent
window.addFlags(FLAG_TRANSLUCENT_STATUS);
}
// Toolbar
appBarLayout = findViewById(R.id.appBarLayout);
Toolbar toolbar = requireNonNull(setUpCustomToolbar(true));
TextView contactName = toolbar.findViewById(R.id.contactName);
TextView dateView = toolbar.findViewById(R.id.dateView);
// Intent Extras
AttachmentItem attachment = getIntent().getParcelableExtra(ATTACHMENT);
String name = getIntent().getStringExtra(NAME);
long time = getIntent().getLongExtra(DATE, 0);
String date = formatDateAbsolute(this, time);
contactName.setText(name);
dateView.setText(date);
// Image View
photoView = findViewById(R.id.photoView);
if (SDK_INT >= 16) {
photoView.setOnClickListener(view -> toggleSystemUi());
window.getDecorView().setSystemUiVisibility(
SYSTEM_UI_FLAG_LAYOUT_STABLE |
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
// Request Listener
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
supportStartPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource,
boolean isFirstResource) {
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
// set transition name only when not animatable,
// because the animation won't start otherwise
photoView.setTransitionName(
attachment.getTransitionName());
}
// Move image to the top if overlapping toolbar
if (isOverlappingToolbar(resource)) {
photoView.setScaleType(FIT_START);
}
supportStartPostponedEnterTransition();
return false;
}
};
// Load Image
GlideApp.with(this)
.load(attachment)
.diskCacheStrategy(NONE)
.error(R.drawable.ic_image_broken)
.dontTransform()
.addListener(listener)
.into(photoView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onPullStart() {
appBarLayout.animate()
.alpha(0f)
.start();
}
@Override
public void onPull(float progress) {
layout.getBackground().setAlpha(Math.round((1 - progress) * 255));
}
@Override
public void onPullCancel() {
appBarLayout.animate()
.alpha(1f)
.start();
}
@Override
public void onPullComplete() {
supportFinishAfterTransition();
}
@RequiresApi(api = 16)
private void toggleSystemUi() {
View decorView = getWindow().getDecorView();
if (appBarLayout.getVisibility() == VISIBLE) {
hideSystemUi(decorView);
} else {
showSystemUi(decorView);
}
}
@RequiresApi(api = 16)
private void hideSystemUi(View decorView) {
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_FULLSCREEN
);
appBarLayout.animate()
.translationYBy(-1 * appBarLayout.getHeight())
.alpha(0f)
.withEndAction(() -> appBarLayout.setVisibility(GONE))
.start();
}
@RequiresApi(api = 16)
private void showSystemUi(View decorView) {
decorView.setSystemUiVisibility(
SYSTEM_UI_FLAG_LAYOUT_STABLE
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
appBarLayout.animate()
.translationYBy(appBarLayout.getHeight())
.alpha(1f)
.withStartAction(() -> appBarLayout.setVisibility(VISIBLE))
.start();
}
private boolean isOverlappingToolbar(Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
float widthPercentage = photoView.getWidth() / (float) width;
float heightPercentage = photoView.getHeight() / (float) height;
float scaleFactor = Math.min(widthPercentage, heightPercentage);
int realWidth = (int) (width * scaleFactor);
int realHeight = (int) (height * scaleFactor);
// return if photo doesn't use the full width,
// because it will be moved to the right otherwise
if (realWidth < photoView.getWidth()) return false;
int drawableTop = (photoView.getHeight() - realHeight) / 2;
return drawableTop < appBarLayout.getBottom() &&
drawableTop != appBarLayout.getTop();
}
}

View File

@@ -0,0 +1,85 @@
package org.briarproject.briar.android.conversation.glide;
import android.support.annotation.Nullable;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.data.DataFetcher;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.conversation.AttachmentItem;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.InputStream;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static com.bumptech.glide.load.DataSource.LOCAL;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class BriarDataFetcher implements DataFetcher<InputStream> {
private final static Logger LOG =
getLogger(BriarDataFetcher.class.getName());
private final MessagingManager messagingManager;
@DatabaseExecutor
private final Executor dbExecutor;
private final AttachmentItem attachment;
@Nullable
private volatile InputStream inputStream;
private volatile boolean cancel = false;
@Inject
public BriarDataFetcher(MessagingManager messagingManager,
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) {
this.messagingManager = messagingManager;
this.dbExecutor = dbExecutor;
this.attachment = attachment;
}
@Override
public void loadData(Priority priority,
DataCallback<? super InputStream> callback) {
MessageId id = attachment.getMessageId();
dbExecutor.execute(() -> {
if (cancel) return;
try {
inputStream = messagingManager.getAttachment(id).getStream();
callback.onDataReady(inputStream);
} catch (DbException e) {
callback.onLoadFailed(e);
}
});
}
@Override
public void cleanup() {
tryToClose(inputStream, LOG, WARNING);
}
@Override
public void cancel() {
cancel = true;
}
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@Override
public DataSource getDataSource() {
return LOCAL;
}
}

View File

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

View File

@@ -0,0 +1,44 @@
package org.briarproject.briar.android.conversation.glide;
import android.content.Context;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.conversation.AttachmentItem;
import java.io.InputStream;
import static android.util.Log.DEBUG;
import static android.util.Log.WARN;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@GlideModule
@NotNullByDefault
public final class BriarGlideModule extends AppGlideModule {
@Override
public void registerComponents(Context context, Glide glide,
Registry registry) {
BriarApplication app =
(BriarApplication) context.getApplicationContext();
BriarModelLoaderFactory factory = new BriarModelLoaderFactory(app);
registry.prepend(AttachmentItem.class, InputStream.class, factory);
}
@Override
public void applyOptions(Context context, GlideBuilder builder) {
builder.setLogLevel(IS_DEBUG_BUILD ? DEBUG : WARN);
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.briar.android.conversation.glide;
import android.graphics.Bitmap;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
public BriarImageTransformation(int smallRadius, int radius,
boolean leftCornerSmall, boolean bottomRound) {
super(new CenterCrop(), new ImageCornerTransformation(
smallRadius, radius, leftCornerSmall, bottomRound));
}
}

View File

@@ -0,0 +1,42 @@
package org.briarproject.briar.android.conversation.glide;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.signature.ObjectKey;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.conversation.AttachmentItem;
import java.io.InputStream;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public final class BriarModelLoader
implements ModelLoader<AttachmentItem, InputStream> {
@Inject
BriarDataFetcherFactory dataFetcherFactory;
public BriarModelLoader(BriarApplication app) {
app.getApplicationComponent().inject(this);
}
@Override
public LoadData<InputStream> buildLoadData(AttachmentItem model, int width,
int height, Options options) {
ObjectKey key = new ObjectKey(model.getMessageId());
BriarDataFetcher dataFetcher =
dataFetcherFactory.createBriarDataFetcher(model);
return new LoadData<>(key, dataFetcher);
}
@Override
public boolean handles(AttachmentItem model) {
return true;
}
}

View File

@@ -0,0 +1,34 @@
package org.briarproject.briar.android.conversation.glide;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.conversation.AttachmentItem;
import java.io.InputStream;
@NotNullByDefault
class BriarModelLoaderFactory
implements ModelLoaderFactory<AttachmentItem, InputStream> {
private final BriarApplication app;
public BriarModelLoaderFactory(BriarApplication app) {
this.app = app;
}
@Override
public ModelLoader<AttachmentItem, InputStream> build(
MultiModelLoaderFactory multiFactory) {
return new BriarModelLoader(app);
}
@Override
public void teardown() {
// noop
}
}

View File

@@ -0,0 +1,111 @@
package org.briarproject.briar.android.conversation.glide;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.MessageDigest;
import javax.annotation.concurrent.Immutable;
import static android.graphics.Bitmap.Config.ARGB_8888;
import static android.graphics.Shader.TileMode.CLAMP;
@Immutable
@NotNullByDefault
class ImageCornerTransformation extends BitmapTransformation {
private static final String ID = ImageCornerTransformation.class.getName();
private final int smallRadius, radius;
private final boolean leftCornerSmall, bottomRound;
ImageCornerTransformation(int smallRadius, int radius,
boolean leftCornerSmall, boolean bottomRound) {
this.smallRadius = smallRadius;
this.radius = radius;
this.leftCornerSmall = leftCornerSmall;
this.bottomRound = bottomRound;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
int width = toTransform.getWidth();
int height = toTransform.getHeight();
Bitmap bitmap = pool.get(width, height, ARGB_8888);
bitmap.setHasAlpha(true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP));
drawRect(canvas, paint, width, height);
return bitmap;
}
private void drawRect(Canvas canvas, Paint paint, float width,
float height) {
drawSmallCorner(canvas, paint, width);
drawBigCorners(canvas, paint, width, height);
}
private void drawSmallCorner(Canvas canvas, Paint paint, float width) {
float left = leftCornerSmall ? 0 : width - radius;
float right = leftCornerSmall ? radius : width;
canvas.drawRoundRect(new RectF(left, 0, right, radius),
smallRadius, smallRadius, paint);
}
private void drawBigCorners(Canvas canvas, Paint paint, float width,
float height) {
float top = bottomRound ? 0 : radius;
RectF rect = new RectF(0, top, width, height);
if (bottomRound) {
canvas.drawRoundRect(rect, radius, radius, paint);
} else {
canvas.drawRect(rect, paint);
canvas.drawRoundRect(new RectF(0, 0, width, radius * 2),
radius, radius, paint);
}
}
@Override
public String toString() {
return "ImageCornerTransformation(smallRadius=" + smallRadius +
", radius=" + radius + ", leftCornerSmall=" + leftCornerSmall +
", bottomRound=" + bottomRound + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof ImageCornerTransformation &&
((ImageCornerTransformation) o).smallRadius == smallRadius &&
((ImageCornerTransformation) o).radius == radius &&
((ImageCornerTransformation) o).leftCornerSmall ==
leftCornerSmall &&
((ImageCornerTransformation) o).bottomRound == bottomRound;
}
@Override
public int hashCode() {
return ID.hashCode() + (smallRadius << 16) ^ (radius << 2) ^
(leftCornerSmall ? 2 : 0) ^ (bottomRound ? 1 : 0);
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update((ID + '|' + smallRadius + '|' + radius + '|' +
leftCornerSmall + '|' + bottomRound).getBytes(CHARSET));
}
}

View File

@@ -16,6 +16,7 @@ import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes; import android.support.annotation.ColorRes;
import android.support.annotation.MainThread; import android.support.annotation.MainThread;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
@@ -31,6 +32,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan; import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.URLSpan; import android.text.style.URLSpan;
import android.transition.Transition;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
@@ -60,12 +62,16 @@ import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO;
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES; import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES;
import static android.support.v7.app.AppCompatDelegate.setDefaultNightMode; import static android.support.v7.app.AppCompatDelegate.setDefaultNightMode;
import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_ALL;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE; import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
import static android.text.format.DateUtils.FORMAT_ABBREV_TIME; import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE; import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
import static android.text.format.DateUtils.FORMAT_SHOW_YEAR;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_ENTER; import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.EditorInfo.IME_NULL; import static android.view.inputmethod.EditorInfo.IME_NULL;
@@ -117,6 +123,13 @@ public class UiUtils {
MIN_DATE_RESOLUTION, flags).toString(); MIN_DATE_RESOLUTION, flags).toString();
} }
public static String formatDateAbsolute(Context ctx, long time) {
int flags = FORMAT_SHOW_TIME | FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
long diff = System.currentTimeMillis() - time;
if (diff >= YEAR_IN_MILLIS) flags |= FORMAT_SHOW_YEAR;
return DateUtils.formatDateTime(ctx, time, flags);
}
public static int getDaysUntilExpiry() { public static int getDaysUntilExpiry() {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24; long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
@@ -318,6 +331,12 @@ public class UiUtils {
keyEvent.getKeyCode() == KEYCODE_ENTER; keyEvent.getKeyCode() == KEYCODE_ENTER;
} }
@RequiresApi(api = 21)
public static void excludeSystemUi(Transition transition) {
transition.excludeTarget(android.R.id.statusBarBackground, true);
transition.excludeTarget(android.R.id.navigationBarBackground, true);
}
/** /**
* Observes the given {@link LiveData} until the first change. * Observes the given {@link LiveData} until the first change.
* If the LiveData's value is available, the {@link Observer} will be * If the LiveData's value is available, the {@link Observer} will be

View File

@@ -0,0 +1,161 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 XiNGRZ <xxx@oxo.ooo>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.briarproject.briar.android.view;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class PullDownLayout extends FrameLayout {
private final ViewDragHelper dragger;
private final int minimumFlingVelocity;
@Nullable
private Callback callback;
public PullDownLayout(Context context) {
this(context, null);
}
public PullDownLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PullDownLayout(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
dragger = ViewDragHelper.create(this, 1f / 8f, new ViewDragCallback());
minimumFlingVelocity =
ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
}
public void setCallback(@Nullable Callback callback) {
this.callback = callback;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return dragger.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
dragger.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (dragger.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
public interface Callback {
void onPullStart();
void onPull(float progress);
void onPullCancel();
void onPullComplete();
}
private class ViewDragCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return 0;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return Math.max(0, top);
}
@Override
public int getViewHorizontalDragRange(View child) {
return 0;
}
@Override
public int getViewVerticalDragRange(View child) {
return getHeight();
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
if (callback != null) {
callback.onPullStart();
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
if (callback != null) {
callback.onPull((float) top / (float) getHeight());
}
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int slop = yvel > minimumFlingVelocity ? getHeight() / 6 :
getHeight() / 3;
if (releasedChild.getTop() > slop) {
if (callback != null) {
callback.onPullComplete();
}
} else {
if (callback != null) {
callback.onPullCancel();
}
dragger.settleCapturedViewAt(0, 0);
invalidate();
}
}
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#808080"
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
</vector>

View File

@@ -8,10 +8,10 @@
android:topLeftRadius="@dimen/message_bubble_radius_top_inner" android:topLeftRadius="@dimen/message_bubble_radius_top_inner"
android:topRightRadius="@dimen/message_bubble_radius_top_outer"/> android:topRightRadius="@dimen/message_bubble_radius_top_outer"/>
<padding <padding
android:bottom="@dimen/message_bubble_padding_bottom" android:bottom="@dimen/message_bubble_border"
android:left="@dimen/message_bubble_padding_sides" android:left="@dimen/message_bubble_border"
android:right="@dimen/message_bubble_padding_sides" android:right="@dimen/message_bubble_border"
android:top="@dimen/message_bubble_padding_top"/> android:top="@dimen/message_bubble_border"/>
<solid <solid
android:color="@color/msg_in"/> android:color="@color/msg_in"/>
<stroke <stroke

View File

@@ -8,10 +8,10 @@
android:topLeftRadius="@dimen/message_bubble_radius_top_outer" android:topLeftRadius="@dimen/message_bubble_radius_top_outer"
android:topRightRadius="@dimen/message_bubble_radius_top_inner"/> android:topRightRadius="@dimen/message_bubble_radius_top_inner"/>
<padding <padding
android:bottom="@dimen/message_bubble_padding_bottom" android:bottom="@dimen/message_bubble_border"
android:left="@dimen/message_bubble_padding_sides" android:left="@dimen/message_bubble_border"
android:right="@dimen/message_bubble_padding_sides" android:right="@dimen/message_bubble_border"
android:top="@dimen/message_bubble_padding_top"/> android:top="@dimen/message_bubble_border"/>
<solid <solid
android:color="@color/msg_out"/> android:color="@color/msg_out"/>
<stroke <stroke

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="@dimen/message_bubble_radius_big"/>
<padding
android:bottom="3dp"
android:left="7dp"
android:right="7dp"
android:top="2dp"/>
<solid
android:color="@color/msg_status_bubble_background"/>
</shape>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.briar.android.view.PullDownLayout
android:id="@+id/layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/briar_black"
tools:context=".android.conversation.ImageActivity">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photoView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="ContentDescription"
tools:srcCompat="@tools:sample/backgrounds/scenic"/>
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/msg_status_bubble_background">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
style="@style/BriarToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/msg_status_bubble_background"
android:fitsSystemWindows="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/contactName"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/action_bar_text"
tools:text="Contact Name of someone who chose a long name"/>
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/action_bar_text"
tools:text="date"/>
</LinearLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
</org.briarproject.briar.android.view.PullDownLayout>

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:id="@+id/layout"
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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_margin"
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
android:layout_marginStart="@dimen/message_bubble_margin_tail"
android:layout_marginTop="@dimen/message_bubble_margin"
android:background="@drawable/msg_in"
android:elevation="@dimen/message_bubble_elevation">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/message_bubble_image_default"
android:layout_height="@dimen/message_bubble_image_default"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/alerts_and_states_error"/>
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/text"
style="@style/TextMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
android:textColor="?android:attr/textColorPrimary"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="The text of a message which can sometimes be a bit longer as well"/>
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_top"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:background="@drawable/msg_status_bubble"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:id="@+id/layout"
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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_margin"
android:layout_marginEnd="@dimen/message_bubble_margin_non_tail"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
android:layout_marginStart="@dimen/message_bubble_margin_tail"
android:layout_marginTop="@dimen/message_bubble_margin"
android:background="@drawable/msg_in"
android:elevation="@dimen/message_bubble_elevation">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/message_bubble_image_default"
android:layout_height="@dimen/message_bubble_image_default"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/alerts_and_states_error"/>
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/text"
style="@style/TextMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
android:textColor="?android:attr/textColorPrimary"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="The text of a message which can sometimes be a bit longer as well"/>
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text">
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -15,29 +15,55 @@
android:background="@drawable/msg_in" android:background="@drawable/msg_in"
android:elevation="@dimen/message_bubble_elevation"> android:elevation="@dimen/message_bubble_elevation">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/message_bubble_image_default"
android:layout_height="@dimen/message_bubble_image_default"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"/>
<com.vanniktech.emoji.EmojiTextView <com.vanniktech.emoji.EmojiTextView
android:id="@+id/text" android:id="@+id/text"
style="@style/TextMessage" style="@style/TextMessage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:layout_marginTop="@dimen/message_bubble_padding_top_inner"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/time" app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="@+id/time" app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="Short message"/> tools:text="The text of a message which can sometimes be a bit longer as well"/>
<TextView <LinearLayout
android:id="@+id/time" android:id="@+id/statusLayout"
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:layout_marginLeft="@dimen/message_bubble_padding_sides_inner"
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text" app:layout_constraintTop_toBottomOf="@+id/text">
tools:text="Dec 24, 13:37"/>
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Dec 24, 13:37"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

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