mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
32 Commits
tor-probe
...
41-alias-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68738a5a03 | ||
|
|
1a025d0f40 | ||
|
|
a3593ea8ca | ||
|
|
34eaedbd63 | ||
|
|
44e1ce32ce | ||
|
|
7af4b3d3ca | ||
|
|
1423ca7a15 | ||
|
|
baf64e1129 | ||
|
|
88adfabe09 | ||
|
|
969150bff0 | ||
|
|
8fc622f85d | ||
|
|
22eed91019 | ||
|
|
fcb88ed58c | ||
|
|
0d940fc7d7 | ||
|
|
53da13794f | ||
|
|
2ab03f48cc | ||
|
|
436f45554d | ||
|
|
51209b5eec | ||
|
|
822597b4c6 | ||
|
|
7c01bc59c0 | ||
|
|
825d342f9b | ||
|
|
34955fecbb | ||
|
|
5c28b60a6b | ||
|
|
9c4fb4fd34 | ||
|
|
3d6a336f6d | ||
|
|
4b7a81177c | ||
|
|
9515e93857 | ||
|
|
efe15df940 | ||
|
|
de611857cf | ||
|
|
8935ec2c2e | ||
|
|
bd00fb1c04 | ||
|
|
3192015cfd |
@@ -16,18 +16,27 @@ import org.briarproject.bramble.api.plugin.PluginException;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
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.AndroidUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_STARTED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
@@ -37,8 +46,13 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
|
|||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
|
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
|
||||||
|
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 java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -47,8 +61,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||||
|
|
||||||
|
private static final int MAX_DISCOVERY_MS = 10_000;
|
||||||
|
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
private volatile boolean wasEnabledByUs = false;
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
private volatile BluetoothStateReceiver receiver = null;
|
||||||
@@ -58,12 +75,13 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
|
|
||||||
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
Executor ioExecutor, AndroidExecutor androidExecutor,
|
Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
Context appContext, SecureRandom secureRandom, Clock clock,
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -182,6 +200,74 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||||
|
if (adapter == null) return null;
|
||||||
|
for (String address : discoverDevices()) {
|
||||||
|
try {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||||
|
return connectTo(address, uuid);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Could not connect to "
|
||||||
|
+ scrubMacAddress(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Could not connect to any devices");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<String> discoverDevices() {
|
||||||
|
List<String> addresses = new ArrayList<>();
|
||||||
|
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
|
||||||
|
DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_DISCOVERY_STARTED);
|
||||||
|
filter.addAction(ACTION_DISCOVERY_FINISHED);
|
||||||
|
filter.addAction(ACTION_FOUND);
|
||||||
|
appContext.registerReceiver(receiver, filter);
|
||||||
|
try {
|
||||||
|
if (adapter.startDiscovery()) {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long end = now + MAX_DISCOVERY_MS;
|
||||||
|
while (now < end) {
|
||||||
|
Intent i = intents.poll(end - now, MILLISECONDS);
|
||||||
|
if (i == null) break;
|
||||||
|
String action = i.getAction();
|
||||||
|
if (ACTION_DISCOVERY_STARTED.equals(action)) {
|
||||||
|
LOG.info("Discovery started");
|
||||||
|
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
|
||||||
|
LOG.info("Discovery finished");
|
||||||
|
break;
|
||||||
|
} else if (ACTION_FOUND.equals(action)) {
|
||||||
|
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
|
||||||
|
String address = d.getAddress();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Discovered " + scrubMacAddress(address));
|
||||||
|
if (!addresses.contains(address))
|
||||||
|
addresses.add(address);
|
||||||
|
}
|
||||||
|
now = clock.currentTimeMillis();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Could not start discovery");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.info("Interrupted while discovering devices");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
LOG.info("Cancelling discovery");
|
||||||
|
adapter.cancelDiscovery();
|
||||||
|
appContext.unregisterReceiver(receiver);
|
||||||
|
}
|
||||||
|
// Shuffle the addresses so we don't always try the same one first
|
||||||
|
Collections.shuffle(addresses);
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
private void tryToClose(@Nullable Closeable c) {
|
||||||
try {
|
try {
|
||||||
if (c != null) c.close();
|
if (c != null) c.close();
|
||||||
@@ -207,4 +293,18 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DiscoveryReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
private final BlockingQueue<Intent> intents;
|
||||||
|
|
||||||
|
private DiscoveryReceiver(BlockingQueue<Intent> intents) {
|
||||||
|
this.intents = intents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
intents.add(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -33,17 +34,19 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
private final Clock clock;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||||
AndroidExecutor androidExecutor, Context appContext,
|
AndroidExecutor androidExecutor, Context appContext,
|
||||||
SecureRandom secureRandom, EventBus eventBus,
|
SecureRandom secureRandom, EventBus eventBus, Clock clock,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
|
this.clock = clock;
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
|
||||||
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
connectionLimiter, ioExecutor, androidExecutor, appContext,
|
||||||
secureRandom, backoff, callback, MAX_LATENCY);
|
secureRandom, clock, backoff, callback, MAX_LATENCY);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.tor;
|
|
||||||
|
|
||||||
import org.spongycastle.crypto.tls.Certificate;
|
|
||||||
import org.spongycastle.crypto.tls.CipherSuite;
|
|
||||||
import org.spongycastle.crypto.tls.DefaultTlsClient;
|
|
||||||
import org.spongycastle.crypto.tls.ServerOnlyTlsAuthentication;
|
|
||||||
import org.spongycastle.crypto.tls.TlsAuthentication;
|
|
||||||
import org.spongycastle.crypto.tls.TlsClientProtocol;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
|
|
||||||
public class TorProbe {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(TorProbe.class.getName());
|
|
||||||
|
|
||||||
private static final int READ_TIMEOUT = 10 * 1000;
|
|
||||||
|
|
||||||
// https://trac.torproject.org/projects/tor/wiki/org/projects/Tor/TLSHistory
|
|
||||||
private static final int SSL3_RSA_FIPS_WITH_3DES_EDE_CBC_SHA = 0xfeff;
|
|
||||||
|
|
||||||
// https://gitweb.torproject.org/torspec.git/tree/tor-spec.txt#n347
|
|
||||||
private static final int[] TOR_CIPHER_SUITES = new int[] {
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_RSA_WITH_RC4_128_MD5,
|
|
||||||
CipherSuite.TLS_RSA_WITH_RC4_128_SHA,
|
|
||||||
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
SSL3_RSA_FIPS_WITH_3DES_EDE_CBC_SHA,
|
|
||||||
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://gitweb.torproject.org/torspec.git/tree/tor-spec.txt#n412
|
|
||||||
private static final byte[] VERSIONS_CELL = new byte[] {
|
|
||||||
0x00, 0x00, // Circuit ID: 0
|
|
||||||
0x07, // Command: Versions
|
|
||||||
0x00, 0x06, // Payload length: 6 bytes
|
|
||||||
0x00, 0x03, 0x00, 0x04, 0x00, 0x05 // Supported versions: 3, 4, 5
|
|
||||||
};
|
|
||||||
|
|
||||||
public List<Integer> probe(String address, int port) throws IOException {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to " + address + ":" + port);
|
|
||||||
Socket socket = new Socket(address, port);
|
|
||||||
LOG.info("Connected");
|
|
||||||
TlsClientProtocol client = new TlsClientProtocol(
|
|
||||||
socket.getInputStream(), socket.getOutputStream(),
|
|
||||||
new SecureRandom());
|
|
||||||
client.connect(new TorTlsClient());
|
|
||||||
LOG.info("TLS handshake succeeded");
|
|
||||||
socket.setSoTimeout(READ_TIMEOUT);
|
|
||||||
try {
|
|
||||||
// Send a versions cell
|
|
||||||
OutputStream out = client.getOutputStream();
|
|
||||||
out.write(VERSIONS_CELL);
|
|
||||||
out.flush();
|
|
||||||
LOG.info("Sent versions cell");
|
|
||||||
|
|
||||||
// Expect a versions cell in response
|
|
||||||
List<Integer> versions = new ArrayList<>();
|
|
||||||
DataInputStream in = new DataInputStream(client.getInputStream());
|
|
||||||
int circuitId = in.readUnsignedShort();
|
|
||||||
if (circuitId != 0)
|
|
||||||
throw new IOException("Unexpected circuit ID: " + circuitId);
|
|
||||||
int command = in.readUnsignedByte();
|
|
||||||
if (command != 7)
|
|
||||||
throw new IOException("Unexpected command: " + command);
|
|
||||||
int payloadLength = in.readUnsignedShort();
|
|
||||||
if (payloadLength == 0 || payloadLength % 2 != 0) {
|
|
||||||
throw new IOException("Invalid payload length: "
|
|
||||||
+ payloadLength);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < payloadLength / 2; i++) {
|
|
||||||
int version = in.readUnsignedShort();
|
|
||||||
versions.add(version);
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Supported versions: " + versions);
|
|
||||||
return versions;
|
|
||||||
} finally {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
if (args.length != 2) {
|
|
||||||
System.err.println("Usage: TorProbe <address> <port>");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
String address = args[0];
|
|
||||||
int port = Integer.parseInt(args[1]);
|
|
||||||
new TorProbe().probe(address, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TorTlsClient extends DefaultTlsClient {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TlsAuthentication getAuthentication() {
|
|
||||||
return new ServerOnlyTlsAuthentication() {
|
|
||||||
@Override
|
|
||||||
public void notifyServerCertificate(Certificate cert)
|
|
||||||
throws IOException {
|
|
||||||
LOG.info("Received server certificate");
|
|
||||||
org.spongycastle.asn1.x509.Certificate[] chain =
|
|
||||||
cert.getCertificateList();
|
|
||||||
if (chain.length != 1)
|
|
||||||
throw new IOException("Certificate is not self-signed");
|
|
||||||
for (org.spongycastle.asn1.x509.Certificate c : chain) {
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Subject: " + c.getSubject());
|
|
||||||
LOG.info("Issuer: " + c.getIssuer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getCipherSuites() {
|
|
||||||
return TOR_CIPHER_SUITES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,12 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class Contact {
|
public class Contact {
|
||||||
@@ -13,13 +17,21 @@ public class Contact {
|
|||||||
private final ContactId id;
|
private final ContactId id;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final AuthorId localAuthorId;
|
private final AuthorId localAuthorId;
|
||||||
|
@Nullable
|
||||||
|
private final String alias;
|
||||||
private final boolean verified, active;
|
private final boolean verified, active;
|
||||||
|
|
||||||
public Contact(ContactId id, Author author, AuthorId localAuthorId,
|
public Contact(ContactId id, Author author, AuthorId localAuthorId,
|
||||||
boolean verified, boolean active) {
|
@Nullable String alias, boolean verified, boolean active) {
|
||||||
|
if (alias != null) {
|
||||||
|
int aliasLength = toUtf8(alias).length;
|
||||||
|
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.localAuthorId = localAuthorId;
|
this.localAuthorId = localAuthorId;
|
||||||
|
this.alias = alias;
|
||||||
this.verified = verified;
|
this.verified = verified;
|
||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
}
|
||||||
@@ -36,6 +48,11 @@ public class Contact {
|
|||||||
return localAuthorId;
|
return localAuthorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isVerified() {
|
public boolean isVerified() {
|
||||||
return verified;
|
return verified;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ContactManager {
|
public interface ContactManager {
|
||||||
|
|
||||||
@@ -93,6 +96,12 @@ public interface ContactManager {
|
|||||||
void setContactActive(Transaction txn, ContactId c, boolean active)
|
void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an alias name for the contact or unsets it if alias is null.
|
||||||
|
*/
|
||||||
|
void setContactAlias(ContactId c, @Nullable String alias)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if a contact with this name and public key already exists
|
* Return true if a contact with this name and public key already exists
|
||||||
*/
|
*/
|
||||||
@@ -105,6 +114,16 @@ public interface ContactManager {
|
|||||||
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
boolean contactExists(AuthorId remoteAuthorId, AuthorId localAuthorId)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorInfo} for the given author.
|
||||||
|
*/
|
||||||
|
AuthorInfo getAuthorInfo(AuthorId a) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AuthorInfo} for the given author.
|
||||||
|
*/
|
||||||
|
AuthorInfo getAuthorInfo(Transaction txn, AuthorId a) throws DbException;
|
||||||
|
|
||||||
interface ContactHook {
|
interface ContactHook {
|
||||||
|
|
||||||
void addingContact(Transaction txn, Contact c) throws DbException;
|
void addingContact(Transaction txn, Contact c) throws DbException;
|
||||||
|
|||||||
@@ -515,6 +515,12 @@ public interface DatabaseComponent {
|
|||||||
void setContactActive(Transaction txn, ContactId c, boolean active)
|
void setContactActive(Transaction txn, ContactId c, boolean active)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an alias name for the contact or unsets it if alias is null.
|
||||||
|
*/
|
||||||
|
void setContactAlias(Transaction txn, ContactId c, @Nullable String alias)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the given group's visibility to the given contact.
|
* Sets the given group's visibility to the given contact.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class Author implements Nameable {
|
public class Author implements Nameable {
|
||||||
|
|
||||||
public enum Status {
|
|
||||||
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the author structure.
|
* The current version of the author structure.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.briarproject.bramble.api.identity;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AuthorInfo {
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES;
|
||||||
|
|
||||||
|
public boolean isContact() {
|
||||||
|
return this == UNVERIFIED || this == VERIFIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Status status;
|
||||||
|
@Nullable
|
||||||
|
private final String alias;
|
||||||
|
|
||||||
|
public AuthorInfo(Status status, @Nullable String alias) {
|
||||||
|
this.status = status;
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorInfo(Status status) {
|
||||||
|
this(status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Status getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.identity;
|
|||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -37,14 +36,4 @@ public interface IdentityManager {
|
|||||||
*/
|
*/
|
||||||
LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
|
LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Status} of the given author.
|
|
||||||
*/
|
|
||||||
Status getAuthorStatus(AuthorId a) throws DbException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Status} of the given author.
|
|
||||||
*/
|
|
||||||
Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public interface KeyAgreementConstants {
|
|||||||
/**
|
/**
|
||||||
* The connection timeout in milliseconds.
|
* The connection timeout in milliseconds.
|
||||||
*/
|
*/
|
||||||
long CONNECTION_TIMEOUT = 20 * 1000;
|
long CONNECTION_TIMEOUT = 60_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transport identifier for Bluetooth.
|
* The transport identifier for Bluetooth.
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
|
|
||||||
@@ -18,21 +21,32 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ContactManagerImpl implements ContactManager {
|
class ContactManagerImpl implements ContactManager {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final KeyManager keyManager;
|
private final KeyManager keyManager;
|
||||||
|
private final IdentityManager identityManager;
|
||||||
private final List<ContactHook> hooks;
|
private final List<ContactHook> hooks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
|
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager,
|
||||||
|
IdentityManager identityManager) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
|
this.identityManager = identityManager;
|
||||||
hooks = new CopyOnWriteArrayList<>();
|
hooks = new CopyOnWriteArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +162,17 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
db.setContactActive(txn, c, active);
|
db.setContactActive(txn, c, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContactAlias(ContactId c, @Nullable String alias)
|
||||||
|
throws DbException {
|
||||||
|
if (alias != null) {
|
||||||
|
int aliasLength = toUtf8(alias).length;
|
||||||
|
if (aliasLength == 0 || aliasLength > MAX_AUTHOR_NAME_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
db.transaction(false, txn -> db.setContactAlias(txn, c, alias));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
|
||||||
AuthorId localAuthorId) throws DbException {
|
AuthorId localAuthorId) throws DbException {
|
||||||
@@ -176,4 +201,23 @@ class ContactManagerImpl implements ContactManager {
|
|||||||
db.removeContact(txn, c);
|
db.removeContact(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthorInfo getAuthorInfo(AuthorId a) throws DbException {
|
||||||
|
return db.transactionWithResult(true, txn -> getAuthorInfo(txn, a));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthorInfo getAuthorInfo(Transaction txn, AuthorId authorId)
|
||||||
|
throws DbException {
|
||||||
|
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||||
|
if (localAuthor.getId().equals(authorId))
|
||||||
|
return new AuthorInfo(OURSELVES);
|
||||||
|
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
|
||||||
|
if (contacts.isEmpty()) return new AuthorInfo(UNKNOWN);
|
||||||
|
if (contacts.size() > 1) throw new AssertionError();
|
||||||
|
Contact c = contacts.iterator().next();
|
||||||
|
if (c.isVerified()) return new AuthorInfo(VERIFIED, c.getAlias());
|
||||||
|
else return new AuthorInfo(UNVERIFIED, c.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -616,6 +616,12 @@ interface Database<T> {
|
|||||||
void setContactActive(T txn, ContactId c, boolean active)
|
void setContactActive(T txn, ContactId c, boolean active)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an alias name for a contact.
|
||||||
|
*/
|
||||||
|
void setContactAlias(T txn, ContactId c, @Nullable String alias)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the given group's visibility to the given contact to either
|
* Sets the given group's visibility to the given contact to either
|
||||||
* {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
* {@link Visibility VISIBLE} or {@link Visibility SHARED}.
|
||||||
|
|||||||
@@ -859,6 +859,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new ContactStatusChangedEvent(c, active));
|
transaction.attach(new ContactStatusChangedEvent(c, active));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContactAlias(Transaction transaction, ContactId c,
|
||||||
|
String alias) throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
db.setContactAlias(txn, c, alias);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setGroupVisibility(Transaction transaction, ContactId c,
|
public void setGroupVisibility(Transaction transaction, ContactId c,
|
||||||
GroupId g, Visibility v) throws DbException {
|
GroupId g, Visibility v) throws DbException {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
class DatabaseTypes {
|
||||||
|
|
||||||
|
private final String hashType, secretType, binaryType;
|
||||||
|
private final String counterType, stringType;
|
||||||
|
|
||||||
|
public DatabaseTypes(String hashType, String secretType, String binaryType,
|
||||||
|
String counterType, String stringType) {
|
||||||
|
this.hashType = hashType;
|
||||||
|
this.secretType = secretType;
|
||||||
|
this.binaryType = binaryType;
|
||||||
|
this.counterType = counterType;
|
||||||
|
this.stringType = stringType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces database type placeholders in a statement with the actual types.
|
||||||
|
* These placeholders are currently supported:
|
||||||
|
* <li> _HASH
|
||||||
|
* <li> _SECRET
|
||||||
|
* <li> _BINARY
|
||||||
|
* <li> _COUNTER
|
||||||
|
* <li> _STRING
|
||||||
|
*/
|
||||||
|
String replaceTypes(String s) {
|
||||||
|
s = s.replaceAll("_HASH", hashType);
|
||||||
|
s = s.replaceAll("_SECRET", secretType);
|
||||||
|
s = s.replaceAll("_BINARY", binaryType);
|
||||||
|
s = s.replaceAll("_COUNTER", counterType);
|
||||||
|
s = s.replaceAll("_STRING", stringType);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ class H2Database extends JdbcDatabase {
|
|||||||
private static final String BINARY_TYPE = "BINARY";
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
||||||
private static final String STRING_TYPE = "VARCHAR";
|
private static final String STRING_TYPE = "VARCHAR";
|
||||||
|
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
|
||||||
|
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
private final DatabaseConfig config;
|
||||||
private final String url;
|
private final String url;
|
||||||
@@ -40,8 +42,7 @@ class H2Database extends JdbcDatabase {
|
|||||||
@Inject
|
@Inject
|
||||||
H2Database(DatabaseConfig config, MessageFactory messageFactory,
|
H2Database(DatabaseConfig config, MessageFactory messageFactory,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
super(dbTypes, messageFactory, clock);
|
||||||
messageFactory, clock);
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
File dir = config.getDatabaseDirectory();
|
File dir = config.getDatabaseDirectory();
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
String path = new File(dir, "db").getAbsolutePath();
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
private static final String COUNTER_TYPE =
|
private static final String COUNTER_TYPE =
|
||||||
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
|
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
|
||||||
private static final String STRING_TYPE = "VARCHAR";
|
private static final String STRING_TYPE = "VARCHAR";
|
||||||
|
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
|
||||||
|
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
private final DatabaseConfig config;
|
||||||
private final String url;
|
private final String url;
|
||||||
@@ -40,8 +42,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
@Inject
|
@Inject
|
||||||
HyperSqlDatabase(DatabaseConfig config, MessageFactory messageFactory,
|
HyperSqlDatabase(DatabaseConfig config, MessageFactory messageFactory,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
super(dbTypes, messageFactory, clock);
|
||||||
messageFactory, clock);
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
File dir = config.getDatabaseDirectory();
|
File dir = config.getDatabaseDirectory();
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
String path = new File(dir, "db").getAbsolutePath();
|
||||||
@@ -78,7 +79,7 @@ class HyperSqlDatabase extends JdbcDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getFreeSpace() throws DbException {
|
public long getFreeSpace() {
|
||||||
File dir = config.getDatabaseDirectory();
|
File dir = config.getDatabaseDirectory();
|
||||||
long maxSize = config.getMaxSize();
|
long maxSize = config.getMaxSize();
|
||||||
long free = dir.getFreeSpace();
|
long free = dir.getFreeSpace();
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static java.sql.Types.INTEGER;
|
import static java.sql.Types.INTEGER;
|
||||||
|
import static java.sql.Types.VARCHAR;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
@@ -83,7 +84,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 40;
|
static final int CODE_SCHEMA_VERSION = 41;
|
||||||
|
|
||||||
// Rotation period offsets for incoming transport keys
|
// Rotation period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
@@ -113,6 +114,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " authorId _HASH NOT NULL,"
|
+ " authorId _HASH NOT NULL,"
|
||||||
+ " formatVersion INT NOT NULL,"
|
+ " formatVersion INT NOT NULL,"
|
||||||
+ " name _STRING NOT NULL,"
|
+ " name _STRING NOT NULL,"
|
||||||
|
+ " alias _STRING," // Null if no alias exists
|
||||||
+ " publicKey _BINARY NOT NULL,"
|
+ " publicKey _BINARY NOT NULL,"
|
||||||
+ " localAuthorId _HASH NOT NULL,"
|
+ " localAuthorId _HASH NOT NULL,"
|
||||||
+ " verified BOOLEAN NOT NULL,"
|
+ " verified BOOLEAN NOT NULL,"
|
||||||
@@ -310,10 +312,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
// Different database libraries use different names for certain types
|
// Different database libraries use different names for certain types
|
||||||
private final String hashType, secretType, binaryType;
|
|
||||||
private final String counterType, stringType;
|
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
private final DatabaseTypes dbTypes;
|
||||||
|
|
||||||
// Locking: connectionsLock
|
// Locking: connectionsLock
|
||||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||||
@@ -328,14 +329,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private final Lock connectionsLock = new ReentrantLock();
|
private final Lock connectionsLock = new ReentrantLock();
|
||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
JdbcDatabase(String hashType, String secretType, String binaryType,
|
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||||
String counterType, String stringType,
|
Clock clock) {
|
||||||
MessageFactory messageFactory, Clock clock) {
|
this.dbTypes = databaseTypes;
|
||||||
this.hashType = hashType;
|
|
||||||
this.secretType = secretType;
|
|
||||||
this.binaryType = binaryType;
|
|
||||||
this.counterType = counterType;
|
|
||||||
this.stringType = stringType;
|
|
||||||
this.messageFactory = messageFactory;
|
this.messageFactory = messageFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
@@ -427,7 +423,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
List<Migration<Connection>> getMigrations() {
|
List<Migration<Connection>> getMigrations() {
|
||||||
return Arrays.asList(new Migration38_39(), new Migration39_40());
|
return Arrays.asList(
|
||||||
|
new Migration38_39(),
|
||||||
|
new Migration39_40(),
|
||||||
|
new Migration40_41(dbTypes)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCompactionDue(Settings s) {
|
private boolean isCompactionDue(Settings s) {
|
||||||
@@ -486,20 +486,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Statement s = null;
|
Statement s = null;
|
||||||
try {
|
try {
|
||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(insertTypeNames(CREATE_SETTINGS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_SETTINGS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_LOCAL_AUTHORS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_CONTACTS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_GROUPS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUPS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_METADATA));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUP_METADATA));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_GROUP_VISIBILITIES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGE_METADATA));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGE_METADATA));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_MESSAGE_DEPENDENCIES));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_MESSAGE_DEPENDENCIES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_OFFERS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_OFFERS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_STATUSES));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_STATUSES));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_TRANSPORTS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_TRANSPORTS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_OUTGOING_KEYS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_OUTGOING_KEYS));
|
||||||
s.executeUpdate(insertTypeNames(CREATE_INCOMING_KEYS));
|
s.executeUpdate(dbTypes.replaceTypes(CREATE_INCOMING_KEYS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -524,15 +524,6 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String insertTypeNames(String s) {
|
|
||||||
s = s.replaceAll("_HASH", hashType);
|
|
||||||
s = s.replaceAll("_SECRET", secretType);
|
|
||||||
s = s.replaceAll("_BINARY", binaryType);
|
|
||||||
s = s.replaceAll("_COUNTER", counterType);
|
|
||||||
s = s.replaceAll("_STRING", stringType);
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection startTransaction() throws DbException {
|
public Connection startTransaction() throws DbException {
|
||||||
Connection txn;
|
Connection txn;
|
||||||
@@ -1258,8 +1249,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT authorId, formatVersion, name, publicKey,"
|
String sql = "SELECT authorId, formatVersion, name, alias,"
|
||||||
+ " localAuthorId, verified, active"
|
+ " publicKey, localAuthorId, verified, active"
|
||||||
+ " FROM contacts"
|
+ " FROM contacts"
|
||||||
+ " WHERE contactId = ?";
|
+ " WHERE contactId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
@@ -1269,15 +1260,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
||||||
int formatVersion = rs.getInt(2);
|
int formatVersion = rs.getInt(2);
|
||||||
String name = rs.getString(3);
|
String name = rs.getString(3);
|
||||||
byte[] publicKey = rs.getBytes(4);
|
String alias = rs.getString(4);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
byte[] publicKey = rs.getBytes(5);
|
||||||
boolean verified = rs.getBoolean(6);
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
|
||||||
boolean active = rs.getBoolean(7);
|
boolean verified = rs.getBoolean(7);
|
||||||
|
boolean active = rs.getBoolean(8);
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
Author author =
|
Author author =
|
||||||
new Author(authorId, formatVersion, name, publicKey);
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
return new Contact(c, author, localAuthorId, verified, active);
|
return new Contact(c, author, localAuthorId, alias, verified,
|
||||||
|
active);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs);
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
@@ -1292,7 +1285,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
||||||
+ " publicKey, localAuthorId, verified, active"
|
+ " alias, publicKey, localAuthorId, verified, active"
|
||||||
+ " FROM contacts";
|
+ " FROM contacts";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1302,14 +1295,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||||
int formatVersion = rs.getInt(3);
|
int formatVersion = rs.getInt(3);
|
||||||
String name = rs.getString(4);
|
String name = rs.getString(4);
|
||||||
byte[] publicKey = rs.getBytes(5);
|
String alias = rs.getString(5);
|
||||||
|
byte[] publicKey = rs.getBytes(6);
|
||||||
Author author =
|
Author author =
|
||||||
new Author(authorId, formatVersion, name, publicKey);
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(7));
|
||||||
boolean verified = rs.getBoolean(7);
|
boolean verified = rs.getBoolean(8);
|
||||||
boolean active = rs.getBoolean(8);
|
boolean active = rs.getBoolean(9);
|
||||||
contacts.add(new Contact(contactId, author, localAuthorId,
|
contacts.add(new Contact(contactId, author, localAuthorId,
|
||||||
verified, active));
|
alias, verified, active));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -1350,8 +1344,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT contactId, formatVersion, name, publicKey,"
|
String sql = "SELECT contactId, formatVersion, name, alias,"
|
||||||
+ " localAuthorId, verified, active"
|
+ " publicKey, localAuthorId, verified, active"
|
||||||
+ " FROM contacts"
|
+ " FROM contacts"
|
||||||
+ " WHERE authorId = ?";
|
+ " WHERE authorId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
@@ -1362,14 +1356,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ContactId c = new ContactId(rs.getInt(1));
|
ContactId c = new ContactId(rs.getInt(1));
|
||||||
int formatVersion = rs.getInt(2);
|
int formatVersion = rs.getInt(2);
|
||||||
String name = rs.getString(3);
|
String name = rs.getString(3);
|
||||||
byte[] publicKey = rs.getBytes(4);
|
String alias = rs.getString(4);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
byte[] publicKey = rs.getBytes(5);
|
||||||
boolean verified = rs.getBoolean(6);
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
|
||||||
boolean active = rs.getBoolean(7);
|
boolean verified = rs.getBoolean(7);
|
||||||
|
boolean active = rs.getBoolean(8);
|
||||||
Author author =
|
Author author =
|
||||||
new Author(remote, formatVersion, name, publicKey);
|
new Author(remote, formatVersion, name, publicKey);
|
||||||
contacts.add(new Contact(c, author, localAuthorId, verified,
|
contacts.add(new Contact(c, author, localAuthorId, alias,
|
||||||
active));
|
verified, active));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -2794,6 +2789,25 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContactAlias(Connection txn, ContactId c,
|
||||||
|
@Nullable String alias) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "UPDATE contacts SET alias = ? WHERE contactId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
if (alias == null) ps.setNull(1, VARCHAR);
|
||||||
|
else ps.setString(1, alias);
|
||||||
|
ps.setInt(2, c.getInt());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setGroupVisibility(Connection txn, ContactId c, GroupId g,
|
public void setGroupVisibility(Connection txn, ContactId c, GroupId g,
|
||||||
boolean shared) throws DbException {
|
boolean shared) throws DbException {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class Migration40_41 implements Migration<Connection> {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(Migration40_41.class.getName());
|
||||||
|
|
||||||
|
private final DatabaseTypes dbTypes;
|
||||||
|
|
||||||
|
public Migration40_41(DatabaseTypes databaseTypes) {
|
||||||
|
this.dbTypes = databaseTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStartVersion() {
|
||||||
|
return 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEndVersion() {
|
||||||
|
return 41;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
s = txn.createStatement();
|
||||||
|
s.execute("ALTER TABLE contacts"
|
||||||
|
+ dbTypes.replaceTypes(" ADD alias VARCHAR"));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable Statement s) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
package org.briarproject.bramble.identity;
|
package org.briarproject.bramble.identity;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
@@ -118,26 +110,4 @@ class IdentityManagerImpl implements IdentityManager {
|
|||||||
return db.getLocalAuthors(txn).iterator().next();
|
return db.getLocalAuthors(txn).iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Status getAuthorStatus(AuthorId authorId) throws DbException {
|
|
||||||
Transaction txn = db.startTransaction(true);
|
|
||||||
try {
|
|
||||||
return getAuthorStatus(txn, authorId);
|
|
||||||
} finally {
|
|
||||||
db.endTransaction(txn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Status getAuthorStatus(Transaction txn, AuthorId authorId)
|
|
||||||
throws DbException {
|
|
||||||
if (getLocalAuthor(txn).getId().equals(authorId)) return OURSELVES;
|
|
||||||
Collection<Contact> contacts = db.getContactsByAuthorId(txn, authorId);
|
|
||||||
if (contacts.isEmpty()) return UNKNOWN;
|
|
||||||
for (Contact c : contacts) {
|
|
||||||
if (c.isVerified()) return VERIFIED;
|
|
||||||
}
|
|
||||||
return UNVERIFIED;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
|||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -46,6 +45,9 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
|||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.macToBytes;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.macToString;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -96,6 +98,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
abstract DuplexTransportConnection discoverAndConnect(String uuid);
|
||||||
|
|
||||||
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
|
||||||
Executor ioExecutor, SecureRandom secureRandom,
|
Executor ioExecutor, SecureRandom secureRandom,
|
||||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||||
@@ -193,7 +198,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
address = getBluetoothAddress();
|
address = getBluetoothAddress();
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Local address " + scrubMacAddress(address));
|
LOG.info("Local address " + scrubMacAddress(address));
|
||||||
if (!StringUtils.isNullOrEmpty(address)) {
|
if (!isNullOrEmpty(address)) {
|
||||||
p.put(PROP_ADDRESS, address);
|
p.put(PROP_ADDRESS, address);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -256,9 +261,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
// Try to connect to known devices in parallel
|
// Try to connect to known devices in parallel
|
||||||
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
for (Entry<ContactId, TransportProperties> e : contacts.entrySet()) {
|
||||||
String address = e.getValue().get(PROP_ADDRESS);
|
String address = e.getValue().get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
if (isNullOrEmpty(address)) continue;
|
||||||
String uuid = e.getValue().get(PROP_UUID);
|
String uuid = e.getValue().get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
if (isNullOrEmpty(uuid)) continue;
|
||||||
ContactId c = e.getKey();
|
ContactId c = e.getKey();
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||||
@@ -309,9 +314,9 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||||
if (!connectionLimiter.canOpenContactConnection()) return null;
|
if (!connectionLimiter.canOpenContactConnection()) return null;
|
||||||
String address = p.get(PROP_ADDRESS);
|
String address = p.get(PROP_ADDRESS);
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
if (isNullOrEmpty(address)) return null;
|
||||||
String uuid = p.get(PROP_UUID);
|
String uuid = p.get(PROP_UUID);
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
if (isNullOrEmpty(uuid)) return null;
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
DuplexTransportConnection conn = connect(address, uuid);
|
||||||
if (conn == null) return null;
|
if (conn == null) return null;
|
||||||
// TODO: Why don't we reset the backoff here?
|
// TODO: Why don't we reset the backoff here?
|
||||||
@@ -326,9 +331,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
// There's no point listening if we can't discover our own address
|
|
||||||
String address = getBluetoothAddress();
|
|
||||||
if (address == null) return null;
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||||
@@ -346,7 +348,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
}
|
}
|
||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||||
descriptor.add(StringUtils.macToBytes(address));
|
String address = getBluetoothAddress();
|
||||||
|
if (address != null) descriptor.add(macToBytes(address));
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,18 +357,25 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor) {
|
byte[] commitment, BdfList descriptor) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
String address;
|
|
||||||
try {
|
|
||||||
address = parseAddress(descriptor);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
LOG.info("Invalid address in key agreement descriptor");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
if (LOG.isLoggable(INFO))
|
DuplexTransportConnection conn;
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
if (descriptor.size() == 1) {
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Discovering address for key agreement UUID " + uuid);
|
||||||
|
conn = discoverAndConnect(uuid);
|
||||||
|
} else {
|
||||||
|
String address;
|
||||||
|
try {
|
||||||
|
address = parseAddress(descriptor);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.info("Invalid address in key agreement descriptor");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
|
conn = connect(address, uuid);
|
||||||
|
}
|
||||||
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
if (conn != null) connectionLimiter.keyAgreementConnectionOpened(conn);
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
@@ -373,7 +383,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
|||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
byte[] mac = descriptor.getRaw(1);
|
byte[] mac = descriptor.getRaw(1);
|
||||||
if (mac.length != 6) throw new FormatException();
|
if (mac.length != 6) throw new FormatException();
|
||||||
return StringUtils.macToString(mac);
|
return macToString(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ import org.briarproject.bramble.api.contact.ContactId;
|
|||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.transport.KeyManager;
|
import org.briarproject.bramble.api.transport.KeyManager;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -20,10 +25,20 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class ContactManagerImplTest extends BrambleMockTestCase {
|
public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||||
@@ -31,16 +46,20 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final Mockery context = new Mockery();
|
private final Mockery context = new Mockery();
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||||
|
private final IdentityManager identityManager =
|
||||||
|
context.mock(IdentityManager.class);
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final ContactId contactId = new ContactId(42);
|
private final ContactId contactId = new ContactId(42);
|
||||||
private final Author remote = getAuthor();
|
private final Author remote = getAuthor();
|
||||||
private final AuthorId local = new AuthorId(getRandomId());
|
private final AuthorId local = new AuthorId(getRandomId());
|
||||||
|
private final LocalAuthor localAuthor = getLocalAuthor();
|
||||||
|
private final String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||||
private final boolean verified = false, active = true;
|
private final boolean verified = false, active = true;
|
||||||
private final Contact contact =
|
private final Contact contact =
|
||||||
new Contact(contactId, remote, local, verified, active);
|
new Contact(contactId, remote, local, alias, verified, active);
|
||||||
|
|
||||||
public ContactManagerImplTest() {
|
public ContactManagerImplTest() {
|
||||||
contactManager = new ContactManagerImpl(db, keyManager);
|
contactManager = new ContactManagerImpl(db, keyManager, identityManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -105,7 +124,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).startTransaction(true);
|
oneOf(db).startTransaction(true);
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||||
will(returnValue(Collections.emptyList()));
|
will(returnValue(emptyList()));
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -131,7 +150,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testActiveContacts() throws Exception {
|
public void testActiveContacts() throws Exception {
|
||||||
Collection<Contact> activeContacts = Collections.singletonList(contact);
|
Collection<Contact> activeContacts = Collections.singletonList(contact);
|
||||||
Collection<Contact> contacts = new ArrayList<>(activeContacts);
|
Collection<Contact> contacts = new ArrayList<>(activeContacts);
|
||||||
contacts.add(new Contact(new ContactId(3), remote, local, true, false));
|
contacts.add(new Contact(new ContactId(3), remote, local, alias, true,
|
||||||
|
false));
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(true);
|
oneOf(db).startTransaction(true);
|
||||||
@@ -171,6 +191,23 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
contactManager.setContactActive(txn, contactId, active);
|
contactManager.setContactActive(txn, contactId, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetContactAlias() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transaction(with(equal(false)), withDbRunnable(txn));
|
||||||
|
oneOf(db).setContactAlias(txn, contactId, alias);
|
||||||
|
}});
|
||||||
|
|
||||||
|
contactManager.setContactAlias(contactId, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testSetContactAliasTooLong() throws Exception {
|
||||||
|
contactManager.setContactAlias(contactId,
|
||||||
|
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testContactExists() throws Exception {
|
public void testContactExists() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
@@ -186,4 +223,79 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertTrue(contactManager.contactExists(remote.getId(), local));
|
assertTrue(contactManager.contactExists(remote.getId(), local));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAuthorStatus() throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
Collection<Contact> contacts = singletonList(
|
||||||
|
new Contact(new ContactId(1), remote, localAuthor.getId(),
|
||||||
|
alias, false, true));
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(equal(true)),
|
||||||
|
withDbCallable(txn));
|
||||||
|
oneOf(identityManager).getLocalAuthor(txn);
|
||||||
|
will(returnValue(localAuthor));
|
||||||
|
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||||
|
will(returnValue(contacts));
|
||||||
|
}});
|
||||||
|
AuthorInfo authorInfo =
|
||||||
|
contactManager.getAuthorInfo(txn, remote.getId());
|
||||||
|
assertEquals(UNVERIFIED, authorInfo.getStatus());
|
||||||
|
assertEquals(alias, contact.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAuthorStatusTransaction() throws DbException {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
|
||||||
|
// check unknown author
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(identityManager).getLocalAuthor(txn);
|
||||||
|
will(returnValue(localAuthor));
|
||||||
|
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||||
|
will(returnValue(emptyList()));
|
||||||
|
}});
|
||||||
|
AuthorInfo authorInfo =
|
||||||
|
contactManager.getAuthorInfo(txn, remote.getId());
|
||||||
|
assertEquals(UNKNOWN, authorInfo.getStatus());
|
||||||
|
assertNull(authorInfo.getAlias());
|
||||||
|
|
||||||
|
// check unverified contact
|
||||||
|
Collection<Contact> contacts = singletonList(
|
||||||
|
new Contact(new ContactId(1), remote, localAuthor.getId(),
|
||||||
|
alias, false, true));
|
||||||
|
checkAuthorStatusContext(txn, remote.getId(), contacts);
|
||||||
|
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
|
||||||
|
assertEquals(UNVERIFIED, authorInfo.getStatus());
|
||||||
|
assertEquals(alias, contact.getAlias());
|
||||||
|
|
||||||
|
// check verified contact
|
||||||
|
contacts = singletonList(new Contact(new ContactId(1), remote,
|
||||||
|
localAuthor.getId(), alias, true, true));
|
||||||
|
checkAuthorStatusContext(txn, remote.getId(), contacts);
|
||||||
|
authorInfo = contactManager.getAuthorInfo(txn, remote.getId());
|
||||||
|
assertEquals(VERIFIED, authorInfo.getStatus());
|
||||||
|
assertEquals(alias, contact.getAlias());
|
||||||
|
|
||||||
|
// check ourselves
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(identityManager).getLocalAuthor(txn);
|
||||||
|
will(returnValue(localAuthor));
|
||||||
|
never(db).getContactsByAuthorId(txn, remote.getId());
|
||||||
|
}});
|
||||||
|
authorInfo = contactManager.getAuthorInfo(txn, localAuthor.getId());
|
||||||
|
assertEquals(OURSELVES, authorInfo.getStatus());
|
||||||
|
assertNull(authorInfo.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAuthorStatusContext(Transaction txn, AuthorId authorId,
|
||||||
|
Collection<Contact> contacts) throws DbException {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(identityManager).getLocalAuthor(txn);
|
||||||
|
will(returnValue(localAuthor));
|
||||||
|
oneOf(db).getContactsByAuthorId(txn, authorId);
|
||||||
|
will(returnValue(contacts));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import static org.briarproject.bramble.test.TestUtils.getMessage;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
@@ -99,6 +100,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
private final Group group;
|
private final Group group;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final LocalAuthor localAuthor;
|
private final LocalAuthor localAuthor;
|
||||||
|
private final String alias;
|
||||||
private final Message message, message1;
|
private final Message message, message1;
|
||||||
private final MessageId messageId, messageId1;
|
private final MessageId messageId, messageId1;
|
||||||
private final Metadata metadata;
|
private final Metadata metadata;
|
||||||
@@ -115,6 +117,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
groupId = group.getId();
|
groupId = group.getId();
|
||||||
author = getAuthor();
|
author = getAuthor();
|
||||||
localAuthor = getLocalAuthor();
|
localAuthor = getLocalAuthor();
|
||||||
|
alias = getRandomString(5);
|
||||||
message = getMessage(groupId);
|
message = getMessage(groupId);
|
||||||
message1 = getMessage(groupId);
|
message1 = getMessage(groupId);
|
||||||
messageId = message.getId();
|
messageId = message.getId();
|
||||||
@@ -124,7 +127,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
transportId = getTransportId();
|
transportId = getTransportId();
|
||||||
maxLatency = Integer.MAX_VALUE;
|
maxLatency = Integer.MAX_VALUE;
|
||||||
contactId = new ContactId(234);
|
contactId = new ContactId(234);
|
||||||
contact = new Contact(contactId, author, localAuthor.getId(),
|
contact = new Contact(contactId, author, localAuthor.getId(), alias,
|
||||||
true, true);
|
true, true);
|
||||||
keySetId = new KeySetId(345);
|
keySetId = new KeySetId(345);
|
||||||
}
|
}
|
||||||
@@ -288,11 +291,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(16).of(database).startTransaction();
|
exactly(17).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(16).of(database).containsContact(txn, contactId);
|
exactly(17).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(16).of(database).abortTransaction(txn);
|
exactly(17).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
shutdown);
|
shutdown);
|
||||||
@@ -450,6 +453,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
db.endTransaction(transaction);
|
db.endTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction = db.startTransaction(false);
|
||||||
|
try {
|
||||||
|
db.setContactAlias(transaction, contactId, alias);
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
} finally {
|
||||||
|
db.endTransaction(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
transaction = db.startTransaction(false);
|
transaction = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.setGroupVisibility(transaction, contactId, groupId, SHARED);
|
db.setGroupVisibility(transaction, contactId, groupId, SHARED);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import static java.util.Collections.singletonList;
|
|||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||||
@@ -74,6 +75,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -1713,6 +1715,39 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetContactAlias() throws Exception {
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a contact
|
||||||
|
db.addLocalAuthor(txn, localAuthor);
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
|
true, true));
|
||||||
|
|
||||||
|
// The contact should have no alias
|
||||||
|
Contact contact = db.getContact(txn, contactId);
|
||||||
|
assertNull(contact.getAlias());
|
||||||
|
|
||||||
|
// Set a contact alias
|
||||||
|
String alias = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
db.setContactAlias(txn, contactId, alias);
|
||||||
|
|
||||||
|
// The contact should have an alias
|
||||||
|
contact = db.getContact(txn, contactId);
|
||||||
|
assertEquals(alias, contact.getAlias());
|
||||||
|
|
||||||
|
// Set the contact alias null
|
||||||
|
db.setContactAlias(txn, contactId, null);
|
||||||
|
|
||||||
|
// The contact should have no alias
|
||||||
|
contact = db.getContact(txn, contactId);
|
||||||
|
assertNull(contact.getAlias());
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetMessageState() throws Exception {
|
public void testSetMessageState() throws Exception {
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.briarproject.bramble.identity;
|
package org.briarproject.bramble.identity;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
@@ -9,9 +7,7 @@ import org.briarproject.bramble.api.crypto.PublicKey;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
@@ -19,15 +15,9 @@ import org.jmock.Expectations;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -107,60 +97,4 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(localAuthor, identityManager.getLocalAuthor());
|
assertEquals(localAuthor, identityManager.getLocalAuthor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetAuthorStatus() throws DbException {
|
|
||||||
Author author = getAuthor();
|
|
||||||
AuthorId authorId = author.getId();
|
|
||||||
Collection<Contact> contacts = new ArrayList<>();
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).startTransaction(true);
|
|
||||||
will(returnValue(txn));
|
|
||||||
oneOf(db).getLocalAuthors(txn);
|
|
||||||
will(returnValue(localAuthors));
|
|
||||||
oneOf(db).getContactsByAuthorId(txn, authorId);
|
|
||||||
will(returnValue(contacts));
|
|
||||||
oneOf(db).endTransaction(txn);
|
|
||||||
}});
|
|
||||||
assertEquals(UNKNOWN, identityManager.getAuthorStatus(authorId));
|
|
||||||
|
|
||||||
// add one unverified contact
|
|
||||||
Contact contact = new Contact(new ContactId(1), author,
|
|
||||||
localAuthor.getId(), false, true);
|
|
||||||
contacts.add(contact);
|
|
||||||
|
|
||||||
checkAuthorStatusContext(authorId, contacts);
|
|
||||||
assertEquals(UNVERIFIED, identityManager.getAuthorStatus(authorId));
|
|
||||||
|
|
||||||
// add one verified contact
|
|
||||||
Contact contact2 = new Contact(new ContactId(1), author,
|
|
||||||
localAuthor.getId(), true, true);
|
|
||||||
contacts.add(contact2);
|
|
||||||
|
|
||||||
checkAuthorStatusContext(authorId, contacts);
|
|
||||||
assertEquals(VERIFIED, identityManager.getAuthorStatus(authorId));
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).startTransaction(true);
|
|
||||||
will(returnValue(txn));
|
|
||||||
never(db).getLocalAuthors(txn);
|
|
||||||
never(db).getContactsByAuthorId(txn, authorId);
|
|
||||||
oneOf(db).endTransaction(txn);
|
|
||||||
}});
|
|
||||||
assertEquals(OURSELVES,
|
|
||||||
identityManager.getAuthorStatus(localAuthor.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAuthorStatusContext(AuthorId authorId,
|
|
||||||
Collection<Contact> contacts) throws DbException {
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(db).startTransaction(true);
|
|
||||||
will(returnValue(txn));
|
|
||||||
never(db).getLocalAuthors(txn);
|
|
||||||
oneOf(db).getContactsByAuthorId(txn, authorId);
|
|
||||||
will(returnValue(contacts));
|
|
||||||
oneOf(db).endTransaction(txn);
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
@@ -612,7 +613,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
private Contact getContact(boolean active) {
|
private Contact getContact(boolean active) {
|
||||||
ContactId c = new ContactId(nextContactId++);
|
ContactId c = new ContactId(nextContactId++);
|
||||||
return new Contact(c, getAuthor(), localAuthor.getId(),
|
return new Contact(c, getAuthor(), localAuthor.getId(),
|
||||||
true, active);
|
getRandomString(5), true, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectGetLocalProperties(Transaction txn) throws Exception {
|
private void expectGetLocalProperties(Transaction txn) throws Exception {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbCallable;
|
||||||
|
import org.briarproject.bramble.api.db.DbRunnable;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
|
||||||
|
public class DbExpectations extends Expectations {
|
||||||
|
|
||||||
|
protected <E extends Exception> DbRunnable<E> withDbRunnable(
|
||||||
|
Transaction txn) {
|
||||||
|
addParameterMatcher(any(DbRunnable.class));
|
||||||
|
currentBuilder().setAction(new RunTransactionAction(txn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <R, E extends Exception> DbCallable<R, E> withDbCallable(
|
||||||
|
Transaction txn) {
|
||||||
|
addParameterMatcher(any(DbCallable.class));
|
||||||
|
currentBuilder().setAction(new RunTransactionWithResultAction(txn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbRunnable;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.jmock.api.Action;
|
||||||
|
import org.jmock.api.Invocation;
|
||||||
|
|
||||||
|
public class RunTransactionAction implements Action {
|
||||||
|
|
||||||
|
private final Transaction txn;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public RunTransactionAction(Transaction txn) {
|
||||||
|
this.txn = txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Invocation invocation) throws Throwable {
|
||||||
|
DbRunnable task = (DbRunnable) invocation.getParameter(1);
|
||||||
|
task.run(txn);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("runs a task inside a database transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbCallable;
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.jmock.api.Action;
|
||||||
|
import org.jmock.api.Invocation;
|
||||||
|
|
||||||
|
public class RunTransactionWithResultAction implements Action {
|
||||||
|
|
||||||
|
private final Transaction txn;
|
||||||
|
|
||||||
|
public RunTransactionWithResultAction(Transaction txn) {
|
||||||
|
this.txn = txn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Invocation invocation) throws Throwable {
|
||||||
|
DbCallable task = (DbCallable) invocation.getParameter(1);
|
||||||
|
return task.call(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("runs a task inside a database transaction");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
import static org.briarproject.bramble.test.TestUtils.getTransportId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class KeyManagerImplTest extends BrambleMockTestCase {
|
public class KeyManagerImplTest extends BrambleMockTestCase {
|
||||||
@@ -66,10 +67,10 @@ public class KeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Author remoteAuthor = getAuthor();
|
Author remoteAuthor = getAuthor();
|
||||||
AuthorId localAuthorId = new AuthorId(getRandomId());
|
AuthorId localAuthorId = new AuthorId(getRandomId());
|
||||||
Collection<Contact> contacts = new ArrayList<>();
|
Collection<Contact> contacts = new ArrayList<>();
|
||||||
contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true,
|
contacts.add(new Contact(contactId, remoteAuthor, localAuthorId,
|
||||||
true));
|
getRandomString(5), true, true));
|
||||||
contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId,
|
contacts.add(new Contact(inactiveContactId, remoteAuthor, localAuthorId,
|
||||||
true, false));
|
getRandomString(5), true, false));
|
||||||
SimplexPluginFactory pluginFactory =
|
SimplexPluginFactory pluginFactory =
|
||||||
context.mock(SimplexPluginFactory.class);
|
context.mock(SimplexPluginFactory.class);
|
||||||
Collection<SimplexPluginFactory> factories =
|
Collection<SimplexPluginFactory> factories =
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
|
||||||
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
|
||||||
@@ -56,7 +57,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final Contact contact = new Contact(new ContactId(123),
|
private final Contact contact = new Contact(new ContactId(123),
|
||||||
getAuthor(), getLocalAuthor().getId(), true, true);
|
getAuthor(), getLocalAuthor().getId(), getRandomString(5), true,
|
||||||
|
true);
|
||||||
private final ClientId clientId = getClientId();
|
private final ClientId clientId = getClientId();
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
private final Transaction txn = new Transaction(null, false);
|
private final Transaction txn = new Transaction(null, false);
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
|||||||
return wrapSocket((StreamConnection) Connector.open(url));
|
return wrapSocket((StreamConnection) Connector.open(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
DuplexTransportConnection discoverAndConnect(String uuid) {
|
||||||
|
return null; // TODO
|
||||||
|
}
|
||||||
|
|
||||||
private String makeUrl(String address, String uuid) {
|
private String makeUrl(String address, String uuid) {
|
||||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ dependencies {
|
|||||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||||
implementation "com.android.support:support-annotations:$supportVersion"
|
implementation "com.android.support:support-annotations:$supportVersion"
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||||
|
implementation "android.arch.lifecycle:extensions:1.1.1"
|
||||||
|
|
||||||
implementation('ch.acra:acra:4.11') {
|
implementation('ch.acra:acra:4.11') {
|
||||||
exclude module: 'support-v4'
|
exclude module: 'support-v4'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||||
import org.briarproject.briar.BriarCoreEagerSingletons;
|
import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.android.contact.ConversationViewModel;
|
||||||
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
import org.briarproject.briar.android.login.SignInReminderReceiver;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
@@ -164,6 +165,8 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
void inject(TextInputView textInputView);
|
void inject(TextInputView textInputView);
|
||||||
|
|
||||||
|
void inject(ConversationViewModel conversationViewModel);
|
||||||
|
|
||||||
// Eager singleton load
|
// Eager singleton load
|
||||||
void inject(AppModule.EagerSingletons init);
|
void inject(AppModule.EagerSingletons init);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class AppModule {
|
|||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth =
|
DuplexPluginFactory bluetooth =
|
||||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||||
appContext, random, eventBus, backoffFactory);
|
appContext, random, eventBus, clock, backoffFactory);
|
||||||
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
|
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
|
||||||
scheduler, appContext, networkManager, locationUtils, eventBus,
|
scheduler, appContext, networkManager, locationUtils, eventBus,
|
||||||
torSocketFactory, backoffFactory, resourceProvider,
|
torSocketFactory, backoffFactory, resourceProvider,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -42,6 +43,8 @@ import static android.arch.lifecycle.Lifecycle.State.STARTED;
|
|||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +54,8 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
|
|||||||
public abstract class BaseActivity extends AppCompatActivity
|
public abstract class BaseActivity extends AppCompatActivity
|
||||||
implements DestroyableContext, OnTapFilteredListener {
|
implements DestroyableContext, OnTapFilteredListener {
|
||||||
|
|
||||||
|
private final static Logger LOG = getLogger(BaseActivity.class.getName());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected ScreenFilterMonitor screenFilterMonitor;
|
protected ScreenFilterMonitor screenFilterMonitor;
|
||||||
|
|
||||||
@@ -119,6 +124,8 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Starting " + this.getClass().getSimpleName());
|
||||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||||
alc.onActivityStart();
|
alc.onActivityStart();
|
||||||
}
|
}
|
||||||
@@ -137,6 +144,8 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Stopping " + this.getClass().getSimpleName());
|
||||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||||
alc.onActivityStop();
|
alc.onActivityStop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ public interface RequestCodes {
|
|||||||
int REQUEST_WRITE_BLOG_POST = 5;
|
int REQUEST_WRITE_BLOG_POST = 5;
|
||||||
int REQUEST_SHARE_BLOG = 6;
|
int REQUEST_SHARE_BLOG = 6;
|
||||||
int REQUEST_RINGTONE = 7;
|
int REQUEST_RINGTONE = 7;
|
||||||
int REQUEST_PERMISSION_CAMERA = 8;
|
int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
|
||||||
int REQUEST_DOZE_WHITELISTING = 9;
|
int REQUEST_DOZE_WHITELISTING = 9;
|
||||||
int REQUEST_ENABLE_BLUETOOTH = 10;
|
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
|
||||||
int REQUEST_UNLOCK = 11;
|
int REQUEST_UNLOCK = 11;
|
||||||
int REQUEST_KEYGUARD_UNLOCK = 12;
|
int REQUEST_KEYGUARD_UNLOCK = 12;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.briarproject.briar.android.blog;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||||
@@ -15,6 +15,7 @@ import javax.annotation.concurrent.NotThreadSafe;
|
|||||||
public class BlogPostItem implements Comparable<BlogPostItem> {
|
public class BlogPostItem implements Comparable<BlogPostItem> {
|
||||||
|
|
||||||
private final BlogPostHeader header;
|
private final BlogPostHeader header;
|
||||||
|
@Nullable
|
||||||
protected String text;
|
protected String text;
|
||||||
private boolean read;
|
private boolean read;
|
||||||
|
|
||||||
@@ -40,10 +41,11 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
|||||||
return header.getAuthor();
|
return header.getAuthor();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status getAuthorStatus() {
|
AuthorInfo getAuthorInfo() {
|
||||||
return header.getAuthorStatus();
|
return header.getAuthorInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public String getText() {
|
public String getText() {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.AuthorView;
|
import org.briarproject.briar.android.view.AuthorView;
|
||||||
@@ -98,9 +97,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
// author and date
|
// author and date
|
||||||
BlogPostHeader post = item.getPostHeader();
|
BlogPostHeader post = item.getPostHeader();
|
||||||
Author a = post.getAuthor();
|
author.setAuthor(post.getAuthor(), post.getAuthorInfo());
|
||||||
author.setAuthor(a);
|
|
||||||
author.setAuthorStatus(post.getAuthorStatus());
|
|
||||||
author.setDate(post.getTimestamp());
|
author.setDate(post.getTimestamp());
|
||||||
author.setPersona(
|
author.setPersona(
|
||||||
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
||||||
@@ -143,8 +140,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private void onBindComment(BlogCommentItem item) {
|
private void onBindComment(BlogCommentItem item) {
|
||||||
// reblogger
|
// reblogger
|
||||||
reblogger.setAuthor(item.getAuthor());
|
reblogger.setAuthor(item.getAuthor(), item.getAuthorInfo());
|
||||||
reblogger.setAuthorStatus(item.getAuthorStatus());
|
|
||||||
reblogger.setDate(item.getTimestamp());
|
reblogger.setDate(item.getTimestamp());
|
||||||
if (!fullText) {
|
if (!fullText) {
|
||||||
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
|
reblogger.setAuthorClickable(v -> listener.onAuthorClick(item));
|
||||||
@@ -165,8 +161,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
AuthorView author = v.findViewById(R.id.authorView);
|
AuthorView author = v.findViewById(R.id.authorView);
|
||||||
TextView text = v.findViewById(R.id.textView);
|
TextView text = v.findViewById(R.id.textView);
|
||||||
|
|
||||||
author.setAuthor(c.getAuthor());
|
author.setAuthor(c.getAuthor(), c.getAuthorInfo());
|
||||||
author.setAuthorStatus(c.getAuthorStatus());
|
|
||||||
author.setDate(c.getTimestamp());
|
author.setDate(c.getTimestamp());
|
||||||
// TODO make author clickable #624
|
// TODO make author clickable #624
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.app.AppCompatDialogFragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
public class AliasDialogFragment extends AppCompatDialogFragment {
|
||||||
|
|
||||||
|
final static String TAG = AliasDialogFragment.class.getName();
|
||||||
|
|
||||||
|
private ConversationViewModel viewModel;
|
||||||
|
private ContactId contactId;
|
||||||
|
private EditText aliasEditText;
|
||||||
|
|
||||||
|
public static AliasDialogFragment newInstance(ContactId id) {
|
||||||
|
AliasDialogFragment f = new AliasDialogFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt("contactId", id.getInt());
|
||||||
|
f.setArguments(args);
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (getArguments() == null) throw new IllegalArgumentException();
|
||||||
|
int contactIdInt = getArguments().getInt("contactId", -1);
|
||||||
|
if (contactIdInt == -1) throw new IllegalArgumentException();
|
||||||
|
contactId = new ContactId(contactIdInt);
|
||||||
|
|
||||||
|
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
|
||||||
|
|
||||||
|
viewModel =
|
||||||
|
ViewModelProviders.of(getActivity()).get(ConversationViewModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
|
||||||
|
false);
|
||||||
|
|
||||||
|
aliasEditText = v.findViewById(R.id.aliasEditText);
|
||||||
|
Contact contact = viewModel.getContact().getValue();
|
||||||
|
String alias = contact == null ? null : contact.getAlias();
|
||||||
|
aliasEditText.setText(alias);
|
||||||
|
if (alias != null) aliasEditText.setSelection(alias.length());
|
||||||
|
|
||||||
|
Button setButton = v.findViewById(R.id.setButton);
|
||||||
|
setButton.setOnClickListener(v1 -> {
|
||||||
|
viewModel.setContactAlias(contactId,
|
||||||
|
aliasEditText.getText().toString());
|
||||||
|
getDialog().dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button cancelButton = v.findViewById(R.id.cancelButton);
|
||||||
|
cancelButton.setOnClickListener(v1 -> getDialog().cancel());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
@@ -9,6 +10,7 @@ import org.briarproject.briar.android.util.BriarAdapter;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
|
public abstract class BaseContactListAdapter<I extends ContactItem, VH extends ContactItemViewHolder<I>>
|
||||||
extends BriarAdapter<I, VH> {
|
extends BriarAdapter<I, VH> {
|
||||||
@@ -23,15 +25,15 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(VH ui, int position) {
|
public void onBindViewHolder(@NonNull VH ui, int position) {
|
||||||
I item = items.get(position);
|
I item = items.get(position);
|
||||||
ui.bind(item, listener);
|
ui.bind(item, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(I c1, I c2) {
|
public int compare(I c1, I c2) {
|
||||||
return c1.getContact().getAuthor().getName()
|
return getContactDisplayName(c1.getContact())
|
||||||
.compareTo(c2.getContact().getAuthor().getName());
|
.compareTo(getContactDisplayName(c2.getContact()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
import im.delight.android.identicons.IdenticonDrawable;
|
import im.delight.android.identicons.IdenticonDrawable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ContactItemViewHolder<I extends ContactItem>
|
public class ContactItemViewHolder<I extends ContactItem>
|
||||||
@@ -41,8 +43,7 @@ public class ContactItemViewHolder<I extends ContactItem>
|
|||||||
Author author = item.getContact().getAuthor();
|
Author author = item.getContact().getAuthor();
|
||||||
avatar.setImageDrawable(
|
avatar.setImageDrawable(
|
||||||
new IdenticonDrawable(author.getId().getBytes()));
|
new IdenticonDrawable(author.getId().getBytes()));
|
||||||
String contactName = author.getName();
|
name.setText(getContactDisplayName(item.getContact()));
|
||||||
name.setText(contactName);
|
|
||||||
|
|
||||||
if (bulb != null) {
|
if (bulb != null) {
|
||||||
// online/offline
|
// online/offline
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package org.briarproject.briar.android.contact;
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.Observer;
|
import android.arch.lifecycle.Observer;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -23,7 +24,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
@@ -34,7 +34,6 @@ import org.briarproject.bramble.api.db.NoSuchContactException;
|
|||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||||
@@ -131,8 +130,8 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Executor cryptoExecutor;
|
Executor cryptoExecutor;
|
||||||
|
|
||||||
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
|
||||||
private final MutableLiveData<String> contactName = new MutableLiveData<>();
|
|
||||||
|
|
||||||
|
private ConversationViewModel viewModel;
|
||||||
private ConversationVisitor visitor;
|
private ConversationVisitor visitor;
|
||||||
private ConversationAdapter adapter;
|
private ConversationAdapter adapter;
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
@@ -166,8 +165,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
private volatile ContactId contactId;
|
private volatile ContactId contactId;
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile AuthorId contactAuthorId;
|
|
||||||
@Nullable
|
|
||||||
private volatile GroupId messagingGroupId;
|
private volatile GroupId messagingGroupId;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -176,6 +173,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
setSceneTransitionAnimation();
|
setSceneTransitionAnimation();
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
|
viewModel =
|
||||||
|
ViewModelProviders.of(this).get(ConversationViewModel.class);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
int id = i.getIntExtra(CONTACT_ID, -1);
|
int id = i.getIntExtra(CONTACT_ID, -1);
|
||||||
if (id == -1) throw new IllegalStateException();
|
if (id == -1) throw new IllegalStateException();
|
||||||
@@ -185,16 +185,29 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
// Custom Toolbar
|
// Custom Toolbar
|
||||||
toolbar = setUpCustomToolbar(true);
|
toolbar = setUpCustomToolbar(true);
|
||||||
if (toolbar != null) {
|
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
|
||||||
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
|
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
|
||||||
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
|
toolbarTitle = toolbar.findViewById(R.id.contactName);
|
||||||
toolbarTitle = toolbar.findViewById(R.id.contactName);
|
|
||||||
}
|
viewModel.getContactAuthorId().observe(this, authorId -> {
|
||||||
|
toolbarAvatar.setImageDrawable(
|
||||||
|
new IdenticonDrawable(authorId.getBytes()));
|
||||||
|
// we only need this once
|
||||||
|
viewModel.getContactAuthorId().removeObservers(this);
|
||||||
|
});
|
||||||
|
viewModel.getContactDisplayName().observe(this, contactName -> {
|
||||||
|
toolbarTitle.setText(contactName);
|
||||||
|
});
|
||||||
|
viewModel.isContactDeleted().observe(this, deleted -> {
|
||||||
|
if (deleted != null && deleted) finish();
|
||||||
|
});
|
||||||
|
viewModel.loadContact(contactId);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
|
||||||
|
|
||||||
visitor = new ConversationVisitor(this, this, contactName);
|
visitor = new ConversationVisitor(this, this,
|
||||||
|
viewModel.getContactDisplayName());
|
||||||
adapter = new ConversationAdapter(this, this);
|
adapter = new ConversationAdapter(this, this);
|
||||||
list = findViewById(R.id.conversationView);
|
list = findViewById(R.id.conversationView);
|
||||||
list.setLayoutManager(new LinearLayoutManager(this));
|
list.setLayoutManager(new LinearLayoutManager(this));
|
||||||
@@ -229,7 +242,19 @@ public class ConversationActivity extends BriarActivity
|
|||||||
notificationManager.blockContactNotification(contactId);
|
notificationManager.blockContactNotification(contactId);
|
||||||
notificationManager.clearContactNotification(contactId);
|
notificationManager.clearContactNotification(contactId);
|
||||||
displayContactOnlineStatus();
|
displayContactOnlineStatus();
|
||||||
loadContactDetailsAndMessages();
|
LiveData<String> contactName = viewModel.getContactDisplayName();
|
||||||
|
if (contactName.getValue() == null) {
|
||||||
|
// wait for contact name to be initialized
|
||||||
|
contactName.observe(this, new Observer<String>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable String cName) {
|
||||||
|
if (cName != null) {
|
||||||
|
loadMessages();
|
||||||
|
contactName.removeObserver(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else loadMessages();
|
||||||
list.startPeriodicUpdate();
|
list.startPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +291,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
intent.putExtra(CONTACT_ID, contactId.getInt());
|
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||||
startActivityForResult(intent, REQUEST_INTRODUCTION);
|
startActivityForResult(intent, REQUEST_INTRODUCTION);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_set_alias:
|
||||||
|
AliasDialogFragment.newInstance(contactId).show(
|
||||||
|
getSupportFragmentManager(), AliasDialogFragment.TAG);
|
||||||
|
return true;
|
||||||
case R.id.action_social_remove_person:
|
case R.id.action_social_remove_person:
|
||||||
askToRemoveContact();
|
askToRemoveContact();
|
||||||
return true;
|
return true;
|
||||||
@@ -274,36 +303,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContactDetailsAndMessages() {
|
|
||||||
runOnDbThread(() -> {
|
|
||||||
try {
|
|
||||||
long start = now();
|
|
||||||
if (contactAuthorId == null) {
|
|
||||||
Contact contact = contactManager.getContact(contactId);
|
|
||||||
contactName.postValue(contact.getAuthor().getName());
|
|
||||||
contactAuthorId = contact.getAuthor().getId();
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Loading contact", start);
|
|
||||||
loadMessages();
|
|
||||||
displayContactDetails();
|
|
||||||
} catch (NoSuchContactException e) {
|
|
||||||
finishOnUiThread();
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// contactAuthorId and contactName are expected to be set
|
|
||||||
private void displayContactDetails() {
|
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
toolbarAvatar.setImageDrawable(
|
|
||||||
new IdenticonDrawable(contactAuthorId.getBytes()));
|
|
||||||
toolbarTitle.setText(contactName.getValue());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayContactOnlineStatus() {
|
private void displayContactOnlineStatus() {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
if (connectionRegistry.isConnected(contactId)) {
|
if (connectionRegistry.isConnected(contactId)) {
|
||||||
@@ -453,15 +452,17 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void onNewPrivateMessage(PrivateMessageHeader h) {
|
private void onNewPrivateMessage(PrivateMessageHeader h) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
if (h instanceof PrivateRequest || h instanceof PrivateResponse) {
|
if (h instanceof PrivateRequest || h instanceof PrivateResponse) {
|
||||||
String cName = contactName.getValue();
|
String cName = viewModel.getContactDisplayName().getValue();
|
||||||
if (cName == null) {
|
if (cName == null) {
|
||||||
// Wait for the contact name to be loaded
|
// Wait for the contact name to be loaded
|
||||||
contactName.observe(this, new Observer<String>() {
|
viewModel.getContactDisplayName()
|
||||||
|
.observe(this, new Observer<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(@Nullable String cName) {
|
public void onChanged(@Nullable String cName) {
|
||||||
if (cName != null) {
|
if (cName != null) {
|
||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
contactName.removeObserver(this);
|
viewModel.getContactDisplayName()
|
||||||
|
.removeObserver(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package org.briarproject.briar.android.contact;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.AndroidViewModel;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.arch.lifecycle.Transformations;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.briar.android.AndroidComponent;
|
||||||
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
|
public class ConversationViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static Logger LOG =
|
||||||
|
Logger.getLogger(ConversationViewModel.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@DatabaseExecutor
|
||||||
|
Executor dbExecutor;
|
||||||
|
@Inject
|
||||||
|
ContactManager contactManager;
|
||||||
|
|
||||||
|
private final MutableLiveData<Contact> contact = new MutableLiveData<>();
|
||||||
|
private final LiveData<AuthorId> contactAuthorId =
|
||||||
|
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||||
|
private final LiveData<String> contactName =
|
||||||
|
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||||
|
private final MutableLiveData<Boolean> contactDeleted =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
|
public ConversationViewModel(@NonNull Application application) {
|
||||||
|
super(application);
|
||||||
|
AndroidComponent component =
|
||||||
|
((BriarApplication) application).getApplicationComponent();
|
||||||
|
component.inject(this);
|
||||||
|
contactDeleted.setValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadContact(ContactId contactId) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
long start = now();
|
||||||
|
contact.postValue(contactManager.getContact(contactId));
|
||||||
|
logDuration(LOG, "Loading contact", start);
|
||||||
|
} catch (NoSuchContactException e) {
|
||||||
|
contactDeleted.postValue(true);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setContactAlias(ContactId contactId, String alias) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
contactManager.setContactAlias(contactId,
|
||||||
|
alias.isEmpty() ? null : alias);
|
||||||
|
loadContact(contactId);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Contact> getContact() {
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<AuthorId> getContactAuthorId() {
|
||||||
|
return contactAuthorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<String> getContactDisplayName() {
|
||||||
|
return contactName;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> isContactDeleted() {
|
||||||
|
return contactDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ import static org.briarproject.briar.android.contact.ConversationRequestItem.Req
|
|||||||
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.FORUM;
|
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.FORUM;
|
||||||
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.GROUP;
|
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.GROUP;
|
||||||
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.INTRODUCTION;
|
import static org.briarproject.briar.android.contact.ConversationRequestItem.RequestType.INTRODUCTION;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -188,33 +189,36 @@ class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationItem visitIntroductionRequest(IntroductionRequest r) {
|
public ConversationItem visitIntroductionRequest(IntroductionRequest r) {
|
||||||
|
String name = getContactDisplayName(r.getNameable(), r.getAlias());
|
||||||
if (r.isLocal()) {
|
if (r.isLocal()) {
|
||||||
String text = ctx.getString(R.string.introduction_request_sent,
|
String text = ctx.getString(R.string.introduction_request_sent,
|
||||||
contactName.getValue(), r.getName());
|
contactName.getValue(), name);
|
||||||
return new ConversationNoticeOutItem(text, r);
|
return new ConversationNoticeOutItem(text, r);
|
||||||
} else {
|
} else {
|
||||||
String text = ctx.getString(R.string.introduction_request_received,
|
String text = ctx.getString(R.string.introduction_request_received,
|
||||||
contactName.getValue(), r.getName());
|
contactName.getValue(), name);
|
||||||
return new ConversationRequestItem(text, INTRODUCTION, r);
|
return new ConversationRequestItem(text, INTRODUCTION, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConversationItem visitIntroductionResponse(IntroductionResponse r) {
|
public ConversationItem visitIntroductionResponse(IntroductionResponse r) {
|
||||||
|
String introducedAuthor =
|
||||||
|
getContactDisplayName(r.getIntroducedAuthor(),
|
||||||
|
r.getIntroducedAuthorInfo().getAlias());
|
||||||
if (r.isLocal()) {
|
if (r.isLocal()) {
|
||||||
String text;
|
String text;
|
||||||
if (r.wasAccepted()) {
|
if (r.wasAccepted()) {
|
||||||
String introducee = r.getIntroducedAuthor().getName();
|
|
||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_accepted_sent,
|
R.string.introduction_response_accepted_sent,
|
||||||
introducee)
|
introducedAuthor)
|
||||||
+ "\n\n" + ctx.getString(
|
+ "\n\n" + ctx.getString(
|
||||||
R.string.introduction_response_accepted_sent_info,
|
R.string.introduction_response_accepted_sent_info,
|
||||||
introducee);
|
introducedAuthor);
|
||||||
} else {
|
} else {
|
||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_declined_sent,
|
R.string.introduction_response_declined_sent,
|
||||||
r.getIntroducedAuthor().getName());
|
introducedAuthor);
|
||||||
}
|
}
|
||||||
return new ConversationNoticeOutItem(text, r);
|
return new ConversationNoticeOutItem(text, r);
|
||||||
} else {
|
} else {
|
||||||
@@ -223,17 +227,17 @@ class ConversationVisitor implements PrivateMessageVisitor<ConversationItem> {
|
|||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_accepted_received,
|
R.string.introduction_response_accepted_received,
|
||||||
contactName.getValue(),
|
contactName.getValue(),
|
||||||
r.getIntroducedAuthor().getName());
|
introducedAuthor);
|
||||||
} else if (r.isIntroducer()) {
|
} else if (r.isIntroducer()) {
|
||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_declined_received,
|
R.string.introduction_response_declined_received,
|
||||||
contactName.getValue(),
|
contactName.getValue(),
|
||||||
r.getIntroducedAuthor().getName());
|
introducedAuthor);
|
||||||
} else {
|
} else {
|
||||||
text = ctx.getString(
|
text = ctx.getString(
|
||||||
R.string.introduction_response_declined_received_by_introducee,
|
R.string.introduction_response_declined_received_by_introducee,
|
||||||
contactName.getValue(),
|
contactName.getValue(),
|
||||||
r.getIntroducedAuthor().getName());
|
introducedAuthor);
|
||||||
}
|
}
|
||||||
return new ConversationNoticeInItem(text, r);
|
return new ConversationNoticeInItem(text, r);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.briar.android.forum;
|
package org.briarproject.briar.android.forum;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.threaded.ThreadItem;
|
import org.briarproject.briar.android.threaded.ThreadItem;
|
||||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||||
@@ -14,11 +14,11 @@ class ForumItem extends ThreadItem {
|
|||||||
|
|
||||||
ForumItem(ForumPostHeader h, String text) {
|
ForumItem(ForumPostHeader h, String text) {
|
||||||
super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
|
super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
|
||||||
h.getAuthorStatus(), h.isRead());
|
h.getAuthorInfo(), h.isRead());
|
||||||
}
|
}
|
||||||
|
|
||||||
ForumItem(MessageId messageId, @Nullable MessageId parentId, String text,
|
ForumItem(MessageId messageId, @Nullable MessageId parentId, String text,
|
||||||
long timestamp, Author author, Status status) {
|
long timestamp, Author author, AuthorInfo status) {
|
||||||
super(messageId, parentId, text, timestamp, author, status, true);
|
super(messageId, parentId, text, timestamp, author, status, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import static android.view.View.VISIBLE;
|
|||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
|
||||||
|
|
||||||
public class IntroductionMessageFragment extends BaseFragment
|
public class IntroductionMessageFragment extends BaseFragment
|
||||||
@@ -148,8 +149,8 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
c2.getAuthor().getId().getBytes()));
|
c2.getAuthor().getId().getBytes()));
|
||||||
|
|
||||||
// set contact names
|
// set contact names
|
||||||
ui.contactName1.setText(c1.getAuthor().getName());
|
ui.contactName1.setText(getContactDisplayName(c1));
|
||||||
ui.contactName2.setText(c2.getAuthor().getName());
|
ui.contactName2.setText(getContactDisplayName(c2));
|
||||||
|
|
||||||
// hide progress bar
|
// hide progress bar
|
||||||
ui.progressBar.setVisibility(GONE);
|
ui.progressBar.setVisibility(GONE);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.keyagreement;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -11,19 +10,15 @@ import android.support.annotation.StringRes;
|
|||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.support.v7.app.AlertDialog.Builder;
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.R.string;
|
|
||||||
import org.briarproject.briar.R.style;
|
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
@@ -37,16 +32,19 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||||
import static android.Manifest.permission.CAMERA;
|
import static android.Manifest.permission.CAMERA;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
|
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -55,7 +53,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
KeyAgreementEventListener {
|
KeyAgreementEventListener {
|
||||||
|
|
||||||
private enum BluetoothState {
|
private enum BluetoothState {
|
||||||
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
|
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED, DISCOVERABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Permission {
|
||||||
|
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -64,8 +66,27 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Inject
|
@Inject
|
||||||
EventBus eventBus;
|
EventBus eventBus;
|
||||||
|
|
||||||
private boolean isResumed = false, enableWasRequested = false;
|
/**
|
||||||
private boolean continueClicked, gotCameraPermission;
|
* Set to true in onPostResume() and false in onPause(). This prevents the
|
||||||
|
* QR code fragment from being shown if onRequestPermissionsResult() is
|
||||||
|
* called while the activity is paused, which could cause a crash due to
|
||||||
|
* https://issuetracker.google.com/issues/37067655.
|
||||||
|
*/
|
||||||
|
private boolean isResumed = false;
|
||||||
|
/**
|
||||||
|
* Set to true when the continue button is clicked, and false when the QR
|
||||||
|
* code fragment is shown. This prevents the QR code fragment from being
|
||||||
|
* shown automatically before the continue button has been clicked.
|
||||||
|
*/
|
||||||
|
private boolean continueClicked = false;
|
||||||
|
/**
|
||||||
|
* Records whether the Bluetooth adapter was already enabled before we
|
||||||
|
* asked for Bluetooth discoverability, so we know whether to broadcast a
|
||||||
|
* {@link BluetoothEnabledEvent}.
|
||||||
|
*/
|
||||||
|
private boolean wasAdapterEnabled = false;
|
||||||
|
private Permission cameraPermission = Permission.UNKNOWN;
|
||||||
|
private Permission locationPermission = Permission.UNKNOWN;
|
||||||
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
||||||
private BroadcastReceiver bluetoothReceiver = null;
|
private BroadcastReceiver bluetoothReceiver = null;
|
||||||
|
|
||||||
@@ -85,7 +106,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
if (state == null) {
|
if (state == null) {
|
||||||
showInitialFragment(IntroFragment.newInstance());
|
showInitialFragment(IntroFragment.newInstance());
|
||||||
}
|
}
|
||||||
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_STATE_CHANGED);
|
||||||
|
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||||
bluetoothReceiver = new BluetoothStateReceiver();
|
bluetoothReceiver = new BluetoothStateReceiver();
|
||||||
registerReceiver(bluetoothReceiver, filter);
|
registerReceiver(bluetoothReceiver, filter);
|
||||||
}
|
}
|
||||||
@@ -107,20 +130,40 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
// Permissions may have been granted manually while we were stopped
|
||||||
|
cameraPermission = Permission.UNKNOWN;
|
||||||
|
locationPermission = Permission.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostResume() {
|
protected void onPostResume() {
|
||||||
super.onPostResume();
|
super.onPostResume();
|
||||||
isResumed = true;
|
isResumed = true;
|
||||||
// Workaround for
|
// Workaround for
|
||||||
// https://code.google.com/p/android/issues/detail?id=190966
|
// https://code.google.com/p/android/issues/detail?id=190966
|
||||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canShowQrCodeFragment() {
|
private void showQrCodeFragmentIfAllowed() {
|
||||||
return isResumed && continueClicked
|
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||||
&& (SDK_INT < 23 || gotCameraPermission)
|
if (bluetoothState == BluetoothState.UNKNOWN ||
|
||||||
&& bluetoothState != BluetoothState.UNKNOWN
|
bluetoothState == BluetoothState.ENABLED) {
|
||||||
&& bluetoothState != BluetoothState.WAITING;
|
requestBluetoothDiscoverable();
|
||||||
|
} else if (bluetoothState != BluetoothState.WAITING) {
|
||||||
|
showQrCodeFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean areEssentialPermissionsGranted() {
|
||||||
|
// If the camera permission has been granted, and the location
|
||||||
|
// permission has been granted or permanently denied, we can continue
|
||||||
|
return cameraPermission == Permission.GRANTED &&
|
||||||
|
(locationPermission == Permission.GRANTED ||
|
||||||
|
locationPermission == Permission.PERMANENTLY_DENIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,50 +175,54 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void showNextScreen() {
|
public void showNextScreen() {
|
||||||
continueClicked = true;
|
continueClicked = true;
|
||||||
if (checkPermissions()) {
|
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||||
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
|
|
||||||
else if (canShowQrCodeFragment()) showQrCodeFragment();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldRequestEnableBluetooth() {
|
private void requestBluetoothDiscoverable() {
|
||||||
return bluetoothState == BluetoothState.UNKNOWN
|
|
||||||
|| bluetoothState == BluetoothState.REFUSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestEnableBluetooth() {
|
|
||||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
if (bt == null) {
|
if (bt == null) {
|
||||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||||
} else if (bt.isEnabled()) {
|
|
||||||
setBluetoothState(BluetoothState.ENABLED);
|
|
||||||
} else {
|
} else {
|
||||||
enableWasRequested = true;
|
|
||||||
setBluetoothState(BluetoothState.WAITING);
|
setBluetoothState(BluetoothState.WAITING);
|
||||||
Intent i = new Intent(ACTION_REQUEST_ENABLE);
|
wasAdapterEnabled = bt.isEnabled();
|
||||||
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
|
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
|
||||||
|
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBluetoothState(BluetoothState bluetoothState) {
|
private void setBluetoothState(BluetoothState bluetoothState) {
|
||||||
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
||||||
this.bluetoothState = bluetoothState;
|
this.bluetoothState = bluetoothState;
|
||||||
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
|
if (!wasAdapterEnabled && bluetoothState == BluetoothState.ENABLED) {
|
||||||
eventBus.broadcast(new BluetoothEnabledEvent());
|
eventBus.broadcast(new BluetoothEnabledEvent());
|
||||||
enableWasRequested = false;
|
wasAdapterEnabled = true;
|
||||||
}
|
}
|
||||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
showQrCodeFragmentIfAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int request, int result, Intent data) {
|
public void onActivityResult(int request, int result, Intent data) {
|
||||||
// If the request was granted we'll catch the state change event
|
if (request == REQUEST_BLUETOOTH_DISCOVERABLE) {
|
||||||
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
|
if (result == RESULT_CANCELED) {
|
||||||
setBluetoothState(BluetoothState.REFUSED);
|
setBluetoothState(BluetoothState.REFUSED);
|
||||||
|
} else {
|
||||||
|
// If Bluetooth is already discoverable, show the QR code -
|
||||||
|
// otherwise wait for the state or scan mode to change
|
||||||
|
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
if (bt == null) throw new AssertionError();
|
||||||
|
if (bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||||
|
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showQrCodeFragment() {
|
private void showQrCodeFragment() {
|
||||||
|
// If we return to the intro fragment, the continue button needs to be
|
||||||
|
// clicked again before showing the QR code fragment
|
||||||
continueClicked = false;
|
continueClicked = false;
|
||||||
|
// If we return to the intro fragment, ask for Bluetooth
|
||||||
|
// discoverability again before showing the QR code fragment
|
||||||
|
bluetoothState = BluetoothState.UNKNOWN;
|
||||||
// FIXME #824
|
// FIXME #824
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) {
|
||||||
@@ -194,74 +241,113 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkPermissions() {
|
private boolean checkPermissions() {
|
||||||
if (ContextCompat.checkSelfPermission(this, CAMERA) !=
|
if (areEssentialPermissionsGranted()) return true;
|
||||||
PERMISSION_GRANTED) {
|
// If the camera permission has been permanently denied, ask the
|
||||||
// Should we show an explanation?
|
// user to change the setting
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
|
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||||
CAMERA)) {
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
OnClickListener continueListener =
|
builder.setTitle(R.string.permission_camera_title);
|
||||||
(dialog, which) -> requestPermission();
|
builder.setMessage(R.string.permission_camera_denied_body);
|
||||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
builder.setPositiveButton(R.string.ok,
|
||||||
builder.setTitle(string.permission_camera_title);
|
UiUtils.getGoToSettingsListener(this));
|
||||||
builder.setMessage(string.permission_camera_request_body);
|
builder.setNegativeButton(R.string.cancel,
|
||||||
builder.setNeutralButton(string.continue_button,
|
(dialog, which) -> supportFinishAfterTransition());
|
||||||
continueListener);
|
builder.show();
|
||||||
builder.show();
|
|
||||||
} else {
|
|
||||||
requestPermission();
|
|
||||||
}
|
|
||||||
gotCameraPermission = false;
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
gotCameraPermission = true;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// Should we show the rationale for one or both permissions?
|
||||||
|
if (cameraPermission == Permission.SHOW_RATIONALE &&
|
||||||
|
locationPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_camera_location_title,
|
||||||
|
R.string.permission_camera_location_request_body);
|
||||||
|
} else if (cameraPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_camera_title,
|
||||||
|
R.string.permission_camera_request_body);
|
||||||
|
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||||
|
showRationale(R.string.permission_location_title,
|
||||||
|
R.string.permission_location_request_body);
|
||||||
|
} else {
|
||||||
|
requestPermissions();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestPermission() {
|
private void showRationale(@StringRes int title, @StringRes int body) {
|
||||||
ActivityCompat.requestPermissions(this, new String[] {CAMERA},
|
Builder builder = new Builder(this, R.style.BriarDialogTheme);
|
||||||
REQUEST_PERMISSION_CAMERA);
|
builder.setTitle(title);
|
||||||
|
builder.setMessage(body);
|
||||||
|
builder.setNeutralButton(R.string.continue_button,
|
||||||
|
(dialog, which) -> requestPermissions());
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPermissions() {
|
||||||
|
ActivityCompat.requestPermissions(this,
|
||||||
|
new String[] {CAMERA, ACCESS_COARSE_LOCATION},
|
||||||
|
REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
String permissions[], int[] grantResults) {
|
String[] permissions, int[] grantResults) {
|
||||||
if (requestCode == REQUEST_PERMISSION_CAMERA) {
|
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
|
||||||
// If request is cancelled, the result arrays are empty.
|
throw new AssertionError();
|
||||||
if (grantResults.length > 0 &&
|
if (gotPermission(CAMERA, permissions, grantResults)) {
|
||||||
grantResults[0] == PERMISSION_GRANTED) {
|
cameraPermission = Permission.GRANTED;
|
||||||
gotCameraPermission = true;
|
} else if (shouldShowRationale(CAMERA)) {
|
||||||
showNextScreen();
|
cameraPermission = Permission.SHOW_RATIONALE;
|
||||||
} else {
|
} else {
|
||||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
cameraPermission = Permission.PERMANENTLY_DENIED;
|
||||||
CAMERA)) {
|
|
||||||
// The user has permanently denied the request
|
|
||||||
OnClickListener cancelListener =
|
|
||||||
(dialog, which) -> supportFinishAfterTransition();
|
|
||||||
Builder builder = new Builder(this, style.BriarDialogTheme);
|
|
||||||
builder.setTitle(string.permission_camera_title);
|
|
||||||
builder.setMessage(string.permission_camera_denied_body);
|
|
||||||
builder.setPositiveButton(string.ok,
|
|
||||||
UiUtils.getGoToSettingsListener(this));
|
|
||||||
builder.setNegativeButton(string.cancel, cancelListener);
|
|
||||||
builder.show();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, string.permission_camera_denied_toast,
|
|
||||||
LENGTH_LONG).show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (gotPermission(ACCESS_COARSE_LOCATION, permissions, grantResults)) {
|
||||||
|
locationPermission = Permission.GRANTED;
|
||||||
|
} else if (shouldShowRationale(ACCESS_COARSE_LOCATION)) {
|
||||||
|
locationPermission = Permission.SHOW_RATIONALE;
|
||||||
|
} else {
|
||||||
|
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||||
|
}
|
||||||
|
// If a permission dialog has been shown, showing the QR code fragment
|
||||||
|
// on this call path would cause a crash due to
|
||||||
|
// https://code.google.com/p/android/issues/detail?id=190966.
|
||||||
|
// In that case the isResumed flag prevents the fragment from being
|
||||||
|
// shown here, and showQrCodeFragmentIfAllowed() will be called again
|
||||||
|
// from onPostResume().
|
||||||
|
if (checkPermissions()) showQrCodeFragmentIfAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean gotPermission(String permission, String[] permissions,
|
||||||
|
int[] grantResults) {
|
||||||
|
for (int i = 0; i < permissions.length; i++) {
|
||||||
|
if (permission.equals(permissions[i]))
|
||||||
|
return grantResults[i] == PERMISSION_GRANTED;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldShowRationale(String permission) {
|
||||||
|
return ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||||
|
permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
String action = intent.getAction();
|
||||||
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
|
if (ACTION_STATE_CHANGED.equals(action)) {
|
||||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
|
if (state == STATE_ON)
|
||||||
|
setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||||
|
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
|
||||||
|
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||||
|
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE)
|
||||||
|
setBluetoothState(BluetoothState.DISCOVERABLE);
|
||||||
|
else if (scanMode == SCAN_MODE_CONNECTABLE)
|
||||||
|
setBluetoothState(BluetoothState.ENABLED);
|
||||||
|
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.support.annotation.LayoutRes;
|
|||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -22,14 +22,14 @@ class GroupMessageItem extends ThreadItem {
|
|||||||
|
|
||||||
private GroupMessageItem(MessageId messageId, GroupId groupId,
|
private GroupMessageItem(MessageId messageId, GroupId groupId,
|
||||||
@Nullable MessageId parentId, String text, long timestamp,
|
@Nullable MessageId parentId, String text, long timestamp,
|
||||||
Author author, Status status, boolean isRead) {
|
Author author, AuthorInfo status, boolean isRead) {
|
||||||
super(messageId, parentId, text, timestamp, author, status, isRead);
|
super(messageId, parentId, text, timestamp, author, status, isRead);
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupMessageItem(GroupMessageHeader h, String text) {
|
GroupMessageItem(GroupMessageHeader h, String text) {
|
||||||
this(h.getId(), h.getGroupId(), h.getParentId(), text, h.getTimestamp(),
|
this(h.getId(), h.getGroupId(), h.getParentId(), text, h.getTimestamp(),
|
||||||
h.getAuthor(), h.getAuthorStatus(), h.isRead());
|
h.getAuthor(), h.getAuthorInfo(), h.isRead());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupId getGroupId() {
|
public GroupId getGroupId() {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import org.briarproject.briar.R;
|
|||||||
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
|
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
|
||||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -36,24 +37,27 @@ class JoinMessageItemViewHolder
|
|||||||
if (item.isInitial()) {
|
if (item.isInitial()) {
|
||||||
textView.setText(R.string.groups_member_created_you);
|
textView.setText(R.string.groups_member_created_you);
|
||||||
} else {
|
} else {
|
||||||
textView.setText(
|
String name = getContactDisplayName(item.getAuthor(),
|
||||||
getContext().getString(R.string.groups_member_joined,
|
item.getAuthorInfo().getAlias());
|
||||||
item.getAuthor().getName()));
|
textView.setText(getContext()
|
||||||
|
.getString(R.string.groups_member_joined, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bind(JoinMessageItem item) {
|
private void bind(JoinMessageItem item) {
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
String name = getContactDisplayName(item.getAuthor(),
|
||||||
|
item.getAuthorInfo().getAlias());
|
||||||
|
|
||||||
if (item.isInitial()) {
|
if (item.isInitial()) {
|
||||||
textView.setText(ctx.getString(R.string.groups_member_created,
|
textView.setText(
|
||||||
item.getAuthor().getName()));
|
ctx.getString(R.string.groups_member_created, name));
|
||||||
} else {
|
} else {
|
||||||
if (item.getStatus() == OURSELVES) {
|
if (item.getAuthorInfo().getStatus() == OURSELVES) {
|
||||||
textView.setText(R.string.groups_member_joined_you);
|
textView.setText(R.string.groups_member_joined_you);
|
||||||
} else {
|
} else {
|
||||||
textView.setText(ctx.getString(R.string.groups_member_joined,
|
textView.setText(
|
||||||
item.getAuthor().getName()));
|
ctx.getString(R.string.groups_member_joined, name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
class GroupInvitationViewHolder
|
class GroupInvitationViewHolder
|
||||||
extends InvitationViewHolder<GroupInvitationItem> {
|
extends InvitationViewHolder<GroupInvitationItem> {
|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ class GroupInvitationViewHolder
|
|||||||
|
|
||||||
sharedBy.setText(
|
sharedBy.setText(
|
||||||
sharedBy.getContext().getString(R.string.groups_created_by,
|
sharedBy.getContext().getString(R.string.groups_created_by,
|
||||||
item.getCreator().getAuthor().getName()));
|
getContactDisplayName(item.getCreator())));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.privategroup.list;
|
package org.briarproject.briar.android.privategroup.list;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||||
@@ -12,12 +13,15 @@ import org.briarproject.briar.api.privategroup.PrivateGroup;
|
|||||||
class GroupItem {
|
class GroupItem {
|
||||||
|
|
||||||
private final PrivateGroup privateGroup;
|
private final PrivateGroup privateGroup;
|
||||||
|
private final AuthorInfo authorInfo;
|
||||||
private int messageCount, unreadCount;
|
private int messageCount, unreadCount;
|
||||||
private long timestamp;
|
private long timestamp;
|
||||||
private boolean dissolved;
|
private boolean dissolved;
|
||||||
|
|
||||||
GroupItem(PrivateGroup privateGroup, GroupCount count, boolean dissolved) {
|
GroupItem(PrivateGroup privateGroup, AuthorInfo authorInfo,
|
||||||
|
GroupCount count, boolean dissolved) {
|
||||||
this.privateGroup = privateGroup;
|
this.privateGroup = privateGroup;
|
||||||
|
this.authorInfo = authorInfo;
|
||||||
this.messageCount = count.getMsgCount();
|
this.messageCount = count.getMsgCount();
|
||||||
this.unreadCount = count.getUnreadCount();
|
this.unreadCount = count.getUnreadCount();
|
||||||
this.timestamp = count.getLatestMsgTime();
|
this.timestamp = count.getLatestMsgTime();
|
||||||
@@ -46,6 +50,10 @@ class GroupItem {
|
|||||||
return privateGroup.getCreator();
|
return privateGroup.getCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthorInfo getCreatorInfo() {
|
||||||
|
return authorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
String getName() {
|
String getName() {
|
||||||
return privateGroup.getName();
|
return privateGroup.getName();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package org.briarproject.briar.android.privategroup.list;
|
|||||||
|
|
||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
@@ -30,7 +33,9 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -52,6 +57,7 @@ class GroupListControllerImpl extends DbControllerImpl
|
|||||||
|
|
||||||
private final PrivateGroupManager groupManager;
|
private final PrivateGroupManager groupManager;
|
||||||
private final GroupInvitationManager groupInvitationManager;
|
private final GroupInvitationManager groupInvitationManager;
|
||||||
|
private final ContactManager contactManager;
|
||||||
private final AndroidNotificationManager notificationManager;
|
private final AndroidNotificationManager notificationManager;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
|
||||||
@@ -61,10 +67,12 @@ class GroupListControllerImpl extends DbControllerImpl
|
|||||||
GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||||
LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
|
LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
|
||||||
GroupInvitationManager groupInvitationManager,
|
GroupInvitationManager groupInvitationManager,
|
||||||
|
ContactManager contactManager,
|
||||||
AndroidNotificationManager notificationManager, EventBus eventBus) {
|
AndroidNotificationManager notificationManager, EventBus eventBus) {
|
||||||
super(dbExecutor, lifecycleManager);
|
super(dbExecutor, lifecycleManager);
|
||||||
this.groupManager = groupManager;
|
this.groupManager = groupManager;
|
||||||
this.groupInvitationManager = groupInvitationManager;
|
this.groupInvitationManager = groupInvitationManager;
|
||||||
|
this.contactManager = contactManager;
|
||||||
this.notificationManager = notificationManager;
|
this.notificationManager = notificationManager;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
}
|
}
|
||||||
@@ -153,12 +161,22 @@ class GroupListControllerImpl extends DbControllerImpl
|
|||||||
Collection<PrivateGroup> groups =
|
Collection<PrivateGroup> groups =
|
||||||
groupManager.getPrivateGroups();
|
groupManager.getPrivateGroups();
|
||||||
List<GroupItem> items = new ArrayList<>(groups.size());
|
List<GroupItem> items = new ArrayList<>(groups.size());
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
for (PrivateGroup g : groups) {
|
for (PrivateGroup g : groups) {
|
||||||
try {
|
try {
|
||||||
GroupId id = g.getId();
|
GroupId id = g.getId();
|
||||||
|
AuthorId authorId = g.getCreator().getId();
|
||||||
|
AuthorInfo authorInfo;
|
||||||
|
if (authorInfos.containsKey(authorId)) {
|
||||||
|
authorInfo = authorInfos.get(authorId);
|
||||||
|
} else {
|
||||||
|
authorInfo = contactManager.getAuthorInfo(authorId);
|
||||||
|
authorInfos.put(authorId, authorInfo);
|
||||||
|
}
|
||||||
GroupCount count = groupManager.getGroupCount(id);
|
GroupCount count = groupManager.getGroupCount(id);
|
||||||
boolean dissolved = groupManager.isDissolved(id);
|
boolean dissolved = groupManager.isDissolved(id);
|
||||||
items.add(new GroupItem(g, count, dissolved));
|
items.add(
|
||||||
|
new GroupItem(g, authorInfo, count, dissolved));
|
||||||
} catch (NoSuchGroupException e) {
|
} catch (NoSuchGroupException e) {
|
||||||
// Continue
|
// Continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import static android.view.View.GONE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_NAME;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_NAME;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
@@ -60,8 +61,9 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
|||||||
name.setText(group.getName());
|
name.setText(group.getName());
|
||||||
|
|
||||||
// Creator
|
// Creator
|
||||||
creator.setText(ctx.getString(R.string.groups_created_by,
|
String creatorName = getContactDisplayName(group.getCreator(),
|
||||||
group.getCreator().getName()));
|
group.getCreatorInfo().getAlias());
|
||||||
|
creator.setText(ctx.getString(R.string.groups_created_by, creatorName));
|
||||||
|
|
||||||
if (!group.isDissolved()) {
|
if (!group.isDissolved()) {
|
||||||
// full visibility
|
// full visibility
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.privategroup.memberlist;
|
package org.briarproject.briar.android.privategroup.memberlist;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -8,6 +9,8 @@ import android.view.ViewGroup;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.BriarAdapter;
|
import org.briarproject.briar.android.util.BriarAdapter;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
class MemberListAdapter extends
|
class MemberListAdapter extends
|
||||||
BriarAdapter<MemberListItem, MemberListItemHolder> {
|
BriarAdapter<MemberListItem, MemberListItemHolder> {
|
||||||
|
|
||||||
@@ -15,8 +18,9 @@ class MemberListAdapter extends
|
|||||||
super(context, MemberListItem.class);
|
super(context, MemberListItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public MemberListItemHolder onCreateViewHolder(ViewGroup viewGroup,
|
public MemberListItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
|
||||||
int i) {
|
int i) {
|
||||||
View v = LayoutInflater.from(ctx).inflate(
|
View v = LayoutInflater.from(ctx).inflate(
|
||||||
R.layout.list_item_group_member, viewGroup, false);
|
R.layout.list_item_group_member, viewGroup, false);
|
||||||
@@ -24,13 +28,18 @@ class MemberListAdapter extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(MemberListItemHolder ui, int position) {
|
public void onBindViewHolder(@NonNull MemberListItemHolder ui,
|
||||||
|
int position) {
|
||||||
ui.bind(items.get(position));
|
ui.bind(items.get(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(MemberListItem m1, MemberListItem m2) {
|
public int compare(MemberListItem m1, MemberListItem m2) {
|
||||||
return m1.getMember().getName().compareTo(m2.getMember().getName());
|
String n1 = getContactDisplayName(m1.getMember(),
|
||||||
|
m1.getAuthorInfo().getAlias());
|
||||||
|
String n2 = getContactDisplayName(m2.getMember(),
|
||||||
|
m2.getAuthorInfo().getAlias());
|
||||||
|
return n1.compareTo(n2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package org.briarproject.briar.android.privategroup.memberlist;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||||
|
|
||||||
@@ -25,8 +26,12 @@ class MemberListItem {
|
|||||||
return groupMember.getAuthor();
|
return groupMember.getAuthor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthorInfo getAuthorInfo() {
|
||||||
|
return groupMember.getAuthorInfo();
|
||||||
|
}
|
||||||
|
|
||||||
Status getStatus() {
|
Status getStatus() {
|
||||||
return groupMember.getStatus();
|
return groupMember.getAuthorInfo().getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCreator() {
|
boolean isCreator() {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.AuthorView;
|
import org.briarproject.briar.android.view.AuthorView;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -29,33 +32,33 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
protected void bind(MemberListItem item) {
|
protected void bind(MemberListItem item) {
|
||||||
// member name, avatar and status
|
// member name, avatar and status
|
||||||
author.setAuthor(item.getMember());
|
author.setAuthor(item.getMember(), item.getAuthorInfo());
|
||||||
author.setAuthorStatus(item.getStatus());
|
|
||||||
|
|
||||||
// online status of visible contacts
|
// online status of visible contacts
|
||||||
if (item.getContactId() != null) {
|
if (item.getContactId() != null) {
|
||||||
bulb.setVisibility(View.VISIBLE);
|
bulb.setVisibility(VISIBLE);
|
||||||
if (item.isOnline()) {
|
if (item.isOnline()) {
|
||||||
bulb.setImageResource(R.drawable.contact_connected);
|
bulb.setImageResource(R.drawable.contact_connected);
|
||||||
} else {
|
} else {
|
||||||
bulb.setImageResource(R.drawable.contact_disconnected);
|
bulb.setImageResource(R.drawable.contact_disconnected);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bulb.setVisibility(View.GONE);
|
bulb.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// text shown for creator
|
// text shown for creator
|
||||||
if (item.isCreator()) {
|
if (item.isCreator()) {
|
||||||
creator.setVisibility(View.VISIBLE);
|
creator.setVisibility(VISIBLE);
|
||||||
if (item.getStatus() == OURSELVES) {
|
if (item.getStatus() == OURSELVES) {
|
||||||
creator.setText(R.string.groups_member_created_you);
|
creator.setText(R.string.groups_member_created_you);
|
||||||
} else {
|
} else {
|
||||||
|
String name = getContactDisplayName(item.getMember(),
|
||||||
|
item.getAuthorInfo().getAlias());
|
||||||
creator.setText(creator.getContext()
|
creator.setText(creator.getContext()
|
||||||
.getString(R.string.groups_member_created,
|
.getString(R.string.groups_member_created, name));
|
||||||
item.getMember().getName()));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
creator.setVisibility(View.GONE);
|
creator.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import javax.annotation.Nullable;
|
|||||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
|
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityIcon;
|
||||||
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
|
import static org.briarproject.briar.android.privategroup.VisibilityHelper.getVisibilityString;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.GREY_OUT;
|
import static org.briarproject.briar.android.util.UiUtils.GREY_OUT;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -36,7 +37,7 @@ class RevealableContactViewHolder
|
|||||||
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
|
icon.setImageResource(getVisibilityIcon(item.getVisibility()));
|
||||||
info.setText(
|
info.setText(
|
||||||
getVisibilityString(info.getContext(), item.getVisibility(),
|
getVisibilityString(info.getContext(), item.getVisibility(),
|
||||||
item.getContact().getAuthor().getName()));
|
getContactDisplayName(item.getContact())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
|||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -210,6 +211,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (SDK_INT < 27) {
|
||||||
|
// remove System Default Theme option
|
||||||
|
List<CharSequence> entries =
|
||||||
|
new ArrayList<>(Arrays.asList(theme.getEntries()));
|
||||||
|
entries.remove(getString(R.string.pref_theme_system));
|
||||||
|
theme.setEntries(entries.toArray(new CharSequence[0]));
|
||||||
|
}
|
||||||
if (IS_DEBUG_BUILD) {
|
if (IS_DEBUG_BUILD) {
|
||||||
findPreference("pref_key_explode").setOnPreferenceClickListener(
|
findPreference("pref_key_explode").setOnPreferenceClickListener(
|
||||||
preference -> {
|
preference -> {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
|
|
||||||
class SharingInvitationViewHolder
|
class SharingInvitationViewHolder
|
||||||
extends InvitationViewHolder<SharingInvitationItem> {
|
extends InvitationViewHolder<SharingInvitationItem> {
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ class SharingInvitationViewHolder
|
|||||||
|
|
||||||
Collection<String> names = new ArrayList<>();
|
Collection<String> names = new ArrayList<>();
|
||||||
for (Contact c : item.getNewSharers())
|
for (Contact c : item.getNewSharers())
|
||||||
names.add(c.getAuthor().getName());
|
names.add(getContactDisplayName(c));
|
||||||
sharedBy.setText(
|
sharedBy.setText(
|
||||||
sharedBy.getContext().getString(R.string.shared_by_format,
|
sharedBy.getContext().getString(R.string.shared_by_format,
|
||||||
StringUtils.join(names, ", ")));
|
StringUtils.join(names, ", ")));
|
||||||
|
|||||||
@@ -43,9 +43,8 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
|||||||
public void bind(I item, ThreadItemListener<I> listener) {
|
public void bind(I item, ThreadItemListener<I> listener) {
|
||||||
textView.setText(StringUtils.trim(item.getText()));
|
textView.setText(StringUtils.trim(item.getText()));
|
||||||
|
|
||||||
author.setAuthor(item.getAuthor());
|
author.setAuthor(item.getAuthor(), item.getAuthorInfo());
|
||||||
author.setDate(item.getTimestamp());
|
author.setDate(item.getTimestamp());
|
||||||
author.setAuthorStatus(item.getStatus());
|
|
||||||
|
|
||||||
if (item.isHighlighted()) {
|
if (item.isHighlighted()) {
|
||||||
layout.setActivated(true);
|
layout.setActivated(true);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.briar.android.threaded;
|
package org.briarproject.briar.android.threaded;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.client.MessageTree.MessageNode;
|
import org.briarproject.briar.api.client.MessageTree.MessageNode;
|
||||||
@@ -21,19 +21,19 @@ public abstract class ThreadItem implements MessageNode {
|
|||||||
private final String text;
|
private final String text;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final Status status;
|
private final AuthorInfo authorInfo;
|
||||||
private int level = UNDEFINED;
|
private int level = UNDEFINED;
|
||||||
private boolean isRead, highlighted;
|
private boolean isRead, highlighted;
|
||||||
|
|
||||||
public ThreadItem(MessageId messageId, @Nullable MessageId parentId,
|
public ThreadItem(MessageId messageId, @Nullable MessageId parentId,
|
||||||
String text, long timestamp, Author author, Status status,
|
String text, long timestamp, Author author, AuthorInfo authorInfo,
|
||||||
boolean isRead) {
|
boolean isRead) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.parentId = parentId;
|
this.parentId = parentId;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.status = status;
|
this.authorInfo = authorInfo;
|
||||||
this.isRead = isRead;
|
this.isRead = isRead;
|
||||||
this.highlighted = false;
|
this.highlighted = false;
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,8 @@ public abstract class ThreadItem implements MessageNode {
|
|||||||
return author;
|
return author;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public AuthorInfo getAuthorInfo() {
|
||||||
return status;
|
return authorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ import android.view.View;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
@@ -74,6 +76,17 @@ public class UiUtils {
|
|||||||
public static final int TEASER_LENGTH = 320;
|
public static final int TEASER_LENGTH = 320;
|
||||||
public static final float GREY_OUT = 0.5f;
|
public static final float GREY_OUT = 0.5f;
|
||||||
|
|
||||||
|
public static String getContactDisplayName(Author author,
|
||||||
|
@Nullable String alias) {
|
||||||
|
String name = author.getName();
|
||||||
|
if (alias == null) return name;
|
||||||
|
else return String.format("%s (%s)", alias, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getContactDisplayName(Contact c) {
|
||||||
|
return getContactDisplayName(c.getAuthor(), c.getAlias());
|
||||||
|
}
|
||||||
|
|
||||||
public static void setError(TextInputLayout til, @Nullable String error,
|
public static void setError(TextInputLayout til, @Nullable String error,
|
||||||
boolean set) {
|
boolean set) {
|
||||||
if (set) {
|
if (set) {
|
||||||
@@ -127,7 +140,9 @@ public class UiUtils {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Spanned getSpanned(String s) {
|
public static Spanned getSpanned(@Nullable String s) {
|
||||||
|
// TODO move to HtmlCompat
|
||||||
|
// https://commonsware.com/blog/2018/05/29/at-last-htmlcompat.html
|
||||||
return Html.fromHtml(s);
|
return Html.fromHtml(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
@@ -24,8 +24,9 @@ import im.delight.android.identicons.IdenticonDrawable;
|
|||||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
import static android.graphics.Typeface.BOLD;
|
import static android.graphics.Typeface.BOLD;
|
||||||
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
|
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -70,24 +71,20 @@ public class AuthorView extends ConstraintLayout {
|
|||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthor(Author author) {
|
public void setAuthor(Author author, AuthorInfo authorInfo) {
|
||||||
authorName.setText(author.getName());
|
authorName
|
||||||
|
.setText(getContactDisplayName(author, authorInfo.getAlias()));
|
||||||
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
|
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
|
||||||
avatar.setImageDrawable(d);
|
avatar.setImageDrawable(d);
|
||||||
|
|
||||||
invalidate();
|
if (authorInfo.getStatus() != NONE) {
|
||||||
requestLayout();
|
trustIndicator.setTrustLevel(authorInfo.getStatus());
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthorStatus(Status status) {
|
|
||||||
if (status != NONE) {
|
|
||||||
trustIndicator.setTrustLevel(status);
|
|
||||||
trustIndicator.setVisibility(VISIBLE);
|
trustIndicator.setVisibility(VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
trustIndicator.setVisibility(GONE);
|
trustIndicator.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == OURSELVES) {
|
if (authorInfo.getStatus() == OURSELVES) {
|
||||||
authorName.setTypeface(authorNameTypeface, BOLD);
|
authorName.setTypeface(authorNameTypeface, BOLD);
|
||||||
} else {
|
} else {
|
||||||
authorName.setTypeface(authorNameTypeface, NORMAL);
|
authorName.setTypeface(authorNameTypeface, NORMAL);
|
||||||
@@ -123,7 +120,7 @@ public class AuthorView extends ConstraintLayout {
|
|||||||
*
|
*
|
||||||
* Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar
|
* Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar
|
||||||
* and override the one set by
|
* and override the one set by
|
||||||
* {@link AuthorView#setAuthor(Author)}.
|
* {@link AuthorView#setAuthor(Author, AuthorInfo)}.
|
||||||
*/
|
*/
|
||||||
public void setPersona(int persona) {
|
public void setPersona(int persona) {
|
||||||
switch (persona) {
|
switch (persona) {
|
||||||
|
|||||||
@@ -20,17 +20,12 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||||
|
|
||||||
public class BriarRecyclerView extends FrameLayout {
|
public class BriarRecyclerView extends FrameLayout {
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(BriarRecyclerView.class.getName());
|
|
||||||
|
|
||||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
@@ -39,6 +34,7 @@ public class BriarRecyclerView extends FrameLayout {
|
|||||||
private TextView emptyText, emptyAction;
|
private TextView emptyText, emptyAction;
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private RecyclerView.AdapterDataObserver emptyObserver;
|
private RecyclerView.AdapterDataObserver emptyObserver;
|
||||||
|
@Nullable
|
||||||
private Runnable refresher = null;
|
private Runnable refresher = null;
|
||||||
private boolean isScrollingToEnd = false;
|
private boolean isScrollingToEnd = false;
|
||||||
|
|
||||||
@@ -217,18 +213,15 @@ public class BriarRecyclerView extends FrameLayout {
|
|||||||
throw new IllegalStateException("Need to call setAdapter() first!");
|
throw new IllegalStateException("Need to call setAdapter() first!");
|
||||||
}
|
}
|
||||||
refresher = () -> {
|
refresher = () -> {
|
||||||
LOG.info("Updating Content...");
|
|
||||||
Adapter adapter = recyclerView.getAdapter();
|
Adapter adapter = recyclerView.getAdapter();
|
||||||
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
|
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
|
||||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||||
};
|
};
|
||||||
LOG.info("Adding Handler Callback");
|
|
||||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopPeriodicUpdate() {
|
public void stopPeriodicUpdate() {
|
||||||
if (refresher != null) {
|
if (refresher != null) {
|
||||||
LOG.info("Removing Handler Callback");
|
|
||||||
handler.removeCallbacks(refresher);
|
handler.removeCallbacks(refresher);
|
||||||
refresher = null;
|
refresher = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package org.briarproject.briar.android.view;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v7.widget.AppCompatImageView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
public class TrustIndicatorView extends ImageView {
|
public class TrustIndicatorView extends AppCompatImageView {
|
||||||
|
|
||||||
public TrustIndicatorView(Context context) {
|
public TrustIndicatorView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<!-- Workaround for toolbar icons failing to apply proper layout direction -->
|
||||||
|
<group
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12"
|
||||||
|
android:rotation="180">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:autoMirrored="true"
|
|
||||||
android:viewportHeight="24.0"
|
android:viewportHeight="24.0"
|
||||||
android:viewportWidth="24.0">
|
android:viewportWidth="24.0">
|
||||||
<path
|
<path
|
||||||
|
|||||||
52
briar-android/src/main/res/layout/fragment_alias_dialog.xml
Normal file
52
briar-android/src/main/res/layout/fragment_alias_dialog.xml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/set_contact_alias"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/aliasEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:hint="@string/set_contact_alias_hint"
|
||||||
|
android:inputType="textPersonName"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_xxlarge"
|
||||||
|
android:layout_marginStart="@dimen/margin_xxlarge"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/cancelButton"
|
||||||
|
style="@style/BriarButtonFlat.Negative"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/cancel"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/setButton"
|
||||||
|
style="@style/BriarButtonFlat.Positive"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/set_alias"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,75 +1,69 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/link_warning_title"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:text="@string/link_warning_intro"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/urlView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="@dimen/text_size_medium"
|
||||||
|
android:typeface="monospace"
|
||||||
|
tools:text="http://very.bad.site.com"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:text="@string/link_warning_text"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="@dimen/text_size_medium"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="@dimen/margin_large">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/link_warning_title"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:text="@string/link_warning_intro"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/urlView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:typeface="monospace"
|
|
||||||
tools:text="http://very.bad.site.com"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:text="@string/link_warning_text"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="@dimen/text_size_medium"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/button_size">
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_xxlarge"
|
||||||
|
android:layout_marginStart="@dimen/margin_xxlarge"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/cancelButton"
|
android:id="@+id/cancelButton"
|
||||||
style="@style/BriarButtonFlat.Positive"
|
style="@style/BriarButtonFlat.Positive"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/cancel"
|
android:text="@string/cancel"/>
|
||||||
app:layout_constraintEnd_toStartOf="@+id/openButton"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/openButton"
|
android:id="@+id/openButton"
|
||||||
style="@style/BriarButtonFlat.Negative"
|
style="@style/BriarButtonFlat.Negative"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/link_warning_open_link"
|
android:text="@string/link_warning_open_link"/>
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/cancelButton"/>
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -10,6 +10,12 @@
|
|||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_set_alias"
|
||||||
|
android:title="@string/set_contact_alias"
|
||||||
|
android:enabled="true"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_social_remove_person"
|
android:id="@+id/action_social_remove_person"
|
||||||
android:icon="@drawable/action_delete_white"
|
android:icon="@drawable/action_delete_white"
|
||||||
|
|||||||
@@ -63,10 +63,10 @@
|
|||||||
<item>@string/pref_theme_auto</item>
|
<item>@string/pref_theme_auto</item>
|
||||||
<item>@string/pref_theme_system</item>
|
<item>@string/pref_theme_system</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="pref_theme_light_value">light</string>
|
<string name="pref_theme_light_value" translatable="false">light</string>
|
||||||
<string name="pref_theme_dark_value">dark</string>
|
<string name="pref_theme_dark_value" translatable="false">dark</string>
|
||||||
<string name="pref_theme_auto_value">auto</string>
|
<string name="pref_theme_auto_value" translatable="false">auto</string>
|
||||||
<string name="pref_theme_system_value">system</string>
|
<string name="pref_theme_system_value" translatable="false">system</string>
|
||||||
<string-array name="pref_theme_values">
|
<string-array name="pref_theme_values">
|
||||||
<item>@string/pref_theme_light_value</item>
|
<item>@string/pref_theme_light_value</item>
|
||||||
<item>@string/pref_theme_dark_value</item>
|
<item>@string/pref_theme_dark_value</item>
|
||||||
@@ -82,8 +82,8 @@
|
|||||||
<item>@string/pref_lock_timeout_30</item>
|
<item>@string/pref_lock_timeout_30</item>
|
||||||
<item>@string/pref_lock_timeout_60</item>
|
<item>@string/pref_lock_timeout_60</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="pref_lock_timeout_value_default">5</string>
|
<string name="pref_lock_timeout_value_default" translatable="false">5</string>
|
||||||
<string name="pref_lock_timeout_value_never">-1</string>
|
<string name="pref_lock_timeout_value_never" translatable="false">-1</string>
|
||||||
<string-array name="pref_key_lock_timeout_values">
|
<string-array name="pref_key_lock_timeout_values">
|
||||||
<item>@string/pref_lock_timeout_value_never</item>
|
<item>@string/pref_lock_timeout_value_never</item>
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
|
|||||||
@@ -127,6 +127,9 @@
|
|||||||
<string name="date_no_private_messages">No messages.</string>
|
<string name="date_no_private_messages">No messages.</string>
|
||||||
<string name="no_private_messages">No messages to show</string>
|
<string name="no_private_messages">No messages to show</string>
|
||||||
<string name="message_hint">Type message</string>
|
<string name="message_hint">Type message</string>
|
||||||
|
<string name="set_contact_alias">Set alias name</string>
|
||||||
|
<string name="set_contact_alias_hint">Contact alias</string>
|
||||||
|
<string name="set_alias">Set Alias</string>
|
||||||
<string name="delete_contact">Delete contact</string>
|
<string name="delete_contact">Delete contact</string>
|
||||||
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
<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="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
|
||||||
@@ -468,8 +471,11 @@
|
|||||||
<!-- Permission Requests -->
|
<!-- Permission Requests -->
|
||||||
<string name="permission_camera_title">Camera permission</string>
|
<string name="permission_camera_title">Camera permission</string>
|
||||||
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
|
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
|
||||||
|
<string name="permission_location_title">Location permission</string>
|
||||||
|
<string name="permission_location_request_body">To discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||||
|
<string name="permission_camera_location_title">Camera and location</string>
|
||||||
|
<string name="permission_camera_location_request_body">To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
|
||||||
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
|
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
|
||||||
<string name="permission_camera_denied_toast">Camera permission was not granted</string>
|
|
||||||
<string name="qr_code">QR code</string>
|
<string name="qr_code">QR code</string>
|
||||||
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
|
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import junit.framework.Assert;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.TestBriarApplication;
|
import org.briarproject.briar.android.TestBriarApplication;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
|
||||||
@@ -26,7 +27,7 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
import static junit.framework.Assert.assertEquals;
|
import static junit.framework.Assert.assertEquals;
|
||||||
import static junit.framework.Assert.assertTrue;
|
import static junit.framework.Assert.assertTrue;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
@@ -87,7 +88,8 @@ public class ForumActivityTest {
|
|||||||
Author author = getAuthor();
|
Author author = getAuthor();
|
||||||
String text = getRandomString(MAX_FORUM_POST_TEXT_LENGTH);
|
String text = getRandomString(MAX_FORUM_POST_TEXT_LENGTH);
|
||||||
forumItems[i] = new ForumItem(MESSAGE_IDS[i], PARENT_IDS[i],
|
forumItems[i] = new ForumItem(MESSAGE_IDS[i], PARENT_IDS[i],
|
||||||
text, System.currentTimeMillis(), author, UNKNOWN);
|
text, System.currentTimeMillis(), author,
|
||||||
|
new AuthorInfo(UNKNOWN));
|
||||||
forumItems[i].setLevel(LEVELS[i]);
|
forumItems[i].setLevel(LEVELS[i]);
|
||||||
}
|
}
|
||||||
ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
|
ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ dependencyVerification {
|
|||||||
'android.arch.core:common:1.1.1:common-1.1.1.jar:3a616a32f433e9e23f556b38575c31b013613d3ae85206263b7625fe1f4c151a',
|
'android.arch.core:common:1.1.1:common-1.1.1.jar:3a616a32f433e9e23f556b38575c31b013613d3ae85206263b7625fe1f4c151a',
|
||||||
'android.arch.core:runtime:1.1.1:runtime-1.1.1.aar:c3215aa5873311b3f88a6f4e4a3c25ad89971bc127de8c3e1291c57f93a05c39',
|
'android.arch.core:runtime:1.1.1:runtime-1.1.1.aar:c3215aa5873311b3f88a6f4e4a3c25ad89971bc127de8c3e1291c57f93a05c39',
|
||||||
'android.arch.lifecycle:common:1.1.1:common-1.1.1.jar:8d378e88ebd5189e09eef623414812c868fd90aa519d6160e2311fb8b81cff56',
|
'android.arch.lifecycle:common:1.1.1:common-1.1.1.jar:8d378e88ebd5189e09eef623414812c868fd90aa519d6160e2311fb8b81cff56',
|
||||||
|
'android.arch.lifecycle:extensions:1.1.1:extensions-1.1.1.aar:429426b2feec2245ffc5e75b3b5309bedb36159cf06dc71843ae43526ac289b6',
|
||||||
'android.arch.lifecycle:livedata-core:1.1.1:livedata-core-1.1.1.aar:d6fdd8b985d6178d7ea2f16986a24e83f1bee936b74d43167c69e08d3cc12c50',
|
'android.arch.lifecycle:livedata-core:1.1.1:livedata-core-1.1.1.aar:d6fdd8b985d6178d7ea2f16986a24e83f1bee936b74d43167c69e08d3cc12c50',
|
||||||
'android.arch.lifecycle:livedata:1.1.1:livedata-1.1.1.aar:50ab0490c1ff1a7cfb4e554032998b080888946d0dd424f39900efc4a1bcd750',
|
'android.arch.lifecycle:livedata:1.1.1:livedata-1.1.1.aar:50ab0490c1ff1a7cfb4e554032998b080888946d0dd424f39900efc4a1bcd750',
|
||||||
'android.arch.lifecycle:runtime:1.1.1:runtime-1.1.1.aar:c4e4be66c1b2f0abec593571454e1de14013f7e0f96bf2a9f212931a48cae550',
|
'android.arch.lifecycle:runtime:1.1.1:runtime-1.1.1.aar:c4e4be66c1b2f0abec593571454e1de14013f7e0f96bf2a9f212931a48cae550',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.briar.api.blog;
|
package org.briarproject.briar.api.blog;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -23,10 +23,10 @@ public class BlogCommentHeader extends BlogPostHeader {
|
|||||||
public BlogCommentHeader(MessageType type, GroupId groupId,
|
public BlogCommentHeader(MessageType type, GroupId groupId,
|
||||||
@Nullable String comment, BlogPostHeader parent, MessageId id,
|
@Nullable String comment, BlogPostHeader parent, MessageId id,
|
||||||
long timestamp, long timeReceived, Author author,
|
long timestamp, long timeReceived, Author author,
|
||||||
Status authorStatus, boolean read) {
|
AuthorInfo authorInfo, boolean read) {
|
||||||
|
|
||||||
super(type, groupId, id, parent.getId(), timestamp,
|
super(type, groupId, id, parent.getId(), timestamp,
|
||||||
timeReceived, author, authorStatus, false, read);
|
timeReceived, author, authorInfo, false, read);
|
||||||
|
|
||||||
if (type != COMMENT && type != WRAPPED_COMMENT)
|
if (type != COMMENT && type != WRAPPED_COMMENT)
|
||||||
throw new IllegalArgumentException("Incompatible Message Type");
|
throw new IllegalArgumentException("Incompatible Message Type");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.briar.api.blog;
|
package org.briarproject.briar.api.blog;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -21,8 +21,8 @@ public class BlogPostHeader extends PostHeader {
|
|||||||
|
|
||||||
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
|
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
|
||||||
@Nullable MessageId parentId, long timestamp, long timeReceived,
|
@Nullable MessageId parentId, long timestamp, long timeReceived,
|
||||||
Author author, Status authorStatus, boolean rssFeed, boolean read) {
|
Author author, AuthorInfo authorInfo, boolean rssFeed, boolean read) {
|
||||||
super(id, parentId, timestamp, author, authorStatus, read);
|
super(id, parentId, timestamp, author, authorInfo, read);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.timeReceived = timeReceived;
|
this.timeReceived = timeReceived;
|
||||||
@@ -31,9 +31,9 @@ public class BlogPostHeader extends PostHeader {
|
|||||||
|
|
||||||
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
|
public BlogPostHeader(MessageType type, GroupId groupId, MessageId id,
|
||||||
long timestamp, long timeReceived, Author author,
|
long timestamp, long timeReceived, Author author,
|
||||||
Status authorStatus, boolean rssFeed, boolean read) {
|
AuthorInfo authorInfo, boolean rssFeed, boolean read) {
|
||||||
this(type, groupId, id, null, timestamp, timeReceived, author,
|
this(type, groupId, id, null, timestamp, timeReceived, author,
|
||||||
authorStatus, rssFeed, read);
|
authorInfo, rssFeed, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageType getType() {
|
public MessageType getType() {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package org.briarproject.briar.api.client;
|
package org.briarproject.briar.api.client;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
@@ -17,16 +18,16 @@ public abstract class PostHeader {
|
|||||||
private final MessageId parentId;
|
private final MessageId parentId;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final Status authorStatus;
|
private final AuthorInfo authorInfo;
|
||||||
private final boolean read;
|
private final boolean read;
|
||||||
|
|
||||||
public PostHeader(MessageId id, @Nullable MessageId parentId,
|
public PostHeader(MessageId id, @Nullable MessageId parentId,
|
||||||
long timestamp, Author author, Status authorStatus, boolean read) {
|
long timestamp, Author author, AuthorInfo authorInfo, boolean read) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.parentId = parentId;
|
this.parentId = parentId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.authorStatus = authorStatus;
|
this.authorInfo = authorInfo;
|
||||||
this.read = read;
|
this.read = read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,11 @@ public abstract class PostHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Status getAuthorStatus() {
|
public Status getAuthorStatus() {
|
||||||
return authorStatus;
|
return authorInfo.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorInfo getAuthorInfo() {
|
||||||
|
return authorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTimestamp() {
|
public long getTimestamp() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.api.forum;
|
package org.briarproject.briar.api.forum;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.client.PostHeader;
|
import org.briarproject.briar.api.client.PostHeader;
|
||||||
@@ -13,9 +14,9 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
public class ForumPostHeader extends PostHeader {
|
public class ForumPostHeader extends PostHeader {
|
||||||
|
|
||||||
public ForumPostHeader(MessageId id, @Nullable MessageId parentId,
|
public ForumPostHeader(MessageId id, @Nullable MessageId parentId,
|
||||||
long timestamp, Author author, Author.Status authorStatus,
|
long timestamp, Author author, AuthorInfo authorInfo,
|
||||||
boolean read) {
|
boolean read) {
|
||||||
super(id, parentId, timestamp, author, authorStatus, read);
|
super(id, parentId, timestamp, author, authorInfo, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.api.introduction;
|
package org.briarproject.briar.api.introduction;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -15,19 +16,24 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class IntroductionRequest extends PrivateRequest<Author> {
|
public class IntroductionRequest extends PrivateRequest<Author> {
|
||||||
|
|
||||||
private final boolean contact;
|
private final AuthorInfo authorInfo;
|
||||||
|
|
||||||
public IntroductionRequest(MessageId messageId, GroupId groupId,
|
public IntroductionRequest(MessageId messageId, GroupId groupId,
|
||||||
long time, boolean local, boolean sent, boolean seen, boolean read,
|
long time, boolean local, boolean sent, boolean seen, boolean read,
|
||||||
SessionId sessionId, Author author, @Nullable String text,
|
SessionId sessionId, Author author, @Nullable String text,
|
||||||
boolean answered, boolean contact) {
|
boolean answered, AuthorInfo authorInfo) {
|
||||||
super(messageId, groupId, time, local, sent, seen, read, sessionId,
|
super(messageId, groupId, time, local, sent, seen, read, sessionId,
|
||||||
author, text, answered);
|
author, text, answered);
|
||||||
this.contact = contact;
|
this.authorInfo = authorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getAlias() {
|
||||||
|
return authorInfo.getAlias();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isContact() {
|
public boolean isContact() {
|
||||||
return contact;
|
return authorInfo.getStatus().isContact();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.api.introduction;
|
package org.briarproject.briar.api.introduction;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -17,14 +18,17 @@ import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
|
|||||||
public class IntroductionResponse extends PrivateResponse {
|
public class IntroductionResponse extends PrivateResponse {
|
||||||
|
|
||||||
private final Author introducedAuthor;
|
private final Author introducedAuthor;
|
||||||
|
private final AuthorInfo introducedAuthorInfo;
|
||||||
private final Role ourRole;
|
private final Role ourRole;
|
||||||
|
|
||||||
public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
|
public IntroductionResponse(MessageId messageId, GroupId groupId, long time,
|
||||||
boolean local, boolean sent, boolean seen, boolean read,
|
boolean local, boolean sent, boolean seen, boolean read,
|
||||||
SessionId sessionId, boolean accepted, Author author, Role role) {
|
SessionId sessionId, boolean accepted, Author author,
|
||||||
|
AuthorInfo introducedAuthorInfo, Role role) {
|
||||||
super(messageId, groupId, time, local, sent, seen, read, sessionId,
|
super(messageId, groupId, time, local, sent, seen, read, sessionId,
|
||||||
accepted);
|
accepted);
|
||||||
this.introducedAuthor = author;
|
this.introducedAuthor = author;
|
||||||
|
this.introducedAuthorInfo = introducedAuthorInfo;
|
||||||
this.ourRole = role;
|
this.ourRole = role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +36,10 @@ public class IntroductionResponse extends PrivateResponse {
|
|||||||
return introducedAuthor;
|
return introducedAuthor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthorInfo getIntroducedAuthorInfo() {
|
||||||
|
return introducedAuthorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isIntroducer() {
|
public boolean isIntroducer() {
|
||||||
return ourRole == INTRODUCER;
|
return ourRole == INTRODUCER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.api.privategroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -13,16 +13,16 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
public class GroupMember {
|
public class GroupMember {
|
||||||
|
|
||||||
private final Author author;
|
private final Author author;
|
||||||
private final Status status;
|
private final AuthorInfo authorInfo;
|
||||||
private final boolean isCreator;
|
private final boolean isCreator;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final Visibility visibility;
|
private final Visibility visibility;
|
||||||
|
|
||||||
public GroupMember(Author author, Status status, boolean isCreator,
|
public GroupMember(Author author, AuthorInfo status, boolean isCreator,
|
||||||
@Nullable ContactId contactId, Visibility visibility) {
|
@Nullable ContactId contactId, Visibility visibility) {
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.status = status;
|
this.authorInfo = status;
|
||||||
this.isCreator = isCreator;
|
this.isCreator = isCreator;
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
this.visibility = visibility;
|
this.visibility = visibility;
|
||||||
@@ -32,8 +32,8 @@ public class GroupMember {
|
|||||||
return author;
|
return author;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getStatus() {
|
public AuthorInfo getAuthorInfo() {
|
||||||
return status;
|
return authorInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCreator() {
|
public boolean isCreator() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.briarproject.briar.api.privategroup;
|
package org.briarproject.briar.api.privategroup;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
@@ -18,8 +18,8 @@ public class GroupMessageHeader extends PostHeader {
|
|||||||
|
|
||||||
public GroupMessageHeader(GroupId groupId, MessageId id,
|
public GroupMessageHeader(GroupId groupId, MessageId id,
|
||||||
@Nullable MessageId parentId, long timestamp,
|
@Nullable MessageId parentId, long timestamp,
|
||||||
Author author, Status authorStatus, boolean read) {
|
Author author, AuthorInfo authorInfo, boolean read) {
|
||||||
super(id, parentId, timestamp, author, authorStatus, read);
|
super(id, parentId, timestamp, author, authorInfo, read);
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class JoinMessageHeader extends GroupMessageHeader {
|
|||||||
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility,
|
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility,
|
||||||
boolean isInitial) {
|
boolean isInitial) {
|
||||||
super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
|
super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
|
||||||
h.getAuthor(), h.getAuthorStatus(), h.isRead());
|
h.getAuthor(), h.getAuthorInfo(), h.isRead());
|
||||||
this.visibility = visibility;
|
this.visibility = visibility;
|
||||||
this.isInitial = isInitial;
|
this.isInitial = isInitial;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.blog;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
import org.briarproject.bramble.api.contact.ContactManager.ContactHook;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
@@ -12,8 +13,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -49,6 +50,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||||
@@ -68,17 +70,19 @@ import static org.briarproject.briar.api.blog.MessageType.WRAPPED_POST;
|
|||||||
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||||
ContactHook, Client {
|
ContactHook, Client {
|
||||||
|
|
||||||
|
private final ContactManager contactManager;
|
||||||
private final IdentityManager identityManager;
|
private final IdentityManager identityManager;
|
||||||
private final BlogFactory blogFactory;
|
private final BlogFactory blogFactory;
|
||||||
private final BlogPostFactory blogPostFactory;
|
private final BlogPostFactory blogPostFactory;
|
||||||
private final List<RemoveBlogHook> removeHooks;
|
private final List<RemoveBlogHook> removeHooks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
|
BlogManagerImpl(DatabaseComponent db, ContactManager contactManager,
|
||||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
IdentityManager identityManager, ClientHelper clientHelper,
|
||||||
BlogFactory blogFactory, BlogPostFactory blogPostFactory) {
|
MetadataParser metadataParser, BlogFactory blogFactory,
|
||||||
|
BlogPostFactory blogPostFactory) {
|
||||||
super(db, clientHelper, metadataParser);
|
super(db, clientHelper, metadataParser);
|
||||||
|
this.contactManager = contactManager;
|
||||||
this.identityManager = identityManager;
|
this.identityManager = identityManager;
|
||||||
this.blogFactory = blogFactory;
|
this.blogFactory = blogFactory;
|
||||||
this.blogPostFactory = blogPostFactory;
|
this.blogPostFactory = blogPostFactory;
|
||||||
@@ -501,25 +505,24 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
new HashMap<>(metadata1.size() + metadata2.size());
|
new HashMap<>(metadata1.size() + metadata2.size());
|
||||||
metadata.putAll(metadata1);
|
metadata.putAll(metadata1);
|
||||||
metadata.putAll(metadata2);
|
metadata.putAll(metadata2);
|
||||||
// get all authors we need to get the status for
|
// get all authors we need to get the information for
|
||||||
Set<AuthorId> authors = new HashSet<>();
|
Set<AuthorId> authors = new HashSet<>();
|
||||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||||
BdfList authorList = entry.getValue().getList(KEY_AUTHOR);
|
BdfList authorList = entry.getValue().getList(KEY_AUTHOR);
|
||||||
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
||||||
authors.add(a.getId());
|
authors.add(a.getId());
|
||||||
}
|
}
|
||||||
// get statuses for all authors
|
// get information for all authors
|
||||||
Map<AuthorId, Status> authorStatuses = new HashMap<>();
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
for (AuthorId authorId : authors) {
|
for (AuthorId authorId : authors) {
|
||||||
authorStatuses.put(authorId,
|
authorInfos.put(authorId,
|
||||||
identityManager.getAuthorStatus(txn, authorId));
|
contactManager.getAuthorInfo(txn, authorId));
|
||||||
}
|
}
|
||||||
// get post headers
|
// get post headers
|
||||||
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
|
||||||
BdfDictionary meta = entry.getValue();
|
BdfDictionary meta = entry.getValue();
|
||||||
BlogPostHeader h =
|
BlogPostHeader h = getPostHeaderFromMetadata(txn, g,
|
||||||
getPostHeaderFromMetadata(txn, g, entry.getKey(), meta,
|
entry.getKey(), meta, authorInfos);
|
||||||
authorStatuses);
|
|
||||||
headers.add(h);
|
headers.add(h);
|
||||||
}
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -563,7 +566,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
|
|
||||||
private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
|
private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
|
||||||
GroupId groupId, MessageId id, BdfDictionary meta,
|
GroupId groupId, MessageId id, BdfDictionary meta,
|
||||||
Map<AuthorId, Status> authorStatuses)
|
Map<AuthorId, AuthorInfo> authorInfos)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
|
|
||||||
MessageType type = getMessageType(meta);
|
MessageType type = getMessageType(meta);
|
||||||
@@ -574,13 +577,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
BdfList authorList = meta.getList(KEY_AUTHOR);
|
BdfList authorList = meta.getList(KEY_AUTHOR);
|
||||||
Author author = clientHelper.parseAndValidateAuthor(authorList);
|
Author author = clientHelper.parseAndValidateAuthor(authorList);
|
||||||
boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false);
|
boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false);
|
||||||
Status authorStatus;
|
AuthorInfo authorInfo;
|
||||||
if (isFeedPost) {
|
if (isFeedPost) {
|
||||||
authorStatus = Status.NONE;
|
authorInfo = new AuthorInfo(NONE);
|
||||||
} else if (authorStatuses.containsKey(author.getId())) {
|
} else if (authorInfos.containsKey(author.getId())) {
|
||||||
authorStatus = authorStatuses.get(author.getId());
|
authorInfo = authorInfos.get(author.getId());
|
||||||
} else {
|
} else {
|
||||||
authorStatus = identityManager.getAuthorStatus(txn, author.getId());
|
authorInfo = contactManager.getAuthorInfo(txn, author.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean read = meta.getBoolean(KEY_READ, false);
|
boolean read = meta.getBoolean(KEY_READ, false);
|
||||||
@@ -591,10 +594,10 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
BlogPostHeader parent =
|
BlogPostHeader parent =
|
||||||
getPostHeaderFromMetadata(txn, groupId, parentId);
|
getPostHeaderFromMetadata(txn, groupId, parentId);
|
||||||
return new BlogCommentHeader(type, groupId, comment, parent, id,
|
return new BlogCommentHeader(type, groupId, comment, parent, id,
|
||||||
timestamp, timeReceived, author, authorStatus, read);
|
timestamp, timeReceived, author, authorInfo, read);
|
||||||
} else {
|
} else {
|
||||||
return new BlogPostHeader(type, groupId, id, timestamp,
|
return new BlogPostHeader(type, groupId, id, timestamp,
|
||||||
timeReceived, author, authorStatus, isFeedPost, read);
|
timeReceived, author, authorInfo, isFeedPost, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.forum;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.MetadataParser;
|
import org.briarproject.bramble.api.data.MetadataParser;
|
||||||
@@ -9,9 +10,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
@@ -45,7 +45,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
|
||||||
@@ -56,19 +56,19 @@ import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
||||||
|
|
||||||
private final IdentityManager identityManager;
|
private final ContactManager contactManager;
|
||||||
private final ForumFactory forumFactory;
|
private final ForumFactory forumFactory;
|
||||||
private final ForumPostFactory forumPostFactory;
|
private final ForumPostFactory forumPostFactory;
|
||||||
private final MessageTracker messageTracker;
|
private final MessageTracker messageTracker;
|
||||||
private final List<RemoveForumHook> removeHooks;
|
private final List<RemoveForumHook> removeHooks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ForumManagerImpl(DatabaseComponent db, IdentityManager identityManager,
|
ForumManagerImpl(DatabaseComponent db, ContactManager contactManager,
|
||||||
ClientHelper clientHelper, MetadataParser metadataParser,
|
ClientHelper clientHelper, MetadataParser metadataParser,
|
||||||
ForumFactory forumFactory, ForumPostFactory forumPostFactory,
|
ForumFactory forumFactory, ForumPostFactory forumPostFactory,
|
||||||
MessageTracker messageTracker) {
|
MessageTracker messageTracker) {
|
||||||
super(db, clientHelper, metadataParser);
|
super(db, clientHelper, metadataParser);
|
||||||
this.identityManager = identityManager;
|
this.contactManager = contactManager;
|
||||||
this.forumFactory = forumFactory;
|
this.forumFactory = forumFactory;
|
||||||
this.forumPostFactory = forumPostFactory;
|
this.forumPostFactory = forumPostFactory;
|
||||||
this.messageTracker = messageTracker;
|
this.messageTracker = messageTracker;
|
||||||
@@ -142,8 +142,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
AuthorInfo authorInfo = new AuthorInfo(OURSELVES);
|
||||||
return new ForumPostHeader(p.getMessage().getId(), p.getParent(),
|
return new ForumPostHeader(p.getMessage().getId(), p.getParent(),
|
||||||
p.getMessage().getTimestamp(), p.getAuthor(), OURSELVES, true);
|
p.getMessage().getTimestamp(), p.getAuthor(), authorInfo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -196,7 +197,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
Collection<ForumPostHeader> headers = new ArrayList<>();
|
Collection<ForumPostHeader> headers = new ArrayList<>();
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
// get all authors we need to get the status for
|
// get all authors we need to get the info for
|
||||||
Set<AuthorId> authors = new HashSet<>();
|
Set<AuthorId> authors = new HashSet<>();
|
||||||
for (Entry<MessageId, BdfDictionary> entry :
|
for (Entry<MessageId, BdfDictionary> entry :
|
||||||
metadata.entrySet()) {
|
metadata.entrySet()) {
|
||||||
@@ -204,17 +205,17 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
Author a = clientHelper.parseAndValidateAuthor(authorList);
|
||||||
authors.add(a.getId());
|
authors.add(a.getId());
|
||||||
}
|
}
|
||||||
// get statuses for all authors
|
// get information for all authors
|
||||||
Map<AuthorId, Status> statuses = new HashMap<>();
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
for (AuthorId id : authors) {
|
for (AuthorId id : authors) {
|
||||||
statuses.put(id, identityManager.getAuthorStatus(txn, id));
|
authorInfos.put(id, contactManager.getAuthorInfo(txn, id));
|
||||||
}
|
}
|
||||||
// Parse the metadata
|
// Parse the metadata
|
||||||
for (Entry<MessageId, BdfDictionary> entry :
|
for (Entry<MessageId, BdfDictionary> entry :
|
||||||
metadata.entrySet()) {
|
metadata.entrySet()) {
|
||||||
BdfDictionary meta = entry.getValue();
|
BdfDictionary meta = entry.getValue();
|
||||||
headers.add(getForumPostHeader(txn, entry.getKey(), meta,
|
headers.add(getForumPostHeader(txn, entry.getKey(), meta,
|
||||||
statuses));
|
authorInfos));
|
||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
});
|
});
|
||||||
@@ -252,7 +253,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ForumPostHeader getForumPostHeader(Transaction txn, MessageId id,
|
private ForumPostHeader getForumPostHeader(Transaction txn, MessageId id,
|
||||||
BdfDictionary meta, Map<AuthorId, Status> statuses)
|
BdfDictionary meta, Map<AuthorId, AuthorInfo> authorInfos)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
|
|
||||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||||
@@ -261,12 +262,12 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
|
|||||||
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
parentId = new MessageId(meta.getRaw(KEY_PARENT));
|
||||||
BdfList authorList = meta.getList(KEY_AUTHOR);
|
BdfList authorList = meta.getList(KEY_AUTHOR);
|
||||||
Author author = clientHelper.parseAndValidateAuthor(authorList);
|
Author author = clientHelper.parseAndValidateAuthor(authorList);
|
||||||
Status status = statuses.get(author.getId());
|
AuthorInfo authorInfo = authorInfos.get(author.getId());
|
||||||
if (status == null)
|
if (authorInfo == null)
|
||||||
status = identityManager.getAuthorStatus(txn, author.getId());
|
authorInfo = contactManager.getAuthorInfo(txn, author.getId());
|
||||||
boolean read = meta.getBoolean(MSG_KEY_READ);
|
boolean read = meta.getBoolean(MSG_KEY_READ);
|
||||||
|
|
||||||
return new ForumPostHeader(id, parentId, timestamp, author, status,
|
return new ForumPostHeader(id, parentId, timestamp, author, authorInfo,
|
||||||
read);
|
read);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
@@ -149,11 +150,13 @@ abstract class AbstractProtocolEngine<S extends Session>
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
|
AuthorId localAuthorId = identityManager.getLocalAuthor(txn).getId();
|
||||||
Contact c = contactManager.getContact(txn, sender, localAuthorId);
|
Contact c = contactManager.getContact(txn, sender, localAuthorId);
|
||||||
|
AuthorInfo otherAuthorInfo =
|
||||||
|
contactManager.getAuthorInfo(txn, otherAuthor.getId());
|
||||||
IntroductionResponse response =
|
IntroductionResponse response =
|
||||||
new IntroductionResponse(m.getMessageId(), m.getGroupId(),
|
new IntroductionResponse(m.getMessageId(), m.getGroupId(),
|
||||||
m.getTimestamp(), false, false, false, false,
|
m.getTimestamp(), false, false, false, false,
|
||||||
s.getSessionId(), m instanceof AcceptMessage,
|
s.getSessionId(), m instanceof AcceptMessage,
|
||||||
otherAuthor, s.getRole());
|
otherAuthor, otherAuthorInfo, s.getRole());
|
||||||
IntroductionResponseReceivedEvent e =
|
IntroductionResponseReceivedEvent e =
|
||||||
new IntroductionResponseReceivedEvent(response, c.getId());
|
new IntroductionResponseReceivedEvent(response, c.getId());
|
||||||
txn.attach(e);
|
txn.attach(e);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -251,12 +252,12 @@ class IntroduceeProtocolEngine
|
|||||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
||||||
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
|
Contact c = contactManager.getContact(txn, s.getIntroducer().getId(),
|
||||||
localAuthor.getId());
|
localAuthor.getId());
|
||||||
boolean contactExists = contactManager
|
AuthorInfo authorInfo =
|
||||||
.contactExists(txn, m.getAuthor().getId(), localAuthor.getId());
|
contactManager.getAuthorInfo(txn, m.getAuthor().getId());
|
||||||
IntroductionRequest request = new IntroductionRequest(m.getMessageId(),
|
IntroductionRequest request = new IntroductionRequest(m.getMessageId(),
|
||||||
m.getGroupId(), m.getTimestamp(), false, false, false, false,
|
m.getGroupId(), m.getTimestamp(), false, false, false, false,
|
||||||
s.getSessionId(), m.getAuthor(), m.getText(), false,
|
s.getSessionId(), m.getAuthor(), m.getText(), false,
|
||||||
contactExists);
|
authorInfo);
|
||||||
IntroductionRequestReceivedEvent e =
|
IntroductionRequestReceivedEvent e =
|
||||||
new IntroductionRequestReceivedEvent(request, c.getId());
|
new IntroductionRequestReceivedEvent(request, c.getId());
|
||||||
txn.attach(e);
|
txn.attach(e);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -39,6 +41,7 @@ import org.briarproject.briar.introduction.IntroducerSession.Introducee;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
@@ -408,6 +411,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
||||||
List<PrivateMessageHeader> messages =
|
List<PrivateMessageHeader> messages =
|
||||||
new ArrayList<>(results.size());
|
new ArrayList<>(results.size());
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
for (Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> e : results.entrySet()) {
|
||||||
MessageId m = e.getKey();
|
MessageId m = e.getKey();
|
||||||
MessageMetadata meta =
|
MessageMetadata meta =
|
||||||
@@ -419,15 +423,17 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
if (type == REQUEST) {
|
if (type == REQUEST) {
|
||||||
messages.add(
|
messages.add(
|
||||||
parseInvitationRequest(txn, contactGroupId, m,
|
parseInvitationRequest(txn, contactGroupId, m,
|
||||||
meta, status, ss.bdfSession));
|
meta, status, ss.bdfSession, authorInfos));
|
||||||
} else if (type == ACCEPT) {
|
} else if (type == ACCEPT) {
|
||||||
messages.add(
|
messages.add(
|
||||||
parseInvitationResponse(contactGroupId, m, meta,
|
parseInvitationResponse(txn, contactGroupId, m,
|
||||||
status, ss.bdfSession, true));
|
meta, status, ss.bdfSession, authorInfos,
|
||||||
|
true));
|
||||||
} else if (type == DECLINE) {
|
} else if (type == DECLINE) {
|
||||||
messages.add(
|
messages.add(
|
||||||
parseInvitationResponse(contactGroupId, m, meta,
|
parseInvitationResponse(txn, contactGroupId, m,
|
||||||
status, ss.bdfSession, false));
|
meta, status, ss.bdfSession, authorInfos,
|
||||||
|
false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return messages;
|
return messages;
|
||||||
@@ -438,7 +444,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
|
|
||||||
private IntroductionRequest parseInvitationRequest(Transaction txn,
|
private IntroductionRequest parseInvitationRequest(Transaction txn,
|
||||||
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||||
MessageStatus status, BdfDictionary bdfSession)
|
MessageStatus status, BdfDictionary bdfSession,
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
Role role = sessionParser.getRole(bdfSession);
|
Role role = sessionParser.getRole(bdfSession);
|
||||||
SessionId sessionId;
|
SessionId sessionId;
|
||||||
@@ -462,19 +469,22 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
BdfList body = clientHelper.toList(msg);
|
BdfList body = clientHelper.toList(msg);
|
||||||
RequestMessage rm = messageParser.parseRequestMessage(msg, body);
|
RequestMessage rm = messageParser.parseRequestMessage(msg, body);
|
||||||
String text = rm.getText();
|
String text = rm.getText();
|
||||||
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
|
AuthorInfo authorInfo = authorInfos.get(author.getId());
|
||||||
boolean contactExists = contactManager
|
if (authorInfo == null) {
|
||||||
.contactExists(txn, rm.getAuthor().getId(),
|
authorInfo = contactManager.getAuthorInfo(txn, author.getId());
|
||||||
localAuthor.getId());
|
authorInfos.put(author.getId(), authorInfo);
|
||||||
|
}
|
||||||
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
|
return new IntroductionRequest(m, contactGroupId, meta.getTimestamp(),
|
||||||
meta.isLocal(), status.isSent(), status.isSeen(), meta.isRead(),
|
meta.isLocal(), status.isSent(), status.isSeen(), meta.isRead(),
|
||||||
sessionId, author, text, !meta.isAvailableToAnswer(),
|
sessionId, author, text, !meta.isAvailableToAnswer(),
|
||||||
contactExists);
|
authorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntroductionResponse parseInvitationResponse(GroupId contactGroupId,
|
private IntroductionResponse parseInvitationResponse(Transaction txn,
|
||||||
MessageId m, MessageMetadata meta, MessageStatus status,
|
GroupId contactGroupId, MessageId m, MessageMetadata meta,
|
||||||
BdfDictionary bdfSession, boolean accept) throws FormatException {
|
MessageStatus status, BdfDictionary bdfSession,
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos, boolean accept)
|
||||||
|
throws FormatException, DbException {
|
||||||
Role role = sessionParser.getRole(bdfSession);
|
Role role = sessionParser.getRole(bdfSession);
|
||||||
SessionId sessionId;
|
SessionId sessionId;
|
||||||
Author author;
|
Author author;
|
||||||
@@ -493,9 +503,14 @@ class IntroductionManagerImpl extends ConversationClientImpl
|
|||||||
sessionId = session.getSessionId();
|
sessionId = session.getSessionId();
|
||||||
author = session.getRemote().author;
|
author = session.getRemote().author;
|
||||||
} else throw new AssertionError();
|
} else throw new AssertionError();
|
||||||
|
AuthorInfo authorInfo = authorInfos.get(author.getId());
|
||||||
|
if (authorInfo == null) {
|
||||||
|
authorInfo = contactManager.getAuthorInfo(txn, author.getId());
|
||||||
|
authorInfos.put(author.getId(), authorInfo);
|
||||||
|
}
|
||||||
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
|
return new IntroductionResponse(m, contactGroupId, meta.getTimestamp(),
|
||||||
meta.isLocal(), status.isSent(), status.isSeen(), meta.isRead(),
|
meta.isLocal(), status.isSent(), status.isSeen(), meta.isRead(),
|
||||||
sessionId, accept, author, role);
|
sessionId, accept, author, authorInfo, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSessionWithIntroducer(Transaction txn,
|
private void removeSessionWithIntroducer(Transaction txn,
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.Author.Status;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -53,9 +54,9 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
||||||
import static org.briarproject.briar.api.privategroup.MessageType.POST;
|
import static org.briarproject.briar.api.privategroup.MessageType.POST;
|
||||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||||
@@ -231,9 +232,10 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
|
AuthorInfo authorInfo = new AuthorInfo(OURSELVES);
|
||||||
return new GroupMessageHeader(m.getMessage().getGroupId(),
|
return new GroupMessageHeader(m.getMessage().getGroupId(),
|
||||||
m.getMessage().getId(), m.getParent(),
|
m.getMessage().getId(), m.getParent(),
|
||||||
m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true);
|
m.getMessage().getTimestamp(), m.getMember(), authorInfo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMessageMetadata(BdfDictionary meta, GroupMessage m) {
|
private void addMessageMetadata(BdfDictionary meta, GroupMessage m) {
|
||||||
@@ -321,15 +323,15 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
try {
|
try {
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
// get all authors we need to get the status for
|
// get all authors we need to get the information for
|
||||||
Set<AuthorId> authors = new HashSet<>();
|
Set<AuthorId> authors = new HashSet<>();
|
||||||
for (BdfDictionary meta : metadata.values()) {
|
for (BdfDictionary meta : metadata.values()) {
|
||||||
authors.add(getAuthor(meta).getId());
|
authors.add(getAuthor(meta).getId());
|
||||||
}
|
}
|
||||||
// get statuses for all authors
|
// get information for all authors
|
||||||
Map<AuthorId, Status> statuses = new HashMap<>();
|
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
|
||||||
for (AuthorId id : authors) {
|
for (AuthorId id : authors) {
|
||||||
statuses.put(id, identityManager.getAuthorStatus(txn, id));
|
authorInfos.put(id, contactManager.getAuthorInfo(txn, id));
|
||||||
}
|
}
|
||||||
// get current visibilities for join messages
|
// get current visibilities for join messages
|
||||||
Map<Author, Visibility> visibilities = getMembers(txn, g);
|
Map<Author, Visibility> visibilities = getMembers(txn, g);
|
||||||
@@ -340,10 +342,10 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
Author member = getAuthor(meta);
|
Author member = getAuthor(meta);
|
||||||
Visibility v = visibilities.get(member);
|
Visibility v = visibilities.get(member);
|
||||||
headers.add(getJoinMessageHeader(txn, g, entry.getKey(),
|
headers.add(getJoinMessageHeader(txn, g, entry.getKey(),
|
||||||
meta, statuses, v));
|
meta, authorInfos, v));
|
||||||
} else {
|
} else {
|
||||||
headers.add(getGroupMessageHeader(txn, g, entry.getKey(),
|
headers.add(getGroupMessageHeader(txn, g, entry.getKey(),
|
||||||
meta, statuses));
|
meta, authorInfos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -356,7 +358,8 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g,
|
private GroupMessageHeader getGroupMessageHeader(Transaction txn, GroupId g,
|
||||||
MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses)
|
MessageId id, BdfDictionary meta,
|
||||||
|
Map<AuthorId, AuthorInfo> authorInfos)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
|
|
||||||
MessageId parentId = null;
|
MessageId parentId = null;
|
||||||
@@ -366,24 +369,25 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
long timestamp = meta.getLong(KEY_TIMESTAMP);
|
||||||
|
|
||||||
Author member = getAuthor(meta);
|
Author member = getAuthor(meta);
|
||||||
Status status;
|
AuthorInfo authorInfo;
|
||||||
if (statuses.containsKey(member.getId())) {
|
if (authorInfos.containsKey(member.getId())) {
|
||||||
status = statuses.get(member.getId());
|
authorInfo = authorInfos.get(member.getId());
|
||||||
} else {
|
} else {
|
||||||
status = identityManager.getAuthorStatus(txn, member.getId());
|
authorInfo = contactManager.getAuthorInfo(txn, member.getId());
|
||||||
}
|
}
|
||||||
boolean read = meta.getBoolean(KEY_READ);
|
boolean read = meta.getBoolean(KEY_READ);
|
||||||
|
|
||||||
return new GroupMessageHeader(g, id, parentId, timestamp, member,
|
return new GroupMessageHeader(g, id, parentId, timestamp, member,
|
||||||
status, read);
|
authorInfo, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g,
|
private JoinMessageHeader getJoinMessageHeader(Transaction txn, GroupId g,
|
||||||
MessageId id, BdfDictionary meta, Map<AuthorId, Status> statuses,
|
MessageId id, BdfDictionary meta,
|
||||||
Visibility v) throws DbException, FormatException {
|
Map<AuthorId, AuthorInfo> authorInfos, Visibility v)
|
||||||
|
throws DbException, FormatException {
|
||||||
|
|
||||||
GroupMessageHeader header =
|
GroupMessageHeader header =
|
||||||
getGroupMessageHeader(txn, g, id, meta, statuses);
|
getGroupMessageHeader(txn, g, id, meta, authorInfos);
|
||||||
boolean creator = meta.getBoolean(KEY_INITIAL_JOIN_MSG);
|
boolean creator = meta.getBoolean(KEY_INITIAL_JOIN_MSG);
|
||||||
return new JoinMessageHeader(header, v, creator);
|
return new JoinMessageHeader(header, v, creator);
|
||||||
}
|
}
|
||||||
@@ -398,7 +402,8 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
PrivateGroup privateGroup = getPrivateGroup(txn, g);
|
||||||
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
for (Entry<Author, Visibility> m : authors.entrySet()) {
|
||||||
Author a = m.getKey();
|
Author a = m.getKey();
|
||||||
Status status = identityManager.getAuthorStatus(txn, a.getId());
|
AuthorInfo authorInfo = contactManager.getAuthorInfo(txn, a.getId());
|
||||||
|
Status status = authorInfo.getStatus();
|
||||||
Visibility v = m.getValue();
|
Visibility v = m.getValue();
|
||||||
ContactId c = null;
|
ContactId c = null;
|
||||||
if (v != INVISIBLE &&
|
if (v != INVISIBLE &&
|
||||||
@@ -407,7 +412,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
|
|||||||
.getId();
|
.getId();
|
||||||
}
|
}
|
||||||
boolean isCreator = privateGroup.getCreator().equals(a);
|
boolean isCreator = privateGroup.getCreator().equals(a);
|
||||||
members.add(new GroupMember(a, status, isCreator, c, v));
|
members.add(new GroupMember(a, authorInfo, isCreator, c, v));
|
||||||
}
|
}
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
return members;
|
return members;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.client.ClientHelper;
|
import org.briarproject.bramble.api.client.ClientHelper;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
@@ -12,6 +13,7 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorInfo;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
@@ -29,9 +31,9 @@ import org.jmock.Expectations;
|
|||||||
import org.jmock.Mockery;
|
import org.jmock.Mockery;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
import static org.briarproject.bramble.test.TestUtils.getGroup;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
@@ -64,6 +66,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
private final Mockery context = new Mockery();
|
private final Mockery context = new Mockery();
|
||||||
private final BlogManagerImpl blogManager;
|
private final BlogManagerImpl blogManager;
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
private final ContactManager contactManager =
|
||||||
|
context.mock(ContactManager.class);
|
||||||
private final IdentityManager identityManager =
|
private final IdentityManager identityManager =
|
||||||
context.mock(IdentityManager.class);
|
context.mock(IdentityManager.class);
|
||||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||||
@@ -72,6 +76,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
context.mock(BlogPostFactory.class);
|
context.mock(BlogPostFactory.class);
|
||||||
|
|
||||||
private final LocalAuthor localAuthor1, localAuthor2, rssLocalAuthor;
|
private final LocalAuthor localAuthor1, localAuthor2, rssLocalAuthor;
|
||||||
|
private final AuthorInfo ourselvesInfo = new AuthorInfo(OURSELVES);
|
||||||
|
private final AuthorInfo verifiedInfo = new AuthorInfo(VERIFIED);
|
||||||
private final BdfList authorList1, authorList2, rssAuthorList;
|
private final BdfList authorList1, authorList2, rssAuthorList;
|
||||||
private final Blog blog1, blog2, rssBlog;
|
private final Blog blog1, blog2, rssBlog;
|
||||||
private final Message message, rssMessage;
|
private final Message message, rssMessage;
|
||||||
@@ -81,7 +87,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
|
|
||||||
public BlogManagerImplTest() {
|
public BlogManagerImplTest() {
|
||||||
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
||||||
blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
|
blogManager = new BlogManagerImpl(db, contactManager, identityManager, clientHelper,
|
||||||
metadataParser, blogFactory, blogPostFactory);
|
metadataParser, blogFactory, blogPostFactory);
|
||||||
|
|
||||||
localAuthor1 = getLocalAuthor();
|
localAuthor1 = getLocalAuthor();
|
||||||
@@ -124,7 +130,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
|
|
||||||
ContactId contactId = new ContactId(0);
|
ContactId contactId = new ContactId(0);
|
||||||
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
||||||
blog1.getAuthor().getId(), true, true);
|
blog1.getAuthor().getId(), getRandomString(5), true, true);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
||||||
@@ -146,7 +152,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
|
|
||||||
ContactId contactId = new ContactId(0);
|
ContactId contactId = new ContactId(0);
|
||||||
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
||||||
blog1.getAuthor().getId(), true, true);
|
blog1.getAuthor().getId(), getRandomString(5), true, true);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
||||||
@@ -175,8 +181,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(VERIFIED));
|
will(returnValue(verifiedInfo));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
blogManager.incomingMessage(txn, message, body, meta);
|
blogManager.incomingMessage(txn, message, body, meta);
|
||||||
@@ -281,8 +287,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
|
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
@@ -396,21 +402,21 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
// Create the headers for the comment and its parent
|
// Create the headers for the comment and its parent
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, messageId);
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, messageId);
|
||||||
will(returnValue(postMeta));
|
will(returnValue(postMeta));
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(),
|
BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(),
|
||||||
messageId, null, timestamp, timeReceived, localAuthor1,
|
messageId, null, timestamp, timeReceived, localAuthor1,
|
||||||
OURSELVES, false, true);
|
ourselvesInfo, false, true);
|
||||||
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
|
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
|
||||||
postHeader);
|
postHeader);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -504,22 +510,22 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
// Create the headers for the comment and the wrapped post
|
// Create the headers for the comment and the wrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
||||||
will(returnValue(localAuthor2));
|
will(returnValue(localAuthor2));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor2.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
wrappedPostId);
|
wrappedPostId);
|
||||||
will(returnValue(wrappedPostMeta));
|
will(returnValue(wrappedPostMeta));
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(VERIFIED));
|
will(returnValue(verifiedInfo));
|
||||||
oneOf(db).commitTransaction(txn);
|
oneOf(db).commitTransaction(txn);
|
||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
||||||
blog1.getId(), messageId, null, timestamp, timeReceived,
|
blog1.getId(), messageId, null, timestamp, timeReceived,
|
||||||
localAuthor1, VERIFIED, false, true);
|
localAuthor1, verifiedInfo, false, true);
|
||||||
blogManager.addLocalComment(localAuthor2, blog2.getId(), comment,
|
blogManager.addLocalComment(localAuthor2, blog2.getId(), comment,
|
||||||
originalPostHeader);
|
originalPostHeader);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -613,8 +619,8 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
// Create the headers for the comment and the wrapped post
|
// Create the headers for the comment and the wrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
wrappedPostId);
|
wrappedPostId);
|
||||||
will(returnValue(wrappedPostMeta));
|
will(returnValue(wrappedPostMeta));
|
||||||
@@ -626,7 +632,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
|
|
||||||
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
|
||||||
rssBlog.getId(), rssMessageId, null, timestamp, timeReceived,
|
rssBlog.getId(), rssMessageId, null, timestamp, timeReceived,
|
||||||
rssLocalAuthor, NONE, true, true);
|
rssLocalAuthor, new AuthorInfo(NONE), true, true);
|
||||||
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
|
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
|
||||||
originalPostHeader);
|
originalPostHeader);
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -752,15 +758,15 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
// the rewrapped post
|
// the rewrapped post
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList2);
|
||||||
will(returnValue(localAuthor2));
|
will(returnValue(localAuthor2));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor2.getId());
|
||||||
will(returnValue(OURSELVES));
|
will(returnValue(ourselvesInfo));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
wrappedCommentId);
|
wrappedCommentId);
|
||||||
will(returnValue(wrappedCommentMeta));
|
will(returnValue(wrappedCommentMeta));
|
||||||
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
oneOf(clientHelper).parseAndValidateAuthor(authorList1);
|
||||||
will(returnValue(localAuthor1));
|
will(returnValue(localAuthor1));
|
||||||
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
|
oneOf(contactManager).getAuthorInfo(txn, localAuthor1.getId());
|
||||||
will(returnValue(VERIFIED));
|
will(returnValue(verifiedInfo));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
rewrappedPostId);
|
rewrappedPostId);
|
||||||
will(returnValue(rewrappedPostMeta));
|
will(returnValue(rewrappedPostMeta));
|
||||||
@@ -772,10 +778,10 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
|
|
||||||
BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,
|
BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,
|
||||||
blog1.getId(), wrappedPostId, null, timestamp, timeReceived,
|
blog1.getId(), wrappedPostId, null, timestamp, timeReceived,
|
||||||
rssLocalAuthor, NONE, true, true);
|
rssLocalAuthor, new AuthorInfo(NONE), true, true);
|
||||||
BlogCommentHeader originalCommentHeader = new BlogCommentHeader(COMMENT,
|
BlogCommentHeader originalCommentHeader = new BlogCommentHeader(COMMENT,
|
||||||
blog1.getId(), comment, wrappedPostHeader, originalCommentId,
|
blog1.getId(), comment, wrappedPostHeader, originalCommentId,
|
||||||
timestamp, timeReceived, localAuthor1, VERIFIED, true);
|
timestamp, timeReceived, localAuthor1, verifiedInfo, true);
|
||||||
|
|
||||||
blogManager.addLocalComment(localAuthor2, blog2.getId(), localComment,
|
blogManager.addLocalComment(localAuthor2, blog2.getId(), localComment,
|
||||||
originalCommentHeader);
|
originalCommentHeader);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.blog;
|
package org.briarproject.briar.blog;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.test.TestDatabaseModule;
|
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||||
@@ -23,6 +22,7 @@ import java.util.Iterator;
|
|||||||
import static junit.framework.Assert.assertNotNull;
|
import static junit.framework.Assert.assertNotNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.NONE;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
||||||
@@ -424,7 +424,7 @@ public class BlogManagerIntegrationTest
|
|||||||
assertEquals(1, headers.size());
|
assertEquals(1, headers.size());
|
||||||
BlogPostHeader header = headers.iterator().next();
|
BlogPostHeader header = headers.iterator().next();
|
||||||
assertEquals(POST, header.getType());
|
assertEquals(POST, header.getType());
|
||||||
assertEquals(Author.Status.NONE, header.getAuthorStatus());
|
assertEquals(NONE, header.getAuthorStatus());
|
||||||
assertTrue(header.isRssFeed());
|
assertTrue(header.isRssFeed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_CONTACT;
|
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_CONTACT;
|
||||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_US;
|
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_US;
|
||||||
@@ -92,7 +92,8 @@ public class PrivateGroupIntegrationTest
|
|||||||
Collection<GroupMember> members = groupManager0.getMembers(groupId0);
|
Collection<GroupMember> members = groupManager0.getMembers(groupId0);
|
||||||
assertEquals(1, members.size());
|
assertEquals(1, members.size());
|
||||||
assertEquals(author0, members.iterator().next().getAuthor());
|
assertEquals(author0, members.iterator().next().getAuthor());
|
||||||
assertEquals(OURSELVES, members.iterator().next().getStatus());
|
assertEquals(OURSELVES,
|
||||||
|
members.iterator().next().getAuthorInfo().getStatus());
|
||||||
|
|
||||||
sync0To1(1, true);
|
sync0To1(1, true);
|
||||||
groupInvitationManager1
|
groupInvitationManager1
|
||||||
@@ -107,7 +108,7 @@ public class PrivateGroupIntegrationTest
|
|||||||
members = groupManager0.getMembers(groupId0);
|
members = groupManager0.getMembers(groupId0);
|
||||||
assertEquals(2, members.size());
|
assertEquals(2, members.size());
|
||||||
for (GroupMember m : members) {
|
for (GroupMember m : members) {
|
||||||
if (m.getStatus() == OURSELVES) {
|
if (m.getAuthorInfo().getStatus() == OURSELVES) {
|
||||||
assertEquals(author0.getId(), m.getAuthor().getId());
|
assertEquals(author0.getId(), m.getAuthor().getId());
|
||||||
} else {
|
} else {
|
||||||
assertEquals(author1.getId(), m.getAuthor().getId());
|
assertEquals(author1.getId(), m.getAuthor().getId());
|
||||||
@@ -117,7 +118,7 @@ public class PrivateGroupIntegrationTest
|
|||||||
members = groupManager1.getMembers(groupId0);
|
members = groupManager1.getMembers(groupId0);
|
||||||
assertEquals(2, members.size());
|
assertEquals(2, members.size());
|
||||||
for (GroupMember m : members) {
|
for (GroupMember m : members) {
|
||||||
if (m.getStatus() == OURSELVES) {
|
if (m.getAuthorInfo().getStatus() == OURSELVES) {
|
||||||
assertEquals(author1.getId(), m.getAuthor().getId());
|
assertEquals(author1.getId(), m.getAuthor().getId());
|
||||||
} else {
|
} else {
|
||||||
assertEquals(author0.getId(), m.getAuthor().getId());
|
assertEquals(author0.getId(), m.getAuthor().getId());
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
|
|||||||
BdfDictionary.of(new BdfEntry("me", "ta"));
|
BdfDictionary.of(new BdfEntry("me", "ta"));
|
||||||
final ContactId contactId = new ContactId(5);
|
final ContactId contactId = new ContactId(5);
|
||||||
final Contact contact = new Contact(contactId, author,
|
final Contact contact = new Contact(contactId, author,
|
||||||
new AuthorId(getRandomId()), true, true);
|
new AuthorId(getRandomId()), getRandomString(5), true, true);
|
||||||
|
|
||||||
final InviteMessage inviteMessage =
|
final InviteMessage inviteMessage =
|
||||||
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
|
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final ContactId contactId = new ContactId(0);
|
private final ContactId contactId = new ContactId(0);
|
||||||
private final Author author = getAuthor();
|
private final Author author = getAuthor();
|
||||||
private final Contact contact = new Contact(contactId, author,
|
private final Contact contact = new Contact(contactId, author,
|
||||||
new AuthorId(getRandomId()), true, true);
|
new AuthorId(getRandomId()), getRandomString(5), true, true);
|
||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
private final Group privateGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group privateGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
@@ -845,9 +845,9 @@ public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testRemovingGroupEndsSessions() throws Exception {
|
public void testRemovingGroupEndsSessions() throws Exception {
|
||||||
Contact contact2 = new Contact(new ContactId(2), author,
|
Contact contact2 = new Contact(new ContactId(2), author,
|
||||||
author.getId(), true, true);
|
author.getId(), getRandomString(5), true, true);
|
||||||
Contact contact3 = new Contact(new ContactId(3), author,
|
Contact contact3 = new Contact(new ContactId(3), author,
|
||||||
author.getId(), true, true);
|
author.getId(), getRandomString(5), true, true);
|
||||||
Collection<Contact> contacts =
|
Collection<Contact> contacts =
|
||||||
Arrays.asList(contact, contact2, contact3);
|
Arrays.asList(contact, contact2, contact3);
|
||||||
|
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
|
|||||||
signature);
|
signature);
|
||||||
Author notCreator = getAuthor();
|
Author notCreator = getAuthor();
|
||||||
Contact notCreatorContact = new Contact(contactId, notCreator,
|
Contact notCreatorContact = new Contact(contactId, notCreator,
|
||||||
localAuthor.getId(), true, true);
|
localAuthor.getId(), getRandomString(5), true, true);
|
||||||
|
|
||||||
expectGetContactId();
|
expectGetContactId();
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getGroup;
|
|||||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
|
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
|
||||||
import static org.briarproject.briar.api.blog.BlogSharingManager.MAJOR_VERSION;
|
import static org.briarproject.briar.api.blog.BlogSharingManager.MAJOR_VERSION;
|
||||||
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
|
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
|
||||||
@@ -63,7 +64,8 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final ContactId contactId = new ContactId(0);
|
private final ContactId contactId = new ContactId(0);
|
||||||
private final Author author = getAuthor();
|
private final Author author = getAuthor();
|
||||||
private final Contact contact =
|
private final Contact contact =
|
||||||
new Contact(contactId, author, localAuthor.getId(), true, true);
|
new Contact(contactId, author, localAuthor.getId(),
|
||||||
|
getRandomString(5), true, true);
|
||||||
private final Collection<Contact> contacts =
|
private final Collection<Contact> contacts =
|
||||||
Collections.singletonList(contact);
|
Collections.singletonList(contact);
|
||||||
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION);
|
||||||
|
|||||||
@@ -72,9 +72,16 @@ Returns a JSON array of contacts:
|
|||||||
The only workaround is to add a contact to the Briar app running on a rooted Android phone
|
The only workaround is to add a contact to the Briar app running on a rooted Android phone
|
||||||
and then move its database (and key files) to the headless peer.
|
and then move its database (and key files) to the headless peer.
|
||||||
|
|
||||||
|
### Removing a contact
|
||||||
|
|
||||||
|
`DELETE /v1/contacts/{contactId}`
|
||||||
|
|
||||||
|
The `{contactId}` is the `contactId` of the contact (`1` in the example above).
|
||||||
|
It returns with a status code `200`, if removal was successful.
|
||||||
|
|
||||||
### Listing all private messages
|
### Listing all private messages
|
||||||
|
|
||||||
`GET /messages/{contactId}`
|
`GET /v1/messages/{contactId}`
|
||||||
|
|
||||||
The `{contactId}` is the `contactId` of the contact (`1` in the example above).
|
The `{contactId}` is the `contactId` of the contact (`1` in the example above).
|
||||||
It returns a JSON array of private messages:
|
It returns a JSON array of private messages:
|
||||||
@@ -100,7 +107,7 @@ Attention: There can messages of other `type`s where the message `text` is `null
|
|||||||
|
|
||||||
### Writing a private message
|
### Writing a private message
|
||||||
|
|
||||||
`POST /messages/{contactId}`
|
`POST /v1/messages/{contactId}`
|
||||||
|
|
||||||
The text of the message should be posted as JSON:
|
The text of the message should be posted as JSON:
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'idea'
|
id 'idea'
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.2.70'
|
id 'org.jetbrains.kotlin.jvm' version '1.2.71'
|
||||||
id 'org.jetbrains.kotlin.kapt' version '1.2.70'
|
id 'org.jetbrains.kotlin.kapt' version '1.2.71'
|
||||||
id 'witness'
|
id 'witness'
|
||||||
}
|
}
|
||||||
apply from: 'witness.gradle'
|
apply from: 'witness.gradle'
|
||||||
@@ -14,9 +14,9 @@ dependencies {
|
|||||||
implementation project(path: ':briar-core', configuration: 'default')
|
implementation project(path: ':briar-core', configuration: 'default')
|
||||||
implementation project(path: ':bramble-java', configuration: 'default')
|
implementation project(path: ':bramble-java', configuration: 'default')
|
||||||
|
|
||||||
implementation 'io.javalin:javalin:2.2.0'
|
implementation 'io.javalin:javalin:2.3.0'
|
||||||
implementation 'org.slf4j:slf4j-simple:1.7.25'
|
implementation 'org.slf4j:slf4j-simple:1.7.25'
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.6'
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.7'
|
||||||
implementation 'com.github.ajalt:clikt:1.5.0'
|
implementation 'com.github.ajalt:clikt:1.5.0'
|
||||||
|
|
||||||
kapt 'com.google.dagger:dagger-compiler:2.0.2'
|
kapt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
@@ -24,11 +24,11 @@ dependencies {
|
|||||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
|
||||||
|
|
||||||
def junitVersion = '5.2.0'
|
def junitVersion = '5.3.1'
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
|
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
|
||||||
testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||||
testImplementation "io.mockk:mockk:1.8.6"
|
testImplementation "io.mockk:mockk:1.8.9"
|
||||||
testImplementation "org.skyscreamer:jsonassert:1.5.0"
|
testImplementation "org.skyscreamer:jsonassert:1.5.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user