Compare commits

..

28 Commits

Author SHA1 Message Date
akwizgran
707066f1a1 Add new module to integration tests. 2018-12-06 18:02:48 +00:00
akwizgran
65e5dc266f Hook up MessagingManager to MessageInputStreamFactory. 2018-12-06 17:46:14 +00:00
akwizgran
3487a7cfee Implement MessageInputStreamFactory interface. 2018-12-06 14:05:36 +00:00
akwizgran
eea453fc5f Add placeholder BlockSource implementation to DB. 2018-12-06 13:55:49 +00:00
akwizgran
c6f460a936 Add input stream that fetches blocks from the DB. 2018-12-06 11:34:40 +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
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
122 changed files with 2946 additions and 799 deletions

View File

@@ -12,7 +12,7 @@ test:
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom test --continue
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom test
after_script:
# these file change every time but should not be cached

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.Clock;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
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.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@@ -161,11 +160,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override
void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
IoUtils.tryToClose(ss, LOG, WARNING);
}
@Override
@@ -195,7 +190,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
s.connect();
return wrapSocket(s);
} catch (IOException e) {
tryToClose(s);
IoUtils.tryToClose(s, LOG, WARNING);
throw e;
}
}
@@ -268,14 +263,6 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
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 {
@Override

View File

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

View File

@@ -208,6 +208,24 @@ public interface DatabaseComponent {
Collection<Message> generateRequestedBatch(Transaction txn, ContactId c,
int maxLength, int maxLatency) throws DbException;
/**
* Returns the number of blocks in the given message.
* <p>
* Read-only.
*/
int getBlockCount(Transaction txn, MessageId m) throws DbException;
/**
* Returns the given block of the given message.
* <p>
* Read-only.
*
* @throws NoSuchBlockException if 'blockNumber' is greater than or equal
* to the number of blocks in the message
*/
byte[] getBlock(Transaction txn, MessageId m, int blockNumber)
throws DbException;
/**
* Returns the contact with the given ID.
* <p/>

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when a database operation is attempted for a block that is not in
* the database. This exception may occur due to concurrent updates and does
* not indicate a database error.
*/
public class NoSuchBlockException extends DbException {
}

View File

@@ -0,0 +1,21 @@
package org.briarproject.bramble.api.io;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchBlockException;
import org.briarproject.bramble.api.sync.MessageId;
public interface BlockSource {
/**
* Returns the number of blocks in the given message.
*/
int getBlockCount(MessageId m) throws DbException;
/**
* Returns the given block of the given message.
*
* @throws NoSuchBlockException if 'blockNumber' is greater than or equal
* to the number of blocks in the message
*/
byte[] getBlock(MessageId m, int blockNumber) throws DbException;
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.bramble.api.io;
import org.briarproject.bramble.api.sync.MessageId;
import java.io.IOException;
import java.io.InputStream;
public interface MessageInputStreamFactory {
/**
* Returns an {@link InputStream} for reading the given message from the
* database. This method returns immediately. If the message is not in the
* database or cannot be read, reading from the stream will throw an
* {@link IOException};
*/
InputStream getMessageInputStream(MessageId m);
}

View File

@@ -35,4 +35,9 @@ public interface SyncConstants {
* The maximum number of message IDs in an ack, offer or request record.
*/
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
/**
* The maximum length of a message block in bytes.
*/
int MAX_BLOCK_LENGTH = 32 * 2014; // 32 KiB
}

View File

@@ -8,12 +8,15 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class IoUtils {
@@ -54,16 +57,35 @@ public class IoUtils {
out.flush();
out.close();
} catch (IOException e) {
tryToClose(in);
tryToClose(out);
tryToClose(in, LOG, WARNING);
tryToClose(out, LOG, WARNING);
}
}
private static void tryToClose(@Nullable Closeable c) {
public static void tryToClose(@Nullable Closeable c, Logger logger,
Level level) {
try {
if (c != null) c.close();
} 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
private static final String os = System.getProperty("os.name");
@Nullable
private static final String version = System.getProperty("os.version");
@Nullable
private static final String vendor = System.getProperty("java.vendor");
public static boolean isWindows() {

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule;
@@ -36,6 +37,7 @@ import dagger.Module;
DatabaseExecutorModule.class,
EventModule.class,
IdentityModule.class,
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
PluginModule.class,

View File

@@ -0,0 +1,29 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.sync.MessageId;
import javax.inject.Inject;
class BlockSourceImpl implements BlockSource {
private final DatabaseComponent db;
@Inject
BlockSourceImpl(DatabaseComponent db) {
this.db = db;
}
@Override
public int getBlockCount(MessageId m) throws DbException {
return db.transactionWithResult(true, txn -> db.getBlockCount(txn, m));
}
@Override
public byte[] getBlock(MessageId m, int blockNumber) throws DbException {
return db.transactionWithResult(true, txn ->
db.getBlock(txn, m, blockNumber));
}
}

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.DbRunnable;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchBlockException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -420,6 +421,26 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return messages;
}
@Override
public int getBlockCount(Transaction transaction, MessageId m)
throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
return 1;
}
@Override
public byte[] getBlock(Transaction transaction, MessageId m,
int blockNumber) throws DbException {
T txn = unbox(transaction);
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
if (blockNumber != 0)
throw new NoSuchBlockException();
return db.getMessage(txn, m).getBody();
}
@Override
public Contact getContact(Transaction transaction, ContactId c)
throws DbException {

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.system.Clock;
@@ -31,4 +32,9 @@ public class DatabaseModule {
return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
shutdown);
}
@Provides
BlockSource provideBlockSource(BlockSourceImpl blockSource) {
return blockSource;
}
}

View File

@@ -15,16 +15,23 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Logger;
import javax.annotation.Nullable;
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.
*/
@NotNullByDefault
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 SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY";
@@ -121,8 +128,8 @@ class H2Database extends JdbcDatabase {
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}

View File

@@ -14,16 +14,24 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.annotation.Nullable;
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.
*/
@NotNullByDefault
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 SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY";
@@ -72,8 +80,8 @@ class HyperSqlDatabase extends JdbcDatabase {
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}
@@ -122,8 +130,8 @@ class HyperSqlDatabase extends JdbcDatabase {
s.close();
c.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(c);
tryToClose(s, LOG, WARNING);
tryToClose(c, LOG, WARNING);
throw new DbException(e);
}
}

View File

@@ -72,6 +72,7 @@ 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.SCHEMA_VERSION_KEY;
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.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -458,30 +459,6 @@ abstract class JdbcDatabase implements Database<Connection> {
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 {
Statement s = null;
try {
@@ -502,7 +479,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
s.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@@ -519,7 +496,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.close();
} catch (SQLException e) {
tryToClose(s);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
@@ -566,11 +543,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} catch (SQLException e) {
// Try to close the connection
logException(LOG, WARNING, e);
try {
txn.close();
} catch (SQLException e1) {
logException(LOG, WARNING, e1);
}
tryToClose(txn, LOG, WARNING);
// Whatever happens, allow the database to close
connectionsLock.lock();
try {
@@ -659,8 +632,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return c;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -681,7 +654,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -704,7 +677,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Create a status row for each message in the group
addStatus(txn, c, g, groupShared);
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -735,8 +708,8 @@ abstract class JdbcDatabase implements Database<Connection> {
rs.close();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -761,7 +734,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -810,7 +783,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -840,8 +813,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -873,7 +846,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -915,8 +888,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -935,7 +908,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1015,8 +988,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return keySetId;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1039,8 +1012,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1061,8 +1034,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1083,8 +1056,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1105,8 +1078,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1127,8 +1100,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1149,8 +1122,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1174,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return found;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1198,8 +1171,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return count;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1223,7 +1196,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1240,7 +1213,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1273,8 +1246,8 @@ abstract class JdbcDatabase implements Database<Connection> {
return new Contact(c, author, localAuthorId, alias, verified,
active);
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1310,8 +1283,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return contacts;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1333,8 +1306,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1371,8 +1344,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return contacts;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1395,8 +1368,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return new Group(g, clientId, majorVersion, descriptor);
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1423,8 +1396,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return groups;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1449,8 +1422,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return v;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1473,8 +1446,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return visible;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1505,8 +1478,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return localAuthor;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1537,8 +1510,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return authors;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1566,8 +1539,8 @@ abstract class JdbcDatabase implements Database<Connection> {
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
return new Message(m, g, timestamp, body);
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1590,8 +1563,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1628,8 +1601,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (intersection == null) throw new AssertionError();
return intersection;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1661,8 +1634,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return all;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1696,8 +1669,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return metadata;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1720,8 +1693,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return metadata;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1746,8 +1719,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return metadata;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1776,8 +1749,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return statuses;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1807,8 +1780,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return status;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1837,8 +1810,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return dependencies;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1868,8 +1841,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return dependents;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1891,8 +1864,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return state;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1916,8 +1889,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1950,8 +1923,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -1975,8 +1948,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2014,8 +1987,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2048,8 +2021,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2076,8 +2049,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2106,8 +2079,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return nextSendTime;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2145,8 +2118,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return ids;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2168,8 +2141,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return s;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2234,8 +2207,8 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return keys;
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2254,7 +2227,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2281,7 +2254,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2308,7 +2281,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2338,7 +2311,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2383,8 +2356,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2450,7 +2423,7 @@ abstract class JdbcDatabase implements Database<Connection> {
}
return added;
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2497,7 +2470,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2516,7 +2489,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2535,7 +2508,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2554,7 +2527,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2571,7 +2544,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2587,7 +2560,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2615,7 +2588,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2632,7 +2605,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2648,7 +2621,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2667,7 +2640,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
return affected == 1;
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2692,7 +2665,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2709,7 +2682,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2730,7 +2703,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2749,7 +2722,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2767,7 +2740,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2785,7 +2758,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2804,7 +2777,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2834,7 +2807,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2860,7 +2833,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2913,7 +2886,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2936,7 +2909,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2955,7 +2928,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -2991,8 +2964,8 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@@ -3059,7 +3032,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (rows < 0 || rows > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
tryToClose(ps, LOG, WARNING);
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.util.logging.Logger;
import javax.annotation.Nullable;
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> {
@@ -40,16 +38,8 @@ class Migration38_39 implements Migration<Connection> {
+ " ALTER COLUMN contactId"
+ " SET NOT NULL");
} catch (SQLException e) {
tryToClose(s);
tryToClose(s, LOG, WARNING);
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.util.logging.Logger;
import javax.annotation.Nullable;
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> {
@@ -39,16 +37,8 @@ class Migration39_40 implements Migration<Connection> {
+ " ALTER COLUMN eta"
+ " SET NOT NULL");
} catch (SQLException e) {
tryToClose(s);
tryToClose(s, LOG, WARNING);
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.util.logging.Logger;
import javax.annotation.Nullable;
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.db.JdbcUtils.tryToClose;
class Migration40_41 implements Migration<Connection> {
@@ -41,16 +39,8 @@ class Migration40_41 implements Migration<Connection> {
s.execute("ALTER TABLE contacts"
+ dbTypes.replaceTypes(" ADD alias _STRING"));
} catch (SQLException e) {
tryToClose(s);
tryToClose(s, LOG, WARNING);
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

@@ -0,0 +1,155 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.lang.System.arraycopy;
import static java.lang.Thread.currentThread;
/**
* An {@link InputStream} that asynchronously fetches blocks of data on demand.
*/
@ThreadSafe
@NotNullByDefault
abstract class BlockInputStream extends InputStream {
private final int minBufferBytes;
private final BlockingQueue<Buffer> queue = new ArrayBlockingQueue<>(1);
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private Buffer buffer = null;
@GuardedBy("lock")
private int offset = 0;
@GuardedBy("lock")
private boolean fetchingBlock = false;
abstract void fetchBlockAsync(int blockNumber);
BlockInputStream(int minBufferBytes) {
this.minBufferBytes = minBufferBytes;
}
@Override
public int read() throws IOException {
synchronized (lock) {
if (!prepareRead()) return -1;
if (buffer == null) throw new AssertionError();
return buffer.data[offset++] & 0xFF;
}
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (off < 0 || len < 0 || off + len > b.length)
throw new IllegalArgumentException();
synchronized (lock) {
if (!prepareRead()) return -1;
if (buffer == null) throw new AssertionError();
len = Math.min(len, buffer.length - offset);
if (len < 0) throw new AssertionError();
arraycopy(buffer.data, offset, b, off, len);
offset += len;
return len;
}
}
private boolean prepareRead() throws IOException {
throwExceptionIfNecessary();
if (isEndOfStream()) return false;
if (shouldFetchBlock()) fetchBlockAsync();
waitForBlock();
if (buffer == null) throw new AssertionError();
return offset < buffer.length;
}
@GuardedBy("lock")
private void throwExceptionIfNecessary() throws IOException {
if (buffer != null && buffer.exception != null)
throw new IOException(buffer.exception);
}
@GuardedBy("lock")
private boolean isEndOfStream() {
return buffer != null && offset == buffer.length && !fetchingBlock;
}
@GuardedBy("lock")
private boolean shouldFetchBlock() {
if (fetchingBlock) return false;
if (buffer == null) return true;
if (buffer.length == 0) return false;
return buffer.length - offset < minBufferBytes;
}
@GuardedBy("lock")
private void fetchBlockAsync() {
if (buffer == null) fetchBlockAsync(0);
else fetchBlockAsync(buffer.blockNumber + 1);
fetchingBlock = true;
}
@GuardedBy("lock")
private void waitForBlock() throws IOException {
if (buffer != null && offset < buffer.length) return;
try {
buffer = queue.take();
} catch (InterruptedException e) {
currentThread().interrupt();
throw new InterruptedIOException();
}
fetchingBlock = false;
offset = 0;
throwExceptionIfNecessary();
}
void fetchSucceeded(int blockNumber, byte[] data, int length) {
queue.add(new Buffer(blockNumber, data, length));
}
void fetchFailed(int blockNumber, Exception exception) {
queue.add(new Buffer(blockNumber, exception));
}
private static class Buffer {
private final int blockNumber;
private final byte[] data;
private final int length;
@Nullable
private final Exception exception;
private Buffer(int blockNumber, byte[] data, int length) {
if (length < 0 || length > data.length)
throw new IllegalArgumentException();
this.blockNumber = blockNumber;
this.data = data;
this.length = length;
exception = null;
}
private Buffer(int blockNumber, Exception exception) {
this.blockNumber = blockNumber;
this.exception = exception;
data = new byte[0];
length = 0;
}
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.ThreadSafe;
/**
* A {@link BlockInputStream} that fetches data from a {@link BlockSource}.
*/
@ThreadSafe
@NotNullByDefault
class BlockSourceInputStream extends BlockInputStream {
private final Executor executor;
private final BlockSource blockSource;
private final MessageId messageId;
private volatile int blockCount = -1;
BlockSourceInputStream(int minBufferBytes, Executor executor,
BlockSource blockSource, MessageId messageId) {
super(minBufferBytes);
this.executor = executor;
this.blockSource = blockSource;
this.messageId = messageId;
}
@Override
void fetchBlockAsync(int blockNumber) {
executor.execute(() -> {
try {
if (blockCount == -1) {
blockCount = blockSource.getBlockCount(messageId);
}
if (blockNumber > blockCount) {
fetchFailed(blockNumber, new IllegalArgumentException());
} else if (blockNumber == blockCount) {
fetchSucceeded(blockNumber, new byte[0], 0); // EOF
} else {
byte[] block = blockSource.getBlock(messageId, blockNumber);
fetchSucceeded(blockNumber, block, block.length);
}
} catch (DbException e) {
fetchFailed(blockNumber, e);
}
});
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.MessageInputStreamFactory;
import dagger.Module;
import dagger.Provides;
@Module
public class IoModule {
@Provides
MessageInputStreamFactory provideMessageInputStreamFactory(
MessageInputStreamFactoryImpl messageInputStreamFactory) {
return messageInputStreamFactory;
}
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.io.MessageInputStreamFactory;
import org.briarproject.bramble.api.sync.MessageId;
import java.io.InputStream;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_BLOCK_LENGTH;
class MessageInputStreamFactoryImpl implements MessageInputStreamFactory {
private final Executor dbExecutor;
private final BlockSource blockSource;
@Inject
MessageInputStreamFactoryImpl(@DatabaseExecutor Executor dbExecutor,
BlockSource blockSource) {
this.dbExecutor = dbExecutor;
this.blockSource = blockSource;
}
@Override
public InputStream getMessageInputStream(MessageId m) {
return new BlockSourceInputStream(MAX_BLOCK_LENGTH, dbExecutor,
blockSource, m);
}
}

View File

@@ -4,12 +4,11 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class FileTransportReader implements TransportConnectionReader {
@@ -34,11 +33,7 @@ class FileTransportReader implements TransportConnectionReader {
@Override
public void dispose(boolean exception, boolean recognised) {
try {
in.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
tryToClose(in, LOG, WARNING);
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 java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
@NotNullByDefault
class FileTransportWriter implements TransportConnectionWriter {
@@ -44,11 +43,7 @@ class FileTransportWriter implements TransportConnectionWriter {
@Override
public void dispose(boolean exception) {
try {
out.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
tryToClose(out, LOG, WARNING);
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.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils;
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.PROP_IP_PORTS;
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;
@NotNullByDefault
@@ -293,11 +293,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override
public void close() {
try {
ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
}
IoUtils.tryToClose(ss, LOG, WARNING);
}
}

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.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.IOException;
@@ -153,13 +154,8 @@ abstract class TcpPlugin implements DuplexPlugin {
}
protected void tryToClose(@Nullable ServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
} finally {
callback.transportDisabled();
}
IoUtils.tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
}
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.util.IoUtils;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
@@ -303,8 +302,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
IoUtils.copyAndClose(in, out);
doneFile.createNewFile();
} catch (IOException e) {
tryToClose(in);
tryToClose(out);
IoUtils.tryToClose(in, LOG, WARNING);
IoUtils.tryToClose(out, LOG, WARNING);
throw new PluginException(e);
}
}
@@ -341,22 +340,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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) {
if (f.isDirectory()) {
File[] children = f.listFiles();
@@ -378,7 +361,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
return b;
} 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) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
logException(LOG, WARNING, e);
} finally {
callback.transportDisabled();
}
IoUtils.tryToClose(ss, LOG, WARNING);
callback.transportDisabled();
}
private void publishHiddenService(String port) {
@@ -593,7 +571,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Could not connect to " + scrubOnion(bestOnion)
+ ": " + e.toString());
}
tryToClose(s);
IoUtils.tryToClose(s, LOG, WARNING);
return null;
}
}

View File

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

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

View File

@@ -10,22 +10,24 @@ import java.security.SecureRandomSpi;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
public class LinuxSecureRandomSpi extends SecureRandomSpi {
public class UnixSecureRandomSpi extends SecureRandomSpi {
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 final File inputDevice, outputDevice;
public LinuxSecureRandomSpi() {
@SuppressWarnings("WeakerAccess")
public UnixSecureRandomSpi() {
this(RANDOM_DEVICE, RANDOM_DEVICE);
}
LinuxSecureRandomSpi(File inputDevice, File outputDevice) {
UnixSecureRandomSpi(File inputDevice, File outputDevice) {
this.inputDevice = inputDevice;
this.outputDevice = outputDevice;
}

View File

@@ -0,0 +1,151 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.lib.concurrent.Synchroniser;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import java.util.concurrent.Executor;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_BLOCK_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.spongycastle.util.Arrays.copyOfRange;
public class BlockSourceInputStreamTest extends BrambleMockTestCase {
private static final int MAX_DATA_BYTES = 1_000_000;
private static final int READ_BUFFER_BYTES = 4 * 1024;
private final BlockSource blockSource;
private final Random random = new Random();
private final Executor executor = newSingleThreadExecutor();
private final MessageId messageId = new MessageId(getRandomId());
public BlockSourceInputStreamTest() {
context.setThreadingPolicy(new Synchroniser());
blockSource = context.mock(BlockSource.class);
}
@Test
public void testReadSingleBytes() throws IOException {
byte[] data = createRandomData();
BlockSource source = new ByteArrayBlockSource(data, MAX_BLOCK_LENGTH);
InputStream in = new BlockSourceInputStream(MAX_BLOCK_LENGTH, executor,
source, messageId);
ByteArrayOutputStream out = new ByteArrayOutputStream();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < data.length; i++) {
int read = in.read();
assertNotEquals(-1, read);
out.write(read);
}
assertEquals(-1, in.read());
in.close();
out.flush();
out.close();
assertArrayEquals(data, out.toByteArray());
}
@Test
public void testReadByteArrays() throws IOException {
byte[] data = createRandomData();
BlockSource source = new ByteArrayBlockSource(data, MAX_BLOCK_LENGTH);
InputStream in = new BlockSourceInputStream(MAX_BLOCK_LENGTH, executor,
source, messageId);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[READ_BUFFER_BYTES];
int dataOffset = 0;
while (dataOffset < data.length) {
int length = Math.min(random.nextInt(buf.length) + 1,
data.length - dataOffset);
int bufOffset = 0;
if (length < buf.length)
bufOffset = random.nextInt(buf.length - length);
int read = in.read(buf, bufOffset, length);
assertNotEquals(-1, read);
out.write(buf, bufOffset, read);
dataOffset += read;
}
assertEquals(-1, in.read(buf, 0, 0));
in.close();
out.flush();
out.close();
assertArrayEquals(data, out.toByteArray());
}
@Test(expected = IOException.class)
public void testDbExceptionFromGetBlockCountIsRethrown() throws Exception {
context.checking(new Expectations() {{
oneOf(blockSource).getBlockCount(messageId);
will(throwException(new DbException()));
}});
InputStream in = new BlockSourceInputStream(MAX_BLOCK_LENGTH, executor,
blockSource, messageId);
//noinspection ResultOfMethodCallIgnored
in.read();
}
@Test(expected = IOException.class)
public void testDbExceptionFromGetBlockIsRethrown() throws Exception {
context.checking(new Expectations() {{
oneOf(blockSource).getBlockCount(messageId);
will(returnValue(1));
oneOf(blockSource).getBlock(messageId, 0);
will(throwException(new DbException()));
}});
InputStream in = new BlockSourceInputStream(MAX_BLOCK_LENGTH, executor,
blockSource, messageId);
//noinspection ResultOfMethodCallIgnored
in.read();
}
@Test
public void testReadFullBlockAtEndOfMessage() throws Exception {
testReadBlockAtEndOfMessage(MAX_BLOCK_LENGTH);
}
@Test
public void testReadPartialBlockAtEndOfMessage() throws Exception {
testReadBlockAtEndOfMessage(MAX_BLOCK_LENGTH - 1);
}
private void testReadBlockAtEndOfMessage(int blockLength) throws Exception {
byte[] block = new byte[blockLength];
random.nextBytes(block);
context.checking(new Expectations() {{
oneOf(blockSource).getBlockCount(messageId);
will(returnValue(1));
oneOf(blockSource).getBlock(messageId, 0);
will(returnValue(block));
}});
InputStream in = new BlockSourceInputStream(MAX_BLOCK_LENGTH, executor,
blockSource, messageId);
byte[] buf = new byte[MAX_BLOCK_LENGTH * 2];
assertEquals(block.length, in.read(buf, 0, buf.length));
assertArrayEquals(block, copyOfRange(buf, 0, block.length));
assertEquals(-1, in.read(buf, 0, buf.length));
}
private byte[] createRandomData() {
int length = random.nextInt(MAX_DATA_BYTES) + 1;
byte[] data = new byte[length];
random.nextBytes(data);
return data;
}
}

View File

@@ -0,0 +1,32 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.BlockSource;
import org.briarproject.bramble.api.sync.MessageId;
import static java.lang.System.arraycopy;
class ByteArrayBlockSource implements BlockSource {
private final byte[] data;
private final int blockBytes;
ByteArrayBlockSource(byte[] data, int blockBytes) {
this.data = data;
this.blockBytes = blockBytes;
}
@Override
public int getBlockCount(MessageId m) {
return (data.length + blockBytes - 1) / blockBytes;
}
@Override
public byte[] getBlock(MessageId m, int blockNumber) {
int offset = blockNumber * blockBytes;
if (offset >= data.length) throw new IllegalArgumentException();
int length = Math.min(blockBytes, data.length - offset);
byte[] block = new byte[length];
arraycopy(data, offset, block, 0, length);
return block;
}
}

View File

@@ -1,8 +1,6 @@
package org.briarproject.bramble.system;
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.Before;
import org.junit.Test;
@@ -10,48 +8,50 @@ import org.junit.Test;
import java.io.File;
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.assertNotNull;
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
public void setUp() {
testDir.mkdirs();
assumeTrue(isLinux() || isMac());
assertTrue(testDir.mkdirs());
}
@Test
public void testGetProviderWritesToRandomDeviceOnFirstCall()
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
File urandom = new File(testDir, "urandom");
urandom.delete();
if (urandom.exists()) assertTrue(urandom.delete());
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
LinuxSecureRandomProvider p = new LinuxSecureRandomProvider(urandom);
UnixSecureRandomProvider p = new UnixSecureRandomProvider(urandom);
// Getting a provider should write entropy to the file
Provider provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
assertEquals("UnixPRNG", provider.getName());
// There should be at least 16 bytes from the clock, 8 from the runtime
long length = urandom.length();
assertTrue(length >= 24);
// Getting another provider should not write to the file again
provider = p.getProvider();
assertNotNull(provider);
assertEquals("LinuxPRNG", provider.getName());
assertEquals("UnixPRNG", provider.getName());
assertEquals(length, urandom.length());
}
@After
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.TestUtils;
import org.briarproject.bramble.util.IoUtils;
import org.briarproject.bramble.util.OsUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -15,31 +14,33 @@ import java.io.FileOutputStream;
import java.util.HashSet;
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.assertEquals;
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 int SEED_BYTES = 32;
private final File testDir = TestUtils.getTestDirectory();
private final File testDir = getTestDirectory();
@Before
public void setUp() {
testDir.mkdirs();
assumeTrue(isLinux() || isMac());
assertTrue(testDir.mkdirs());
}
@Test
public void testSeedsAreDistinct() {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
Set<Bytes> seeds = new HashSet<>();
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi();
UnixSecureRandomSpi engine = new UnixSecureRandomSpi();
for (int i = 0; i < 1000; i++) {
byte[] seed = engine.engineGenerateSeed(SEED_BYTES);
assertEquals(SEED_BYTES, seed.length);
@@ -49,19 +50,15 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test
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
File urandom = new File(testDir, "urandom");
urandom.delete();
if (urandom.exists()) assertTrue(urandom.delete());
assertTrue(urandom.createNewFile());
assertEquals(0, urandom.length());
// Generate a seed
byte[] seed = TestUtils.getRandomBytes(SEED_BYTES);
// Check that the engine writes the seed to the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(RANDOM_DEVICE,
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(RANDOM_DEVICE,
urandom);
engine.engineSetSeed(seed);
assertEquals(SEED_BYTES, urandom.length());
@@ -74,15 +71,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test
public void testEngineNextBytesReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
if (urandom.exists()) assertTrue(urandom.delete());
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
@@ -90,7 +83,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = new byte[SEED_BYTES];
engine.engineNextBytes(b);
@@ -99,15 +92,11 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@Test
public void testEngineGenerateSeedReadsFromRandomDevice() throws Exception {
if (!(OsUtils.isLinux())) {
System.err.println("WARNING: Skipping test, can't run on this OS");
return;
}
// Generate some entropy
byte[] entropy = TestUtils.getRandomBytes(SEED_BYTES);
// Write the entropy to a file
File urandom = new File(testDir, "urandom");
urandom.delete();
if (urandom.exists()) assertTrue(urandom.delete());
FileOutputStream out = new FileOutputStream(urandom);
out.write(entropy);
out.flush();
@@ -115,7 +104,7 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
assertTrue(urandom.exists());
assertEquals(SEED_BYTES, urandom.length());
// Check that the engine reads from the file
LinuxSecureRandomSpi engine = new LinuxSecureRandomSpi(urandom,
UnixSecureRandomSpi engine = new UnixSecureRandomSpi(urandom,
RANDOM_DEVICE);
byte[] b = engine.engineGenerateSeed(SEED_BYTES);
assertArrayEquals(entropy, b);
@@ -123,6 +112,6 @@ public class LinuxSecureRandomSpiTest extends BrambleTestCase {
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
deleteTestDirectory(testDir);
}
}

View File

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

View File

@@ -15,7 +15,6 @@ import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.OsUtils;
import java.util.Collections;
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 java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@ThreadSafe
@NotNullByDefault
@@ -71,7 +71,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
// Locking: lock
private void initialise() {
if (OsUtils.isWindows()) {
if (isWindows()) {
new EventLoop().start();
} else {
LOG.warning("Windows shutdown manager used on non-Windows OS");
@@ -111,7 +111,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
public void run() {
try {
// Load user32.dll
User32 user32 = (User32) Native.loadLibrary("user32",
User32 user32 = Native.loadLibrary("user32",
User32.class, options);
// Create a callback to handle the WM_QUERYENDSESSION message
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 java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.CodeSource;
@@ -19,9 +18,9 @@ import java.util.concurrent.Executor;
import javax.net.SocketFactory;
@NotNullByDefault
class LinuxTorPlugin extends TorPlugin {
abstract class JavaTorPlugin extends TorPlugin {
LinuxTorPlugin(Executor ioExecutor, NetworkManager networkManager,
JavaTorPlugin(Executor ioExecutor, NetworkManager networkManager,
LocationUtils locationUtils, SocketFactory torSocketFactory,
Clock clock, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
@@ -34,17 +33,6 @@ class LinuxTorPlugin extends TorPlugin {
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
protected long getLastUpdateTime() {
CodeSource codeSource =
@@ -58,5 +46,4 @@ class LinuxTorPlugin extends TorPlugin {
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.net.SocketFactory;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux;
@Immutable
@NotNullByDefault
public class LinuxTorPluginFactory implements DuplexPluginFactory {
public class UnixTorPluginFactory implements DuplexPluginFactory {
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_IDLE_TIME = 30 * 1000; // 30 seconds
@@ -49,7 +50,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
private final Clock clock;
private final File torDirectory;
public LinuxTorPluginFactory(Executor ioExecutor,
public UnixTorPluginFactory(Executor ioExecutor,
NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory, ResourceProvider resourceProvider,
@@ -95,7 +96,7 @@ public class LinuxTorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
LinuxTorPlugin plugin = new LinuxTorPlugin(ioExecutor, networkManager,
UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager,
locationUtils, torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory);

View File

@@ -1,19 +1,24 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.OsUtils;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
@Module
public class DesktopSecureRandomModule {
@Provides
@Singleton
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 String bridge;
private LinuxTorPluginFactory factory;
private UnixTorPluginFactory factory;
public BridgeTest(String bridge) {
this.bridge = bridge;
@@ -108,7 +108,7 @@ public class BridgeTest extends BrambleTestCase {
return singletonList(bridge);
}
};
factory = new LinuxTorPluginFactory(ioExecutor, networkManager,
factory = new UnixTorPluginFactory(ioExecutor, networkManager,
locationUtils, eventBus, torSocketFactory, backoffFactory,
resourceProvider, bridgeProvider, batteryManager, clock,
torDir);
@@ -124,7 +124,7 @@ public class BridgeTest extends BrambleTestCase {
DuplexPlugin duplexPlugin =
factory.createPlugin(new TorPluginCallBack());
assertNotNull(duplexPlugin);
LinuxTorPlugin plugin = (LinuxTorPlugin) duplexPlugin;
UnixTorPlugin plugin = (UnixTorPlugin) duplexPlugin;
LOG.warning("Testing " + bridge);
try {

View File

@@ -104,6 +104,7 @@ dependencies {
}
implementation "com.android.support:cardview-v7:$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 "android.arch.lifecycle:extensions:1.1.1"
@@ -117,8 +118,15 @@ dependencies {
implementation 'com.google.zxing:core:3.3.3'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
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.github.bumptech.glide:compiler:$glideVersion"
compileOnly 'javax.annotation:jsr250-api:1.0'

View File

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

View File

@@ -113,6 +113,15 @@
/>
</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
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title"

View File

@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.briar.BriarCoreEagerSingletons;
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.reporting.BriarReportSender;
import org.briarproject.briar.android.view.TextInputView;
@@ -170,6 +171,8 @@ public interface AndroidComponent
void inject(TextInputView textInputView);
void inject(BriarModelLoader briarModelLoader);
// Eager singleton load
void inject(AppModule.EagerSingletons init);
}

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.RssFeedManageActivity;
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.ContactModule;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
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.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment;
@@ -110,6 +111,8 @@ public interface ActivityComponent {
void inject(ConversationActivity activity);
void inject(ImageActivity activity);
void inject(ForumInvitationActivity activity);
void inject(BlogInvitationActivity activity);

View File

@@ -2,12 +2,11 @@ package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.support.annotation.RequiresApi;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition;
import android.view.Gravity;
import android.view.Window;
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_PASSWORD;
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.isSamsung7;
@@ -111,21 +111,28 @@ public abstract class BriarActivity extends BaseActivity {
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
if (isSamsung7()) {
return;
}
Transition slide = new Slide(Gravity.RIGHT);
slide.excludeTarget(android.R.id.statusBarBackground, true);
slide.excludeTarget(android.R.id.navigationBarBackground, true);
if (enterTransition != null) excludeSystemUi(enterTransition);
if (exitTransition != null) excludeSystemUi(exitTransition);
if (returnTransition != null) excludeSystemUi(returnTransition);
Window window = getWindow();
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
window.setExitTransition(slide);
window.setEnterTransition(slide);
window.setTransitionBackgroundFadeDuration(getResources()
.getInteger(android.R.integer.config_longAnimTime));
window.setEnterTransition(enterTransition);
window.setExitTransition(exitTransition);
window.setReturnTransition(returnTransition);
}
/**

View File

@@ -18,7 +18,6 @@ public class ReblogActivity extends BriarActivity implements
@Override
public void onCreate(Bundle savedInstanceState) {
setSceneTransitionAnimation();
super.onCreate(savedInstanceState);
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.UiThread;
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.v7.app.AlertDialog;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
@@ -24,6 +28,7 @@ import android.widget.TextView;
import android.widget.Toast;
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.ContactManager;
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.BriarActivity;
import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.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.forum.ForumSharingManager;
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.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
@@ -92,8 +100,11 @@ import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
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.v7.util.SortedList.INVALID_POSITION;
import static android.view.Gravity.END;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort;
@@ -104,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.now;
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.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
@@ -116,7 +130,7 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
@ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity
implements EventListener, ConversationListener, TextInputListener,
TextCache {
TextCache, AttachmentCache {
public static final String CONTACT_ID = "briar.CONTACT_ID";
@@ -134,6 +148,7 @@ public class ConversationActivity extends BriarActivity
Executor cryptoExecutor;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private AttachmentController attachmentController;
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
@@ -143,6 +158,7 @@ public class ConversationActivity extends BriarActivity
private ImageView toolbarStatus;
private TextView toolbarTitle;
private BriarRecyclerView list;
private LinearLayoutManager layoutManager;
private TextInputView textInputView;
// Fields that are accessed from background threads must be volatile
@@ -180,7 +196,10 @@ public class ConversationActivity extends BriarActivity
@Override
public void onCreate(@Nullable Bundle state) {
setSceneTransitionAnimation();
if (SDK_INT >= 21) {
Transition slide = new Slide(END);
setSceneTransitionAnimation(slide, null, slide);
}
super.onCreate(state);
Intent i = getIntent();
@@ -191,6 +210,7 @@ public class ConversationActivity extends BriarActivity
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
viewModel.setContactId(contactId);
attachmentController = viewModel.getAttachmentController();
setContentView(R.layout.activity_conversation);
@@ -217,11 +237,12 @@ public class ConversationActivity extends BriarActivity
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this,
visitor = new ConversationVisitor(this, this, this,
viewModel.getContactDisplayName());
adapter = new ConversationAdapter(this, this);
list = findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
layoutManager = new LinearLayoutManager(this);
list.setLayoutManager(layoutManager);
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_private_messages));
@@ -340,14 +361,30 @@ public class ConversationActivity extends BriarActivity
// 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 &&
((PrivateMessageHeader) latest).hasText()) {
if (latest instanceof PrivateMessageHeader) {
MessageId id = latest.getId();
String text = textCache.get(id);
if (text == null) {
LOG.info("Eagerly loading text of latest message");
text = messagingManager.getMessageText(id);
textCache.put(id, text);
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);
}
}
}
}
@@ -408,16 +445,44 @@ public class ConversationActivity extends BriarActivity
private void displayMessageText(MessageId m, String text) {
runOnUiThreadUnlessDestroyed(() -> {
textCache.put(m, text);
SparseArray<ConversationMessageItem> messages =
adapter.getMessageItems();
for (int i = 0; i < messages.size(); i++) {
ConversationItem item = messages.valueAt(i);
if (item.getId().equals(m)) {
item.setText(text);
adapter.notifyItemChanged(messages.keyAt(i));
list.scrollToPosition(adapter.getItemCount() - 1);
return;
}
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
pair.getSecond().setText(text);
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst());
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
}
});
}
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);
}
});
}
@@ -466,10 +531,10 @@ public class ConversationActivity extends BriarActivity
private void addConversationItem(ConversationItem item) {
runOnUiThreadUnlessDestroyed(() -> {
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.incrementRevision();
adapter.add(item);
// Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1);
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
});
}
@@ -750,6 +815,31 @@ public class ConversationActivity extends BriarActivity
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
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException {
@@ -782,4 +872,16 @@ public class ConversationActivity extends BriarActivity
if (text == null) loadMessageText(m);
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.support.annotation.LayoutRes;
import android.support.v7.widget.LinearLayoutManager;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
@@ -98,16 +101,20 @@ class ConversationAdapter
return messages;
}
SparseArray<ConversationMessageItem> getMessageItems() {
SparseArray<ConversationMessageItem> messages = new SparseArray<>();
@Nullable
Pair<Integer, ConversationMessageItem> getMessageItem(MessageId messageId) {
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
if (item instanceof ConversationMessageItem) {
messages.put(i, (ConversationMessageItem) item);
if (item instanceof ConversationMessageItem &&
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.Nullable;
import android.support.annotation.UiThread;
import android.support.constraint.ConstraintLayout;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -18,11 +18,11 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
abstract class ConversationItemViewHolder extends ViewHolder {
protected final ViewGroup layout;
protected final ConstraintLayout layout;
@Nullable
private final OutItemViewHolder outViewHolder;
private final TextView text;
private final TextView time;
protected final TextView time;
ConversationItemViewHolder(View v, boolean isIncoming) {
super(v);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.conversation;
import android.support.annotation.UiThread;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -14,4 +15,7 @@ interface ConversationListener {
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 org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.util.List;
@@ -14,15 +13,20 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotNullByDefault
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);
this.attachments = h.getAttachmentHeaders();
this.attachments = attachments;
}
List<AttachmentHeader> getAttachments() {
List<AttachmentItem> getAttachments() {
return attachments;
}
void setAttachments(List<AttachmentItem> attachments) {
this.attachments = attachments;
}
}

View File

@@ -1,18 +1,161 @@
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.constraint.ConstraintSet;
import android.support.v4.content.ContextCompat;
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.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
@NotNullByDefault
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) {
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.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -38,6 +39,7 @@ public class ConversationViewModel extends AndroidViewModel {
@DatabaseExecutor
private final Executor dbExecutor;
private final ContactManager contactManager;
private final AttachmentController attachmentController;
@Nullable
private ContactId contactId = null;
@@ -52,10 +54,12 @@ public class ConversationViewModel extends AndroidViewModel {
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
ContactManager contactManager) {
ContactManager contactManager, MessagingManager messagingManager) {
super(application);
this.dbExecutor = dbExecutor;
this.contactManager = contactManager;
this.attachmentController = new AttachmentController(messagingManager,
application.getResources());
contactDeleted.setValue(false);
}
@@ -96,6 +100,10 @@ public class ConversationViewModel extends AndroidViewModel {
});
}
AttachmentController getAttachmentController() {
return attachmentController;
}
LiveData<Contact> getContact() {
return contact;
}

View File

@@ -14,12 +14,16 @@ import org.briarproject.briar.api.forum.ForumInvitationRequest;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.introduction.IntroductionRequest;
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.privategroup.invitation.GroupInvitationRequest;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
import java.util.List;
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.FORUM;
import static org.briarproject.briar.android.conversation.ConversationRequestItem.RequestType.GROUP;
@@ -33,24 +37,33 @@ class ConversationVisitor implements
private final Context ctx;
private final TextCache textCache;
private final AttachmentCache attachmentCache;
private final LiveData<String> contactName;
ConversationVisitor(Context ctx, TextCache textCache,
LiveData<String> contactName) {
AttachmentCache attachmentCache, LiveData<String> contactName) {
this.ctx = ctx;
this.textCache = textCache;
this.attachmentCache = attachmentCache;
this.contactName = contactName;
}
@Override
public ConversationItem visitPrivateMessageHeader(PrivateMessageHeader h) {
ConversationItem item;
List<AttachmentItem> attachments;
if (h.getAttachmentHeaders().isEmpty()) {
attachments = emptyList();
} else {
attachments = attachmentCache
.getAttachmentItems(h.getId(), h.getAttachmentHeaders());
}
if (h.isLocal()) {
item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_out, h);
R.layout.list_item_conversation_msg_out, h, attachments);
} else {
item = new ConversationMessageItem(
R.layout.list_item_conversation_msg_in, h);
R.layout.list_item_conversation_msg_in, h, attachments);
}
if (h.hasText()) {
String text = textCache.getText(h.getId());
@@ -279,4 +292,9 @@ class ConversationVisitor implements
@Nullable
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.MainThread;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
@@ -31,6 +32,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.URLSpan;
import android.transition.Transition;
import android.util.TypedValue;
import android.view.KeyEvent;
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.setDefaultNightMode;
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_RELATIVE;
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_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.WEEK_IN_MILLIS;
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
import static android.view.KeyEvent.ACTION_DOWN;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.inputmethod.EditorInfo.IME_NULL;
@@ -117,6 +123,13 @@ public class UiUtils {
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() {
long now = System.currentTimeMillis();
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
@@ -318,6 +331,12 @@ public class UiUtils {
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.
* 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:topRightRadius="@dimen/message_bubble_radius_top_outer"/>
<padding
android:bottom="@dimen/message_bubble_padding_bottom"
android:left="@dimen/message_bubble_padding_sides"
android:right="@dimen/message_bubble_padding_sides"
android:top="@dimen/message_bubble_padding_top"/>
android:bottom="@dimen/message_bubble_border"
android:left="@dimen/message_bubble_border"
android:right="@dimen/message_bubble_border"
android:top="@dimen/message_bubble_border"/>
<solid
android:color="@color/msg_in"/>
<stroke

View File

@@ -8,10 +8,10 @@
android:topLeftRadius="@dimen/message_bubble_radius_top_outer"
android:topRightRadius="@dimen/message_bubble_radius_top_inner"/>
<padding
android:bottom="@dimen/message_bubble_padding_bottom"
android:left="@dimen/message_bubble_padding_sides"
android:right="@dimen/message_bubble_padding_sides"
android:top="@dimen/message_bubble_padding_top"/>
android:bottom="@dimen/message_bubble_border"
android:left="@dimen/message_bubble_border"
android:right="@dimen/message_bubble_border"
android:top="@dimen/message_bubble_border"/>
<solid
android:color="@color/msg_out"/>
<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: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
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"
app:layout_constraintBottom_toTopOf="@+id/time"
app:layout_constraintEnd_toEndOf="@+id/time"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Short message"/>
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="The text of a message which can sometimes be a bit longer as well"/>
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
<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"
tools:text="Dec 24, 13:37"/>
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

@@ -21,39 +21,68 @@
android:background="@drawable/msg_out"
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"
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="@color/briar_text_primary_inverse"
app:layout_constraintBottom_toTopOf="@+id/time"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:textColor="@color/private_message_date_inverse"
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="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text"
tools:text="Dec 24, 13:37"/>
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginStart="@dimen/margin_medium"
app:layout_constraintBottom_toBottomOf="@+id/time"
app:layout_constraintStart_toEndOf="@+id/time"
app:layout_constraintTop_toTopOf="@+id/time"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
<TextView
android:id="@+id/time"
style="@style/TextMessage.Timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_marginRight="6dp"
android:textColor="@color/private_message_date_inverse"
tools:text="Dec 24, 13:37"/>
<ImageView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View File

@@ -40,6 +40,7 @@
<color name="msg_stroke_light">#cbcbcb</color>
<color name="msg_stroke_dark">#333333</color>
<color name="msg_stroke">@color/msg_stroke_light</color>
<color name="msg_status_bubble_background">#66000000</color>
<!-- text colors -->
<color name="briar_text_link">@color/briar_blue_light</color>

View File

@@ -43,9 +43,18 @@
<dimen name="message_bubble_radius_top_inner">@dimen/message_bubble_radius_small</dimen>
<dimen name="message_bubble_radius_top_outer">@dimen/message_bubble_radius_big</dimen>
<dimen name="message_bubble_margin">6dp</dimen>
<dimen name="message_bubble_image_default">210dp</dimen>
<dimen name="message_bubble_image_min_width">150dp</dimen>
<dimen name="message_bubble_image_max_width">240dp</dimen>
<dimen name="message_bubble_image_min_height">100dp</dimen>
<dimen name="message_bubble_image_max_height">320dp</dimen>
<dimen name="message_bubble_border">2dp</dimen>
<dimen name="message_bubble_padding_sides">12dp</dimen>
<dimen name="message_bubble_padding_sides_inner">10dp</dimen>
<dimen name="message_bubble_padding_top">6dp</dimen>
<dimen name="message_bubble_padding_top_inner">4dp</dimen>
<dimen name="message_bubble_padding_bottom">4dp</dimen>
<dimen name="message_bubble_padding_bottom_inner">2dp</dimen>
<dimen name="message_bubble_timestamp_margin">4dp</dimen>
<dimen name="message_bubble_elevation">2dp</dimen>
<dimen name="message_bubble_margin_tail">8dp</dimen>

View File

@@ -134,6 +134,8 @@
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
<string name="contact_deleted_toast">Contact deleted</string>
<!-- This is shown in the action bar when opening an image in fullscreen that the user sent -->
<string name="you">You</string>
<!-- Adding Contacts -->
<string name="add_contact_title">Add a Contact</string>

View File

@@ -18,6 +18,12 @@
<item name="toolbarStyle">@style/BriarToolbar</item>
</style>
<style name="BriarTheme.Transparent.NoActionBar" parent="BriarTheme.NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowActionBarOverlay">true</item>
</style>
<style name="ActivityAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/screen_new_in</item>
<item name="android:activityOpenExitAnimation">@anim/screen_old_out</item>

View File

@@ -36,6 +36,7 @@ dependencyVerification {
'com.android.support:design:28.0.0:design-28.0.0.aar:7874ad1904eedc74aa41cffffb7f759d8990056f3bbbc9264911651c67c42f5f',
'com.android.support:documentfile:28.0.0:documentfile-28.0.0.aar:47cdcd3e9302b7b064923f05487a5c03babbd9bbda4726b71e97791fab5d4779',
'com.android.support:drawerlayout:28.0.0:drawerlayout-28.0.0.aar:8f6809afae4793550c37461c9810e954ae6a23dbb4d23e5333bf18148df1150a',
'com.android.support:exifinterface:28.0.0:exifinterface-28.0.0.aar:bbf44e519edd6333a24a3285aa21fd00181b920b81ca8aa89a8899f03ab4d6b0',
'com.android.support:interpolator:28.0.0:interpolator-28.0.0.aar:7bc7ee86a0db39a4b51956f3e89842d2bd962118d57d779eb6ed6b34ba0677ea',
'com.android.support:loader:28.0.0:loader-28.0.0.aar:920b85efd72dc33e915b0f88a883fe73b88483c6df8751a741e17611f2460341',
'com.android.support:localbroadcastmanager:28.0.0:localbroadcastmanager-28.0.0.aar:d287c823af5fdde72c099fcfc5f630efe9687af7a914343ae6fd92de32c8a806',
@@ -84,6 +85,11 @@ dependencyVerification {
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
'com.github.bumptech.glide:annotations:4.8.0:annotations-4.8.0.jar:4ea82e59874673105165820336c6ac268fc46149892486aad8e7a131a4449446',
'com.github.bumptech.glide:compiler:4.8.0:compiler-4.8.0.jar:1fa93dd0cf7ef0b8b98a59a67a1ee84915416c2d677d83a771ea3e32ad15e6bf',
'com.github.bumptech.glide:gifdecoder:4.8.0:gifdecoder-4.8.0.aar:b00c5454a023a9488ea49603930d9c25e09192e5ceaadf64977aa52946b3c1b4',
'com.github.bumptech.glide:glide:4.8.0:glide-4.8.0.aar:5ddf08b12cc43332e812988f16c2c39e7fce49d1c4d94b7948dcde7f00bf49d6',
'com.github.chrisbanes:PhotoView:2.1.4:PhotoView-2.1.4.aar:04cb397fcb3df0757c8aed6927ebdd247930b5c78ee9acc59cd07dccdaaf3460',
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',

View File

@@ -9,6 +9,8 @@ import org.briarproject.briar.api.messaging.PrivateMessage;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import static java.util.Collections.emptyList;
@Immutable
@NotNullByDefault
public abstract class ThreadedMessage extends PrivateMessage {
@@ -19,7 +21,7 @@ public abstract class ThreadedMessage extends PrivateMessage {
public ThreadedMessage(Message message, @Nullable MessageId parent,
Author author) {
super(message);
super(message, emptyList());
this.parent = parent;
this.author = author;
}

View File

@@ -3,6 +3,8 @@ package org.briarproject.briar.api.messaging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Message;
import java.util.List;
import javax.annotation.concurrent.Immutable;
@Immutable
@@ -10,13 +12,18 @@ import javax.annotation.concurrent.Immutable;
public class PrivateMessage {
private final Message message;
private final List<AttachmentHeader> attachments;
public PrivateMessage(Message message) {
public PrivateMessage(Message message, List<AttachmentHeader> attachments) {
this.message = message;
this.attachments = attachments;
}
public Message getMessage() {
return message;
}
public List<AttachmentHeader> getAttachmentHeaders() {
return attachments;
}
}

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
@@ -201,8 +200,6 @@ class IntroductionManagerImpl extends ConversationClientImpl
}
// Store the updated session
storeSession(txn, storageId, session);
// FIXME
db.deleteMessage(txn, m.getId());
return false;
}
@@ -423,22 +420,15 @@ class IntroductionManagerImpl extends ConversationClientImpl
StoredSession ss = getSession(txn, meta.getSessionId());
if (ss == null) throw new AssertionError();
MessageType type = meta.getMessageType();
try {
if (type == REQUEST) {
messages.add(parseInvitationRequest(txn,
contactGroupId, m, meta, status,
meta.getSessionId(), authorInfos));
} else if (type == ACCEPT) {
messages.add(parseInvitationResponse(txn,
contactGroupId, m, meta, status,
ss.bdfSession, authorInfos, true));
} else if (type == DECLINE) {
messages.add(parseInvitationResponse(txn,
contactGroupId, m, meta, status,
ss.bdfSession, authorInfos, false));
}
} catch (MessageDeletedException ex) {
// FIXME
if (type == REQUEST) {
messages.add(parseInvitationRequest(txn, contactGroupId, m,
meta, status, meta.getSessionId(), authorInfos));
} else if (type == ACCEPT) {
messages.add(parseInvitationResponse(txn, contactGroupId, m,
meta, status, ss.bdfSession, authorInfos, true));
} else if (type == DECLINE) {
messages.add(parseInvitationResponse(txn, contactGroupId, m,
meta, status, ss.bdfSession, authorInfos, false));
}
}
return messages;

View File

@@ -11,13 +11,16 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
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.io.MessageInputStreamFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.Group.Visibility;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.MessageStatus;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
@@ -32,16 +35,17 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.PrivateMessageReceivedEvent;
import org.briarproject.briar.client.ConversationClientImpl;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
@Immutable
@@ -51,15 +55,21 @@ class MessagingManagerImpl extends ConversationClientImpl
private final ClientVersioningManager clientVersioningManager;
private final ContactGroupFactory contactGroupFactory;
private final MessageFactory messageFactory;
private final MessageInputStreamFactory messageInputStreamFactory;
@Inject
MessagingManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
ClientVersioningManager clientVersioningManager,
MetadataParser metadataParser, MessageTracker messageTracker,
ContactGroupFactory contactGroupFactory) {
ContactGroupFactory contactGroupFactory,
MessageFactory messageFactory,
MessageInputStreamFactory messageInputStreamFactory) {
super(db, clientHelper, metadataParser, messageTracker);
this.clientVersioningManager = clientVersioningManager;
this.contactGroupFactory = contactGroupFactory;
this.messageFactory = messageFactory;
this.messageInputStreamFactory = messageInputStreamFactory;
}
@Override
@@ -127,8 +137,7 @@ class MessagingManagerImpl extends ConversationClientImpl
new PrivateMessageReceivedEvent(header, contactId);
txn.attach(event);
messageTracker.trackIncomingMessage(txn, m);
// FIXME
db.deleteMessage(txn, m.getId());
// don't share message
return false;
}
@@ -143,9 +152,11 @@ class MessagingManagerImpl extends ConversationClientImpl
meta.put("read", true);
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
messageTracker.trackOutgoingMessage(txn, m.getMessage());
for (AttachmentHeader h : m.getAttachmentHeaders())
db.setMessageShared(txn, h.getMessageId());
db.commitTransaction(txn);
} catch (FormatException e) {
throw new RuntimeException(e);
throw new AssertionError(e);
} finally {
db.endTransaction(txn);
}
@@ -153,11 +164,16 @@ class MessagingManagerImpl extends ConversationClientImpl
@Override
public AttachmentHeader addLocalAttachment(GroupId groupId, long timestamp,
String contentType, ByteBuffer data) {
// TODO add real implementation
byte[] b = new byte[MessageId.LENGTH];
new Random().nextBytes(b);
return new AttachmentHeader(new MessageId(b), "image/png");
String contentType, ByteBuffer data) throws DbException {
// TODO: Remove this restriction when large messages are supported
byte[] body = data.array();
if (body.length > MAX_MESSAGE_LENGTH) throw new DbException();
// TODO: Store message type and content type
Message m = messageFactory.createMessage(groupId, timestamp, body);
Metadata meta = new Metadata();
// Attachment will be shared when private message is added
db.transaction(false, txn -> db.addLocalMessage(txn, m, meta, false));
return new AttachmentHeader(m.getId(), contentType);
}
private ContactId getContactId(Transaction txn, GroupId g)
@@ -237,8 +253,9 @@ class MessagingManagerImpl extends ConversationClientImpl
@Override
public Attachment getAttachment(MessageId m) {
// TODO add real implementation
throw new IllegalStateException("Not yet implemented");
InputStream in = messageInputStreamFactory.getMessageInputStream(m);
// TODO: Read message type and content type
return new Attachment(in);
}
}

View File

@@ -39,6 +39,6 @@ class PrivateMessageFactoryImpl implements PrivateMessageFactory {
// Serialise the message
BdfList message = BdfList.of(text);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new PrivateMessage(m);
return new PrivateMessage(m, attachments);
}
}

View File

@@ -32,10 +32,6 @@ import javax.annotation.concurrent.Immutable;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.MAJOR_VERSION;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_DESCRIPTOR;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_SIGNATURE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
@@ -200,29 +196,20 @@ abstract class AbstractProtocolEngine<S extends Session>
void subscribeToPrivateGroup(Transaction txn, MessageId inviteId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getMessageMetadataAsDictionary(txn, inviteId);
GroupId groupId = new GroupId(meta.getRaw(MSG_KEY_PRIVATE_GROUP_ID));
byte[] descriptor = meta.getRaw(MSG_KEY_DESCRIPTOR);
byte[] signature = meta.getRaw(MSG_KEY_SIGNATURE);
long inviteTimestamp = meta.getLong(MSG_KEY_TIMESTAMP);
Group g = getPrivateGroup(groupId, descriptor);
PrivateGroup privateGroup = privateGroupFactory.parsePrivateGroup(g);
InviteMessage invite = messageParser.getInviteMessage(txn, inviteId);
PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
invite.getGroupName(), invite.getCreator(), invite.getSalt());
long timestamp =
Math.max(clock.currentTimeMillis(), inviteTimestamp + 1);
Math.max(clock.currentTimeMillis(), invite.getTimestamp() + 1);
// TODO: Create the join message on the crypto executor
LocalAuthor member = identityManager.getLocalAuthor(txn);
GroupMessage joinMessage = groupMessageFactory.createJoinMessage(
privateGroup.getId(), timestamp, member, inviteTimestamp,
signature);
privateGroup.getId(), timestamp, member, invite.getTimestamp(),
invite.getSignature());
privateGroupManager
.addPrivateGroup(txn, privateGroup, joinMessage, false);
}
private Group getPrivateGroup(GroupId id, byte[] descriptor) {
return new Group(id, CLIENT_ID, MAJOR_VERSION, descriptor);
}
long getLocalTimestamp(S session) {
return Math.max(clock.currentTimeMillis(),
Math.max(session.getLocalTimestamp(),

View File

@@ -8,8 +8,6 @@ interface GroupInvitationConstants {
// Message metadata keys
String MSG_KEY_MESSAGE_TYPE = "messageType";
String MSG_KEY_PRIVATE_GROUP_ID = "privateGroupId";
String MSG_KEY_DESCRIPTOR = "descriptor";
String MSG_KEY_SIGNATURE = "signature";
String MSG_KEY_TIMESTAMP = "timestamp";
String MSG_KEY_LOCAL = "local";
String MSG_KEY_VISIBLE_IN_UI = "visibleInUi";

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MessageDeletedException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
@@ -168,8 +167,6 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
}
// Store the updated session
storeSession(txn, storageId, session);
// FIXME
db.deleteMessage(txn, m.getId());
return false;
}
@@ -387,19 +384,15 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
messageParser.parseMetadata(e.getValue());
MessageStatus status = db.getMessageStatus(txn, c, m);
MessageType type = meta.getMessageType();
try {
if (type == INVITE) {
messages.add(parseInvitationRequest(txn,
contactGroupId, m, meta, status));
} else if (type == JOIN) {
messages.add(parseInvitationResponse(contactGroupId, m,
meta, status, true));
} else if (type == LEAVE) {
messages.add(parseInvitationResponse(contactGroupId, m,
meta, status, false));
}
} catch (MessageDeletedException ex) {
// FIXME
if (type == INVITE) {
messages.add(parseInvitationRequest(txn, contactGroupId, m,
meta, status));
} else if (type == JOIN) {
messages.add(parseInvitationResponse(contactGroupId, m,
meta, status, true));
} else if (type == LEAVE) {
messages.add(parseInvitationResponse(contactGroupId, m,
meta, status, false));
}
}
return messages;
@@ -448,13 +441,8 @@ class GroupInvitationManagerImpl extends ConversationClientImpl
Map<MessageId, BdfDictionary> results =
clientHelper.getMessageMetadataAsDictionary(txn,
contactGroupId, query);
for (MessageId m : results.keySet()) {
try {
items.add(parseGroupInvitationItem(txn, c, m));
} catch (MessageDeletedException e) {
// FIXME
}
}
for (MessageId m : results.keySet())
items.add(parseGroupInvitationItem(txn, c, m));
}
db.commitTransaction(txn);
} catch (FormatException e) {

View File

@@ -31,6 +31,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
@@ -99,9 +100,9 @@ class GroupInvitationValidator extends BdfMessageValidator {
throw new FormatException();
}
// Create the metadata
BdfDictionary meta = messageEncoder.encodeInviteMetadata(
privateGroup.getId(), privateGroup.getGroup().getDescriptor(),
signature, m.getTimestamp(), false, false, false, false, false);
BdfDictionary meta = messageEncoder.encodeMetadata(INVITE,
privateGroup.getId(), m.getTimestamp(), false, false, false,
false, false);
return new BdfMessageContext(meta);
}

View File

@@ -12,10 +12,6 @@ import javax.annotation.Nullable;
@NotNullByDefault
interface MessageEncoder {
BdfDictionary encodeInviteMetadata(GroupId privateGroupId,
byte[] descriptor, byte[] signature, long timestamp, boolean local,
boolean read, boolean visible, boolean available, boolean accepted);
BdfDictionary encodeMetadata(MessageType type, GroupId privateGroupId,
long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted);

View File

@@ -17,12 +17,10 @@ import javax.inject.Inject;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_DESCRIPTOR;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_LOCAL;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_MESSAGE_TYPE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_PRIVATE_GROUP_ID;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_SIGNATURE;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_TIMESTAMP;
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_VISIBLE_IN_UI;
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
@@ -44,25 +42,6 @@ class MessageEncoderImpl implements MessageEncoder {
this.messageFactory = messageFactory;
}
@Override
public BdfDictionary encodeInviteMetadata(GroupId privateGroupId,
byte[] descriptor, byte[] signature, long timestamp, boolean local,
boolean read, boolean visible, boolean available,
boolean accepted) {
BdfDictionary meta = new BdfDictionary();
meta.put(MSG_KEY_MESSAGE_TYPE, INVITE.getValue());
meta.put(MSG_KEY_PRIVATE_GROUP_ID, privateGroupId);
meta.put(MSG_KEY_DESCRIPTOR, descriptor);
meta.put(MSG_KEY_SIGNATURE, signature);
meta.put(MSG_KEY_TIMESTAMP, timestamp);
meta.put(MSG_KEY_LOCAL, local);
meta.put(MSG_KEY_READ, read);
meta.put(MSG_KEY_VISIBLE_IN_UI, visible);
meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
meta.put(MSG_KEY_INVITATION_ACCEPTED, accepted);
return meta;
}
@Override
public BdfDictionary encodeMetadata(MessageType type,
GroupId privateGroupId, long timestamp, boolean local, boolean read,

View File

@@ -3,8 +3,6 @@ package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
@@ -27,7 +25,6 @@ import javax.inject.Inject;
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
import static org.briarproject.briar.api.blog.BlogManager.MAJOR_VERSION;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_DESCRIPTOR;
@Immutable
@NotNullByDefault
@@ -87,10 +84,9 @@ class BlogProtocolEngineImpl extends ProtocolEngineImpl<Blog> {
@Override
protected void addShareable(Transaction txn, MessageId inviteId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getMessageMetadataAsDictionary(txn, inviteId);
BdfList descriptor = meta.getList(MSG_KEY_DESCRIPTOR);
blogManager.addBlog(txn, messageParser.createShareable(descriptor));
InviteMessage<Blog> invite =
messageParser.getInviteMessage(txn, inviteId);
blogManager.addBlog(txn, invite.getShareable());
}
}

View File

@@ -3,8 +3,6 @@ package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
@@ -15,19 +13,18 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.versioning.ClientVersioningManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.conversation.ConversationRequest;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.briar.api.conversation.ConversationRequest;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
import static org.briarproject.briar.api.forum.ForumManager.MAJOR_VERSION;
import static org.briarproject.briar.sharing.SharingConstants.MSG_KEY_DESCRIPTOR;
@Immutable
@NotNullByDefault
@@ -88,10 +85,9 @@ class ForumProtocolEngineImpl extends ProtocolEngineImpl<Forum> {
@Override
protected void addShareable(Transaction txn, MessageId inviteId)
throws DbException, FormatException {
BdfDictionary meta =
clientHelper.getMessageMetadataAsDictionary(txn, inviteId);
BdfList descriptor = meta.getList(MSG_KEY_DESCRIPTOR);
forumManager.addForum(txn, messageParser.createShareable(descriptor));
InviteMessage<Forum> invite =
messageParser.getInviteMessage(txn, inviteId);
forumManager.addForum(txn, invite.getShareable());
}
}

View File

@@ -12,10 +12,6 @@ import javax.annotation.Nullable;
@NotNullByDefault
interface MessageEncoder {
BdfDictionary encodeInviteMetadata(GroupId shareableId, BdfList descriptor,
long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted);
BdfDictionary encodeMetadata(MessageType type, GroupId shareableId,
long timestamp, boolean local, boolean read, boolean visible,
boolean available, boolean accepted);

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