mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
33 Commits
beta-2017-
...
831_refact
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bb55d2580 | ||
|
|
6de539a62d | ||
|
|
34704ec04d | ||
|
|
9fd6d46583 | ||
|
|
920f3581fa | ||
|
|
45e7af31fe | ||
|
|
67d5d8cdf1 | ||
|
|
9d8cadb7a9 | ||
|
|
6425c49d04 | ||
|
|
68d98b50f2 | ||
|
|
84986d393f | ||
|
|
115d488bc3 | ||
|
|
2eeb2213e3 | ||
|
|
1b48d661e8 | ||
|
|
49ba66dee9 | ||
|
|
46920f3bce | ||
|
|
4b955809f7 | ||
|
|
57d4d6546a | ||
|
|
9bfb58a764 | ||
|
|
0256ec0b8c | ||
|
|
b0b4a85d15 | ||
|
|
d40a058ef5 | ||
|
|
58b9efb24c | ||
|
|
17de785c12 | ||
|
|
c7ff1ba974 | ||
|
|
d17669f131 | ||
|
|
9755cd9ab4 | ||
|
|
6d2b18facc | ||
|
|
f8cf7034db | ||
|
|
a1e65c9fa7 | ||
|
|
499d2fe677 | ||
|
|
85c17b4cb0 | ||
|
|
0827b067ec |
@@ -19,8 +19,7 @@ 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.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.PluginException;
|
import org.briarproject.bramble.api.plugin.PluginException;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
@@ -75,7 +74,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class DroidtoothPlugin implements DuplexPlugin {
|
class DroidtoothPlugin<C, S>
|
||||||
|
extends AbstractBluetoothPlugin<C, S>{
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||||
@@ -84,16 +84,10 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
private static final String DISCOVERY_FINISHED =
|
private static final String DISCOVERY_FINISHED =
|
||||||
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final SecureRandom secureRandom;
|
|
||||||
private final Backoff backoff;
|
|
||||||
private final DuplexPluginCallback callback;
|
|
||||||
private final int maxLatency;
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
private volatile boolean wasEnabledByUs = false;
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
private volatile BluetoothStateReceiver receiver = null;
|
||||||
private volatile BluetoothServerSocket socket = null;
|
private volatile BluetoothServerSocket socket = null;
|
||||||
@@ -104,29 +98,15 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
DuplexPluginCallback callback, int maxLatency) {
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
|
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.secureRandom = secureRandom;
|
|
||||||
this.backoff = backoff;
|
|
||||||
this.callback = callback;
|
|
||||||
this.maxLatency = maxLatency;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TransportId getId() {
|
protected void close(S ss) throws IOException {
|
||||||
return ID;
|
((BluetoothServerSocket)ss).close();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
// Bluetooth detects dead connections so we don't need keepalives
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -194,14 +174,14 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
BluetoothServerSocket ss;
|
BluetoothServerSocket ss;
|
||||||
try {
|
try {
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||||
"RFCOMM", getUuid());
|
"RFCOMM", UUID.fromString(getUuid()));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
LOG.log(WARNING, e.toString(), e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isRunning()) {
|
if (!isRunning()) {
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG.info("Socket bound");
|
LOG.info("Socket bound");
|
||||||
@@ -213,29 +193,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private UUID getUuid() {
|
|
||||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
|
||||||
if (uuid == null) {
|
|
||||||
byte[] random = new byte[UUID_BYTES];
|
|
||||||
secureRandom.nextBytes(random);
|
|
||||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
|
||||||
TransportProperties p = new TransportProperties();
|
|
||||||
p.put(PROP_UUID, uuid);
|
|
||||||
callback.mergeLocalProperties(p);
|
|
||||||
}
|
|
||||||
return UUID.fromString(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable BluetoothServerSocket ss) {
|
|
||||||
try {
|
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptContactConnections() {
|
private void acceptContactConnections() {
|
||||||
while (isRunning()) {
|
while (isRunning()) {
|
||||||
BluetoothSocket s;
|
BluetoothSocket s;
|
||||||
@@ -261,9 +218,9 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
this.running = false;
|
||||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||||
tryToClose(socket);
|
tryToClose((S)socket);
|
||||||
// Disable Bluetooth if we enabled it and it's still enabled
|
// Disable Bluetooth if we enabled it and it's still enabled
|
||||||
if (wasEnabledByUs && adapter.isEnabled()) {
|
if (wasEnabledByUs && adapter.isEnabled()) {
|
||||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||||
@@ -276,42 +233,20 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return running && adapter != null && adapter.isEnabled();
|
return running && adapter != null && adapter.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected Runnable returnPollRunnable(final String address, final String uuid,
|
||||||
public boolean shouldPoll() {
|
final ContactId c) {
|
||||||
return true;
|
return new Runnable() {
|
||||||
}
|
@Override
|
||||||
|
public void run() {
|
||||||
@Override
|
if (!running) return;
|
||||||
public int getPollingInterval() {
|
BluetoothSocket s = connect(address, uuid);
|
||||||
return backoff.getPollingInterval();
|
if (s != null) {
|
||||||
}
|
backoff.reset();
|
||||||
|
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||||
@Override
|
|
||||||
public void poll(Collection<ContactId> connected) {
|
|
||||||
if (!isRunning()) return;
|
|
||||||
backoff.increment();
|
|
||||||
// Try to connect to known devices in parallel
|
|
||||||
Map<ContactId, TransportProperties> remote =
|
|
||||||
callback.getRemoteProperties();
|
|
||||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
|
||||||
final ContactId c = e.getKey();
|
|
||||||
if (connected.contains(c)) continue;
|
|
||||||
final String address = e.getValue().get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
|
||||||
final String uuid = e.getValue().get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
|
||||||
ioExecutor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!running) return;
|
|
||||||
BluetoothSocket s = connect(address, uuid);
|
|
||||||
if (s != null) {
|
|
||||||
backoff.reset();
|
|
||||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -347,37 +282,17 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
||||||
+ ": " + e);
|
+ ": " + e);
|
||||||
}
|
}
|
||||||
tryToClose(s);
|
tryToClose((S)s);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public DuplexTransportConnection connectToAddress(String address, String uuid) {
|
||||||
public DuplexTransportConnection createConnection(ContactId c) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
|
||||||
if (p == null) return null;
|
|
||||||
String address = p.get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
|
||||||
String uuid = p.get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
|
||||||
BluetoothSocket s = connect(address, uuid);
|
BluetoothSocket s = connect(address, uuid);
|
||||||
if (s == null) return null;
|
return s == null ? null : wrapSocket(s);
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
@@ -426,7 +341,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
// Closing the socket will terminate the listener task
|
// Closing the socket will terminate the listener task
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
closeSockets(futures, chosen);
|
closeSockets(futures, chosen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,11 +373,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsKeyAgreement() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
@@ -487,31 +397,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
|
||||||
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
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
|
||||||
BluetoothSocket s = connect(address, uuid.toString());
|
|
||||||
if (s == null) return null;
|
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
|
||||||
byte[] mac = descriptor.getRaw(1);
|
|
||||||
if (mac.length != 6) throw new FormatException();
|
|
||||||
return StringUtils.macToString(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@@ -523,7 +408,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
bind();
|
bind();
|
||||||
} else if (state == STATE_OFF) {
|
} else if (state == STATE_OFF) {
|
||||||
LOG.info("Bluetooth disabled");
|
LOG.info("Bluetooth disabled");
|
||||||
tryToClose(socket);
|
tryToClose((S)socket);
|
||||||
}
|
}
|
||||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||||
if (scanMode == SCAN_MODE_NONE) {
|
if (scanMode == SCAN_MODE_NONE) {
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class Author {
|
public class Author {
|
||||||
|
|
||||||
public enum Status {ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES}
|
public enum Status {
|
||||||
|
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
|
||||||
|
}
|
||||||
|
|
||||||
private final AuthorId id;
|
private final AuthorId id;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public interface TorConstants {
|
|||||||
int CONTROL_PORT = 59051;
|
int CONTROL_PORT = 59051;
|
||||||
|
|
||||||
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
|
||||||
|
int EXTRA_SOCKET_TIMEOUT = 30000; // Milliseconds
|
||||||
|
|
||||||
String PREF_TOR_NETWORK = "network";
|
String PREF_TOR_NETWORK = "network";
|
||||||
String PREF_TOR_PORT = "port";
|
String PREF_TOR_PORT = "port";
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package org.briarproject.bramble.api.plugin.duplex;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Santiago Torres-Arias on 1/10/17.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class AbstractBluetoothPlugin<C, S> implements DuplexPlugin {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger("Halp");
|
||||||
|
|
||||||
|
protected final Executor ioExecutor;
|
||||||
|
protected final SecureRandom secureRandom;
|
||||||
|
protected final Backoff backoff;
|
||||||
|
protected final int maxLatency;
|
||||||
|
protected final DuplexPluginCallback callback;
|
||||||
|
protected final S ss = null;
|
||||||
|
|
||||||
|
protected volatile boolean running = false;
|
||||||
|
|
||||||
|
protected Runnable pollRunnable = null;
|
||||||
|
|
||||||
|
public AbstractBluetoothPlugin(Executor ioExecutor,
|
||||||
|
SecureRandom secureRandom,
|
||||||
|
Backoff backoff, int maxLatency,
|
||||||
|
DuplexPluginCallback callback) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.secureRandom = secureRandom;
|
||||||
|
this.backoff = backoff;
|
||||||
|
this.maxLatency = maxLatency;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsInvitations() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsKeyAgreement() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldPoll() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPollingInterval() {
|
||||||
|
return backoff.getPollingInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getUuid() {
|
||||||
|
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
||||||
|
if (uuid == null) {
|
||||||
|
byte[] random = new byte[UUID_BYTES];
|
||||||
|
secureRandom.nextBytes(random);
|
||||||
|
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||||
|
TransportProperties p = new TransportProperties();
|
||||||
|
p.put(PROP_UUID, uuid);
|
||||||
|
callback.mergeLocalProperties(p);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportId getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxLatency() {
|
||||||
|
return maxLatency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxIdleTime() {
|
||||||
|
// Bluetooth detects dead connections so we don't need keepalives
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
|
byte[] mac = descriptor.getRaw(1);
|
||||||
|
if (mac.length != 6) throw new FormatException();
|
||||||
|
return StringUtils.macToString(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void tryToClose(@Nullable S ss) {
|
||||||
|
try {
|
||||||
|
if (ss != null) close(ss);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
} finally {
|
||||||
|
callback.transportDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void close(S ss) throws IOException;
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
running = false;
|
||||||
|
tryToClose(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void poll(final Collection<ContactId> connected) {
|
||||||
|
if (!running) return;
|
||||||
|
backoff.increment();
|
||||||
|
// Try to connect to known devices in parallel
|
||||||
|
Map<ContactId, TransportProperties> remote =
|
||||||
|
callback.getRemoteProperties();
|
||||||
|
for (Map.Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||||
|
final ContactId c = e.getKey();
|
||||||
|
if (connected.contains(c)) continue;
|
||||||
|
final String address = e.getValue().get(PROP_ADDRESS);
|
||||||
|
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||||
|
final String uuid = e.getValue().get(PROP_UUID);
|
||||||
|
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||||
|
ioExecutor.execute(returnPollRunnable(address,uuid, c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Runnable returnPollRunnable(String address, String uuid,
|
||||||
|
ContactId c);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexTransportConnection createConnection(ContactId c) {
|
||||||
|
if (!isRunning()) return null;
|
||||||
|
TransportProperties p = callback.getRemoteProperties().get(c);
|
||||||
|
if (p == null) return null;
|
||||||
|
String address = p.get(PROP_ADDRESS);
|
||||||
|
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||||
|
String uuid = p.get(PROP_UUID);
|
||||||
|
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||||
|
return connectToAddress(address, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract DuplexTransportConnection connectToAddress(String address, String uuid);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
|
byte[] commitment, BdfList descriptor, long timeout) {
|
||||||
|
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
|
||||||
|
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
|
return connectToAddress(address, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -668,7 +668,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
acked.add(m);
|
acked.add(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transaction.attach(new MessagesAckedEvent(c, acked));
|
if (acked.size() > 0) {
|
||||||
|
transaction.attach(new MessagesAckedEvent(c, acked));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
private static final int SCHEMA_VERSION = 29;
|
private static final int SCHEMA_VERSION = 30;
|
||||||
private static final int MIN_SCHEMA_VERSION = 29;
|
private static final int MIN_SCHEMA_VERSION = 30;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
|
||||||
|
import static org.briarproject.bramble.api.plugin.TorConstants.EXTRA_SOCKET_TIMEOUT;
|
||||||
import static org.briarproject.bramble.api.plugin.TorConstants.SOCKS_PORT;
|
import static org.briarproject.bramble.api.plugin.TorConstants.SOCKS_PORT;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -17,6 +18,7 @@ public class SocksModule {
|
|||||||
SocketFactory provideTorSocketFactory() {
|
SocketFactory provideTorSocketFactory() {
|
||||||
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
|
||||||
SOCKS_PORT);
|
SOCKS_PORT);
|
||||||
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT);
|
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT,
|
||||||
|
EXTRA_SOCKET_TIMEOUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ class SocksSocket extends Socket {
|
|||||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout;
|
private final int connectToProxyTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocket(SocketAddress proxy, int connectToProxyTimeout) {
|
SocksSocket(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
|
int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,7 +49,7 @@ class SocksSocket extends Socket {
|
|||||||
InetAddress address = inet.getAddress();
|
InetAddress address = inet.getAddress();
|
||||||
if (address != null
|
if (address != null
|
||||||
&& !Arrays.equals(address.getAddress(), UNSPECIFIED_ADDRESS)) {
|
&& !Arrays.equals(address.getAddress(), UNSPECIFIED_ADDRESS)) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
String host = inet.getHostName();
|
String host = inet.getHostName();
|
||||||
if (host.length() > 255) throw new IllegalArgumentException();
|
if (host.length() > 255) throw new IllegalArgumentException();
|
||||||
@@ -62,16 +64,16 @@ class SocksSocket extends Socket {
|
|||||||
sendMethodRequest(out);
|
sendMethodRequest(out);
|
||||||
receiveMethodResponse(in);
|
receiveMethodResponse(in);
|
||||||
|
|
||||||
// Use the supplied timeout temporarily
|
// Use the supplied timeout temporarily, plus any configured extra
|
||||||
int oldTimeout = getSoTimeout();
|
int oldTimeout = getSoTimeout();
|
||||||
setSoTimeout(timeout);
|
setSoTimeout(timeout + extraSocketTimeout);
|
||||||
|
|
||||||
// Connect to the endpoint via the proxy
|
// Connect to the endpoint via the proxy
|
||||||
sendConnectRequest(out, host, port);
|
sendConnectRequest(out, host, port);
|
||||||
receiveConnectResponse(in);
|
receiveConnectResponse(in);
|
||||||
|
|
||||||
// Restore the old timeout
|
// Restore the old timeout, plus any configured extra
|
||||||
setSoTimeout(oldTimeout);
|
setSoTimeout(oldTimeout + extraSocketTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMethodRequest(OutputStream out) throws IOException {
|
private void sendMethodRequest(OutputStream out) throws IOException {
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ import javax.net.SocketFactory;
|
|||||||
class SocksSocketFactory extends SocketFactory {
|
class SocksSocketFactory extends SocketFactory {
|
||||||
|
|
||||||
private final SocketAddress proxy;
|
private final SocketAddress proxy;
|
||||||
private final int connectToProxyTimeout;
|
private final int connectToProxyTimeout, extraSocketTimeout;
|
||||||
|
|
||||||
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout) {
|
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout,
|
||||||
|
int extraSocketTimeout) {
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
this.connectToProxyTimeout = connectToProxyTimeout;
|
this.connectToProxyTimeout = connectToProxyTimeout;
|
||||||
|
this.extraSocketTimeout = extraSocketTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Socket createSocket() {
|
public Socket createSocket() {
|
||||||
return new SocksSocket(proxy, connectToProxyTimeout);
|
return new SocksSocket(proxy, connectToProxyTimeout, extraSocketTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class TestSocksModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SocketFactory provideSocketFactory() {
|
||||||
|
return SocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,14 +10,14 @@ 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.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
import org.briarproject.bramble.api.plugin.PluginException;
|
import org.briarproject.bramble.api.plugin.PluginException;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.util.OsUtils;
|
import org.briarproject.bramble.util.OsUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.IOError;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -57,46 +57,22 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
|||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class BluetoothPlugin implements DuplexPlugin {
|
class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final SecureRandom secureRandom;
|
|
||||||
private final Backoff backoff;
|
|
||||||
private final DuplexPluginCallback callback;
|
|
||||||
private final int maxLatency;
|
|
||||||
private final Semaphore discoverySemaphore = new Semaphore(1);
|
private final Semaphore discoverySemaphore = new Semaphore(1);
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
|
||||||
private volatile StreamConnectionNotifier socket = null;
|
private volatile StreamConnectionNotifier socket = null;
|
||||||
private volatile LocalDevice localDevice = null;
|
private volatile LocalDevice localDevice = null;
|
||||||
|
|
||||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
BluetoothPlugin(Executor ioExecutor,
|
||||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
SecureRandom secureRandom,
|
||||||
this.ioExecutor = ioExecutor;
|
Backoff backoff, int maxLatency,
|
||||||
this.secureRandom = secureRandom;
|
DuplexPluginCallback callback) {
|
||||||
this.backoff = backoff;
|
super(ioExecutor, secureRandom, backoff, maxLatency, callback);
|
||||||
this.callback = callback;
|
|
||||||
this.maxLatency = maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportId getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
// Bluetooth detects dead connections so we don't need keepalives
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -139,7 +115,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!running) {
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
socket = ss;
|
socket = ss;
|
||||||
@@ -153,29 +129,16 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
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";
|
||||||
}
|
}
|
||||||
|
//
|
||||||
private String getUuid() {
|
// private void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
||||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
// try {
|
||||||
if (uuid == null) {
|
// if (ss != null) ss.close();
|
||||||
byte[] random = new byte[UUID_BYTES];
|
// } catch (IOException e) {
|
||||||
secureRandom.nextBytes(random);
|
// if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
// } finally {
|
||||||
TransportProperties p = new TransportProperties();
|
// callback.transportDisabled();
|
||||||
p.put(PROP_UUID, uuid);
|
// }
|
||||||
callback.mergeLocalProperties(p);
|
// }
|
||||||
}
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
|
||||||
try {
|
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
} finally {
|
|
||||||
callback.transportDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -196,54 +159,33 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||||
return new BluetoothTransportConnection(this, s);
|
return new BluetoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void stop() {
|
||||||
|
// running = false;
|
||||||
|
// tryToClose(socket);
|
||||||
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
protected void close(S ss) throws IOException {
|
||||||
running = false;
|
((StreamConnection)ss).close();
|
||||||
tryToClose(socket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRunning() {
|
public Runnable returnPollRunnable(final String address, final String uuid,
|
||||||
return running;
|
final ContactId c) {
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return new Runnable() {
|
||||||
public boolean shouldPoll() {
|
@Override
|
||||||
return true;
|
public void run() {
|
||||||
}
|
if (!running) return;
|
||||||
|
StreamConnection s = connect(makeUrl(address, uuid));
|
||||||
@Override
|
if (s != null) {
|
||||||
public int getPollingInterval() {
|
backoff.reset();
|
||||||
return backoff.getPollingInterval();
|
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void poll(final Collection<ContactId> connected) {
|
|
||||||
if (!running) return;
|
|
||||||
backoff.increment();
|
|
||||||
// Try to connect to known devices in parallel
|
|
||||||
Map<ContactId, TransportProperties> remote =
|
|
||||||
callback.getRemoteProperties();
|
|
||||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
|
||||||
final ContactId c = e.getKey();
|
|
||||||
if (connected.contains(c)) continue;
|
|
||||||
final String address = e.getValue().get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
|
||||||
final String uuid = e.getValue().get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
|
||||||
ioExecutor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!running) return;
|
|
||||||
StreamConnection s = connect(makeUrl(address, uuid));
|
|
||||||
if (s != null) {
|
|
||||||
backoff.reset();
|
|
||||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamConnection connect(String url) {
|
private StreamConnection connect(String url) {
|
||||||
@@ -259,25 +201,13 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createConnection(ContactId c) {
|
protected DuplexTransportConnection connectToAddress(String address, String uuid) {
|
||||||
if (!running) return null;
|
String url = makeUrl(address, uuid);
|
||||||
TransportProperties p = callback.getRemoteProperties().get(c);
|
|
||||||
if (p == null) return null;
|
|
||||||
String address = p.get(PROP_ADDRESS);
|
|
||||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
|
||||||
String uuid = p.get(PROP_UUID);
|
|
||||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
|
||||||
String url = makeUrl(address, uuid);
|
|
||||||
StreamConnection s = connect(url);
|
StreamConnection s = connect(url);
|
||||||
if (s == null) return null;
|
if (s == null) return null;
|
||||||
return new BluetoothTransportConnection(this, s);
|
return new BluetoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsInvitations() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout, boolean alice) {
|
long timeout, boolean alice) {
|
||||||
@@ -297,7 +227,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!running) {
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Create the background tasks
|
// Create the background tasks
|
||||||
@@ -330,7 +260,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
// Closing the socket will terminate the listener task
|
// Closing the socket will terminate the listener task
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
closeSockets(futures, chosen);
|
closeSockets(futures, chosen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,11 +292,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsKeyAgreement() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
if (!running) return null;
|
if (!running) return null;
|
||||||
@@ -385,7 +310,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!running) {
|
if (!running) {
|
||||||
tryToClose(ss);
|
tryToClose((S)ss);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
@@ -395,33 +320,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
|
||||||
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
|
|
||||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
|
||||||
String url = makeUrl(address, uuid);
|
|
||||||
StreamConnection s = connect(url);
|
|
||||||
if (s == null) return null;
|
|
||||||
return new BluetoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
|
||||||
byte[] mac = descriptor.getRaw(1);
|
|
||||||
if (mac.length != 6) throw new FormatException();
|
|
||||||
return StringUtils.macToString(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeDeviceDiscoverable() {
|
private void makeDeviceDiscoverable() {
|
||||||
// Try to make the device discoverable (requires root on Linux)
|
// Try to make the device discoverable (requires root on Linux)
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import static android.app.Notification.DEFAULT_SOUND;
|
|||||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
|
||||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||||
@@ -310,7 +309,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
ContactId c = contactCounts.keySet().iterator().next();
|
ContactId c = contactCounts.keySet().iterator().next();
|
||||||
i.putExtra(CONTACT_ID, c.getInt());
|
i.putExtra(CONTACT_ID, c.getInt());
|
||||||
i.setData(Uri.parse(CONTACT_URI + "/" + c.getInt()));
|
i.setData(Uri.parse(CONTACT_URI + "/" + c.getInt()));
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(ConversationActivity.class);
|
t.addParentStack(ConversationActivity.class);
|
||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
@@ -319,7 +318,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
// Touching the notification shows the contact list
|
// Touching the notification shows the contact list
|
||||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||||
i.putExtra(INTENT_CONTACTS, true);
|
i.putExtra(INTENT_CONTACTS, true);
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.setData(Uri.parse(CONTACT_URI));
|
i.setData(Uri.parse(CONTACT_URI));
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(NavDrawerActivity.class);
|
t.addParentStack(NavDrawerActivity.class);
|
||||||
@@ -415,7 +414,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
i.putExtra(GROUP_ID, g.getBytes());
|
i.putExtra(GROUP_ID, g.getBytes());
|
||||||
String idHex = StringUtils.toHexString(g.getBytes());
|
String idHex = StringUtils.toHexString(g.getBytes());
|
||||||
i.setData(Uri.parse(GROUP_URI + "/" + idHex));
|
i.setData(Uri.parse(GROUP_URI + "/" + idHex));
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(GroupActivity.class);
|
t.addParentStack(GroupActivity.class);
|
||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
@@ -424,7 +423,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
// Touching the notification shows the group list
|
// Touching the notification shows the group list
|
||||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||||
i.putExtra(INTENT_GROUPS, true);
|
i.putExtra(INTENT_GROUPS, true);
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.setData(Uri.parse(GROUP_URI));
|
i.setData(Uri.parse(GROUP_URI));
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(NavDrawerActivity.class);
|
t.addParentStack(NavDrawerActivity.class);
|
||||||
@@ -507,7 +506,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
i.putExtra(GROUP_ID, g.getBytes());
|
i.putExtra(GROUP_ID, g.getBytes());
|
||||||
String idHex = StringUtils.toHexString(g.getBytes());
|
String idHex = StringUtils.toHexString(g.getBytes());
|
||||||
i.setData(Uri.parse(FORUM_URI + "/" + idHex));
|
i.setData(Uri.parse(FORUM_URI + "/" + idHex));
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(ForumActivity.class);
|
t.addParentStack(ForumActivity.class);
|
||||||
t.addNextIntent(i);
|
t.addNextIntent(i);
|
||||||
@@ -516,7 +515,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
// Touching the notification shows the forum list
|
// Touching the notification shows the forum list
|
||||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||||
i.putExtra(INTENT_FORUMS, true);
|
i.putExtra(INTENT_FORUMS, true);
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.setData(Uri.parse(FORUM_URI));
|
i.setData(Uri.parse(FORUM_URI));
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(NavDrawerActivity.class);
|
t.addParentStack(NavDrawerActivity.class);
|
||||||
@@ -595,7 +594,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
// Touching the notification shows the combined blog feed
|
// Touching the notification shows the combined blog feed
|
||||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||||
i.putExtra(INTENT_BLOGS, true);
|
i.putExtra(INTENT_BLOGS, true);
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.setData(Uri.parse(BLOG_URI));
|
i.setData(Uri.parse(BLOG_URI));
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(NavDrawerActivity.class);
|
t.addParentStack(NavDrawerActivity.class);
|
||||||
@@ -651,7 +650,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
|||||||
// Touching the notification shows the contact list
|
// Touching the notification shows the contact list
|
||||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||||
i.putExtra(INTENT_CONTACTS, true);
|
i.putExtra(INTENT_CONTACTS, true);
|
||||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i.setData(Uri.parse(CONTACT_URI));
|
i.setData(Uri.parse(CONTACT_URI));
|
||||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||||
t.addParentStack(NavDrawerActivity.class);
|
t.addParentStack(NavDrawerActivity.class);
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import javax.inject.Inject;
|
|||||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
|
||||||
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||||
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
|
||||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||||
@@ -83,8 +82,7 @@ public class BriarService extends Service {
|
|||||||
b.setWhen(0); // Don't show the time
|
b.setWhen(0); // Don't show the time
|
||||||
b.setOngoing(true);
|
b.setOngoing(true);
|
||||||
Intent i = new Intent(this, NavDrawerActivity.class);
|
Intent i = new Intent(this, NavDrawerActivity.class);
|
||||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
|
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
FLAG_ACTIVITY_SINGLE_TOP);
|
|
||||||
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
b.setCategory(CATEGORY_SERVICE);
|
b.setCategory(CATEGORY_SERVICE);
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.pm.Signature;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.v7.preference.PreferenceManager;
|
import android.support.v7.preference.PreferenceManager;
|
||||||
@@ -19,11 +19,16 @@ import org.briarproject.bramble.api.lifecycle.ServiceException;
|
|||||||
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;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@@ -31,32 +36,57 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||||
|
import static android.content.Intent.EXTRA_REPLACING;
|
||||||
|
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||||
|
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||||
|
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||||
|
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
||||||
implements Service,
|
implements Service, ScreenFilterMonitor {
|
||||||
ScreenFilterMonitor {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||||
private static final String PREF_SCREEN_FILTER_APPS =
|
private static final String PREF_SCREEN_FILTER_APPS =
|
||||||
"shownScreenFilterApps";
|
"shownScreenFilterApps";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ignore Play Services if it uses this package name and public key - it's
|
||||||
|
* effectively a system app, but not flagged as such on older systems
|
||||||
|
*/
|
||||||
|
private static final String PLAY_SERVICES_PACKAGE =
|
||||||
|
"com.google.android.gms";
|
||||||
|
private static final String PLAY_SERVICES_PUBLIC_KEY =
|
||||||
|
"30820120300D06092A864886F70D01010105000382010D0030820108" +
|
||||||
|
"0282010100AB562E00D83BA208AE0A966F124E29DA11F2AB56D08F58" +
|
||||||
|
"E2CCA91303E9B754D372F640A71B1DCB130967624E4656A7776A9219" +
|
||||||
|
"3DB2E5BFB724A91E77188B0E6A47A43B33D9609B77183145CCDF7B2E" +
|
||||||
|
"586674C9E1565B1F4C6A5955BFF251A63DABF9C55C27222252E875E4" +
|
||||||
|
"F8154A645F897168C0B1BFC612EABF785769BB34AA7984DC7E2EA276" +
|
||||||
|
"4CAE8307D8C17154D7EE5F64A51A44A602C249054157DC02CD5F5C0E" +
|
||||||
|
"55FBEF8519FBE327F0B1511692C5A06F19D18385F5C4DBC2D6B93F68" +
|
||||||
|
"CC2979C70E18AB93866B3BD5DB8999552A0E3B4C99DF58FB918BEDC1" +
|
||||||
|
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
||||||
|
"0B145B6AA192858E79020103";
|
||||||
|
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final LinkedList<String> appNames = new LinkedList<>();
|
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// The following must only be accessed on the UI thread
|
||||||
private final Set<String> apps = new HashSet<>();
|
private final Set<String> apps = new HashSet<>();
|
||||||
private final Set<String> shownApps;
|
private final Set<String> shownApps;
|
||||||
// Used solely for the UiThread
|
|
||||||
private boolean serviceStarted = false;
|
private boolean serviceStarted = false;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -75,7 +105,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
@Override
|
@Override
|
||||||
public Void call() {
|
public Void call() {
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
intentFilter.addAction(ACTION_PACKAGE_ADDED);
|
||||||
intentFilter.addDataScheme("package");
|
intentFilter.addDataScheme("package");
|
||||||
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
|
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
|
||||||
intentFilter);
|
intentFilter);
|
||||||
@@ -109,9 +139,8 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getShownScreenFilterApps() {
|
private Set<String> getShownScreenFilterApps() {
|
||||||
// res must not be modified
|
// Result must not be modified
|
||||||
Set<String> s =
|
Set<String> s = prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
|
||||||
prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
|
|
||||||
HashSet<String> result = new HashSet<>();
|
HashSet<String> result = new HashSet<>();
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
result.addAll(s);
|
result.addAll(s);
|
||||||
@@ -121,7 +150,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
|
||||||
final String packageName =
|
final String packageName =
|
||||||
intent.getData().getEncodedSchemeSpecificPart();
|
intent.getData().getEncodedSchemeSpecificPart();
|
||||||
androidExecutor.runOnUiThread(new Runnable() {
|
androidExecutor.runOnUiThread(new Runnable() {
|
||||||
@@ -155,7 +184,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
@Override
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
|
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
|
||||||
HashSet<String> buf = new HashSet(s);
|
HashSet<String> buf = new HashSet<>(s);
|
||||||
shownApps.addAll(buf);
|
shownApps.addAll(buf);
|
||||||
if (persistent && !s.isEmpty()) {
|
if (persistent && !s.isEmpty()) {
|
||||||
buf.addAll(getShownScreenFilterApps());
|
buf.addAll(getShownScreenFilterApps());
|
||||||
@@ -168,7 +197,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
private Set<String> getInstalledScreenFilterApps() {
|
private Set<String> getInstalledScreenFilterApps() {
|
||||||
HashSet<String> screenFilterApps = new HashSet<>();
|
HashSet<String> screenFilterApps = new HashSet<>();
|
||||||
List<PackageInfo> packageInfos =
|
List<PackageInfo> packageInfos =
|
||||||
pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||||
for (PackageInfo packageInfo : packageInfos) {
|
for (PackageInfo packageInfo : packageInfos) {
|
||||||
if (isOverlayApp(packageInfo)) {
|
if (isOverlayApp(packageInfo)) {
|
||||||
String name = pkgToString(packageInfo);
|
String name = pkgToString(packageInfo);
|
||||||
@@ -180,25 +209,22 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
return screenFilterApps;
|
return screenFilterApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if pkg uses the SYSTEM_ALERT_WINDOW permission and if so
|
// Checks if a package uses the SYSTEM_ALERT_WINDOW permission and if so
|
||||||
// returns the app name.
|
// returns the app name.
|
||||||
@Nullable
|
@Nullable
|
||||||
private String isOverlayApp(String pkg) {
|
private String isOverlayApp(String pkg) {
|
||||||
try {
|
try {
|
||||||
PackageInfo pkgInfo =
|
PackageInfo pkgInfo = pm.getPackageInfo(pkg, GET_PERMISSIONS);
|
||||||
pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
|
|
||||||
if (isOverlayApp(pkgInfo)) {
|
if (isOverlayApp(pkgInfo)) {
|
||||||
return pkgToString(pkgInfo);
|
return pkgToString(pkgInfo);
|
||||||
}
|
}
|
||||||
} catch (PackageManager.NameNotFoundException ignored) {
|
} catch (NameNotFoundException e) {
|
||||||
if (LOG.isLoggable(Level.WARNING)) {
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
LOG.warning("Package name not found: " + pkg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the application name for a given package.
|
// Fetches the application name for a given package.
|
||||||
@Nullable
|
@Nullable
|
||||||
private String pkgToString(PackageInfo pkgInfo) {
|
private String pkgToString(PackageInfo pkgInfo) {
|
||||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||||
@@ -208,25 +234,49 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if an installed pkg is a user app using the permission.
|
// Checks if an installed package is a user app using the permission.
|
||||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||||
int mask = ApplicationInfo.FLAG_SYSTEM |
|
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
||||||
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
|
||||||
// Ignore system apps
|
// Ignore system apps
|
||||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//Get Permissions
|
// Ignore Play Services, it's effectively a system app
|
||||||
String[] requestedPermissions =
|
if (isPlayServices(packageInfo.packageName)) {
|
||||||
packageInfo.requestedPermissions;
|
return false;
|
||||||
|
}
|
||||||
|
// Get permissions
|
||||||
|
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||||
if (requestedPermissions != null) {
|
if (requestedPermissions != null) {
|
||||||
for (String requestedPermission : requestedPermissions) {
|
for (String requestedPermission : requestedPermissions) {
|
||||||
if (requestedPermission
|
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
||||||
.equals(SYSTEM_ALERT_WINDOW)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPlayServices(String pkg) {
|
||||||
|
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
||||||
|
try {
|
||||||
|
PackageInfo sigs = pm.getPackageInfo(pkg, GET_SIGNATURES);
|
||||||
|
// The genuine Play Services app should have a single signature
|
||||||
|
Signature[] signatures = sigs.signatures;
|
||||||
|
if (signatures == null || signatures.length != 1) return false;
|
||||||
|
// Extract the public key from the signature
|
||||||
|
CertificateFactory certFactory =
|
||||||
|
CertificateFactory.getInstance("X509");
|
||||||
|
byte[] signatureBytes = signatures[0].toByteArray();
|
||||||
|
InputStream in = new ByteArrayInputStream(signatureBytes);
|
||||||
|
X509Certificate cert =
|
||||||
|
(X509Certificate) certFactory.generateCertificate(in);
|
||||||
|
byte[] publicKeyBytes = cert.getPublicKey().getEncoded();
|
||||||
|
String publicKey = StringUtils.toHexString(publicKeyBytes);
|
||||||
|
return PLAY_SERVICES_PUBLIC_KEY.equals(publicKey);
|
||||||
|
} catch (NameNotFoundException | CertificateException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
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.RequestCodes.REQUEST_SHARE_BLOG;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
||||||
@@ -149,14 +148,14 @@ public class BlogFragment extends BaseFragment
|
|||||||
return true;
|
return true;
|
||||||
case R.id.action_blog_share:
|
case R.id.action_blog_share:
|
||||||
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
|
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
|
||||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivityForResult(i2, REQUEST_SHARE_BLOG);
|
startActivityForResult(i2, REQUEST_SHARE_BLOG);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_blog_sharing_status:
|
case R.id.action_blog_sharing_status:
|
||||||
Intent i3 = new Intent(getActivity(),
|
Intent i3 = new Intent(getActivity(),
|
||||||
BlogSharingStatusActivity.class);
|
BlogSharingStatusActivity.class);
|
||||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivity(i3);
|
startActivity(i3);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
|||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRssFeed() {
|
||||||
|
return header.isRssFeed();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isRead() {
|
public boolean isRead() {
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
author.setAuthor(a);
|
author.setAuthor(a);
|
||||||
author.setAuthorStatus(post.getAuthorStatus());
|
author.setAuthorStatus(post.getAuthorStatus());
|
||||||
author.setDate(post.getTimestamp());
|
author.setDate(post.getTimestamp());
|
||||||
author.setPersona(AuthorView.NORMAL);
|
author.setPersona(
|
||||||
|
item.isRssFeed() ? AuthorView.RSS_FEED : AuthorView.NORMAL);
|
||||||
// TODO make author clickable more often #624
|
// TODO make author clickable more often #624
|
||||||
if (item.getHeader().getType() == POST) {
|
if (item.getHeader().getType() == POST) {
|
||||||
author.setBlogLink(post.getGroupId());
|
author.setBlogLink(post.getGroupId());
|
||||||
@@ -168,7 +169,9 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
reblogger.setVisibility(VISIBLE);
|
reblogger.setVisibility(VISIBLE);
|
||||||
reblogger.setPersona(AuthorView.REBLOGGER);
|
reblogger.setPersona(AuthorView.REBLOGGER);
|
||||||
|
|
||||||
author.setPersona(AuthorView.COMMENTER);
|
author.setPersona(item.getHeader().getRootPost().isRssFeed() ?
|
||||||
|
AuthorView.RSS_FEED_REBLOGGED :
|
||||||
|
AuthorView.COMMENTER);
|
||||||
|
|
||||||
// comments
|
// comments
|
||||||
for (BlogCommentHeader c : item.getComments()) {
|
for (BlogCommentHeader c : item.getComments()) {
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ public class FeedFragment extends BaseFragment implements
|
|||||||
case R.id.action_rss_feeds_import:
|
case R.id.action_rss_feeds_import:
|
||||||
Intent i2 =
|
Intent i2 =
|
||||||
new Intent(getActivity(), RssFeedImportActivity.class);
|
new Intent(getActivity(), RssFeedImportActivity.class);
|
||||||
i2.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
|
||||||
startActivity(i2);
|
startActivity(i2);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_rss_feeds_manage:
|
case R.id.action_rss_feeds_manage:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -39,12 +39,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
|||||||
if (item == null) return;
|
if (item == null) return;
|
||||||
|
|
||||||
// Feed Title
|
// Feed Title
|
||||||
if (item.getTitle() != null) {
|
ui.title.setText(item.getTitle());
|
||||||
ui.title.setText(item.getTitle());
|
|
||||||
ui.title.setVisibility(VISIBLE);
|
|
||||||
} else {
|
|
||||||
ui.title.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete Button
|
// Delete Button
|
||||||
ui.delete.setOnClickListener(new OnClickListener() {
|
ui.delete.setOnClickListener(new OnClickListener() {
|
||||||
@@ -75,6 +70,14 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
|||||||
} else {
|
} else {
|
||||||
ui.description.setVisibility(GONE);
|
ui.description.setVisibility(GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open feed's blog when clicked
|
||||||
|
ui.layout.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onFeedClick(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,8 +102,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class FeedViewHolder extends RecyclerView.ViewHolder {
|
static class FeedViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final View layout;
|
||||||
private final TextView title;
|
private final TextView title;
|
||||||
private final ImageView delete;
|
private final ImageButton delete;
|
||||||
private final TextView imported;
|
private final TextView imported;
|
||||||
private final TextView updated;
|
private final TextView updated;
|
||||||
private final TextView author;
|
private final TextView author;
|
||||||
@@ -110,8 +114,9 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
|||||||
private FeedViewHolder(View v) {
|
private FeedViewHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
|
|
||||||
|
layout = v;
|
||||||
title = (TextView) v.findViewById(R.id.titleView);
|
title = (TextView) v.findViewById(R.id.titleView);
|
||||||
delete = (ImageView) v.findViewById(R.id.deleteButton);
|
delete = (ImageButton) v.findViewById(R.id.deleteButton);
|
||||||
imported = (TextView) v.findViewById(R.id.importedView);
|
imported = (TextView) v.findViewById(R.id.importedView);
|
||||||
updated = (TextView) v.findViewById(R.id.updatedView);
|
updated = (TextView) v.findViewById(R.id.updatedView);
|
||||||
author = (TextView) v.findViewById(R.id.authorView);
|
author = (TextView) v.findViewById(R.id.authorView);
|
||||||
@@ -121,6 +126,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RssFeedListener {
|
interface RssFeedListener {
|
||||||
|
void onFeedClick(Feed feed);
|
||||||
void onDeleteClick(Feed feed);
|
void onDeleteClick(Feed feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
@@ -15,7 +14,6 @@ import android.widget.ProgressBar;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
@@ -44,9 +42,6 @@ public class RssFeedImportActivity extends BriarActivity {
|
|||||||
@IoExecutor
|
@IoExecutor
|
||||||
Executor ioExecutor;
|
Executor ioExecutor;
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
|
||||||
private volatile GroupId groupId = null;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
volatile FeedManager feedManager;
|
volatile FeedManager feedManager;
|
||||||
@@ -55,12 +50,6 @@ public class RssFeedImportActivity extends BriarActivity {
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// GroupId from Intent
|
|
||||||
Intent i = getIntent();
|
|
||||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
|
||||||
if (b == null) throw new IllegalStateException("No Group in intent.");
|
|
||||||
groupId = new GroupId(b);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_rss_feed_import);
|
setContentView(R.layout.activity_rss_feed_import);
|
||||||
|
|
||||||
urlInput = (EditText) findViewById(R.id.urlInput);
|
urlInput = (EditText) findViewById(R.id.urlInput);
|
||||||
@@ -128,7 +117,7 @@ public class RssFeedImportActivity extends BriarActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
feedManager.addFeed(url, groupId);
|
feedManager.addFeed(url);
|
||||||
feedImported();
|
feedImported();
|
||||||
} catch (DbException | IOException e) {
|
} catch (DbException | IOException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
@@ -23,6 +24,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@@ -34,7 +36,6 @@ public class RssFeedManageActivity extends BriarActivity
|
|||||||
|
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
private RssFeedAdapter adapter;
|
private RssFeedAdapter adapter;
|
||||||
private GroupId groupId;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
@@ -44,12 +45,6 @@ public class RssFeedManageActivity extends BriarActivity
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// GroupId from Intent
|
|
||||||
Intent i = getIntent();
|
|
||||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
|
||||||
if (b == null) throw new IllegalStateException("No Group in intent.");
|
|
||||||
groupId = new GroupId(b);
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_rss_feed_manage);
|
setContentView(R.layout.activity_rss_feed_manage);
|
||||||
|
|
||||||
adapter = new RssFeedAdapter(this, this);
|
adapter = new RssFeedAdapter(this, this);
|
||||||
@@ -87,7 +82,6 @@ public class RssFeedManageActivity extends BriarActivity
|
|||||||
return true;
|
return true;
|
||||||
case R.id.action_rss_feeds_import:
|
case R.id.action_rss_feeds_import:
|
||||||
Intent i = new Intent(this, RssFeedImportActivity.class);
|
Intent i = new Intent(this, RssFeedImportActivity.class);
|
||||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
@@ -100,21 +94,32 @@ public class RssFeedManageActivity extends BriarActivity
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFeedClick(Feed feed) {
|
||||||
|
Intent i = new Intent(this, BlogActivity.class);
|
||||||
|
i.putExtra(GROUP_ID, feed.getBlogId().getBytes());
|
||||||
|
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDeleteClick(final Feed feed) {
|
public void onDeleteClick(final Feed feed) {
|
||||||
runOnDbThread(new Runnable() {
|
DialogInterface.OnClickListener okListener =
|
||||||
@Override
|
new DialogInterface.OnClickListener() {
|
||||||
public void run() {
|
@Override
|
||||||
try {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
feedManager.removeFeed(feed.getUrl());
|
deleteFeed(feed);
|
||||||
onFeedDeleted(feed);
|
}
|
||||||
} catch (DbException e) {
|
};
|
||||||
if (LOG.isLoggable(WARNING))
|
AlertDialog.Builder builder = new AlertDialog.Builder(this,
|
||||||
LOG.log(WARNING, e.toString(), e);
|
R.style.BriarDialogTheme);
|
||||||
onDeleteError();
|
builder.setTitle(getString(R.string.blogs_rss_remove_feed));
|
||||||
}
|
builder.setMessage(
|
||||||
}
|
getString(R.string.blogs_rss_remove_feed_dialog_message));
|
||||||
});
|
builder.setPositiveButton(R.string.cancel, null);
|
||||||
|
builder.setNegativeButton(R.string.blogs_rss_remove_feed_ok,
|
||||||
|
okListener);
|
||||||
|
builder.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFeeds() {
|
private void loadFeeds() {
|
||||||
@@ -149,6 +154,22 @@ public class RssFeedManageActivity extends BriarActivity
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteFeed(final Feed feed) {
|
||||||
|
runOnDbThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
feedManager.removeFeed(feed);
|
||||||
|
onFeedDeleted(feed);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
onDeleteError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void onLoadError() {
|
private void onLoadError() {
|
||||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -946,6 +946,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void respondToRequest(final ConversationRequestItem item,
|
public void respondToRequest(final ConversationRequestItem item,
|
||||||
final boolean accept) {
|
final boolean accept) {
|
||||||
|
item.setAnswered(true);
|
||||||
int position = adapter.findItemPosition(item);
|
int position = adapter.findItemPosition(item);
|
||||||
if (position != INVALID_POSITION) {
|
if (position != INVALID_POSITION) {
|
||||||
adapter.notifyItemChanged(position, item);
|
adapter.notifyItemChanged(position, item);
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class ConversationRequestItem extends ConversationNoticeInItem {
|
|||||||
private final GroupId requestedGroupId;
|
private final GroupId requestedGroupId;
|
||||||
private final RequestType requestType;
|
private final RequestType requestType;
|
||||||
private final SessionId sessionId;
|
private final SessionId sessionId;
|
||||||
private final boolean answered, canBeOpened;
|
private final boolean canBeOpened;
|
||||||
|
private boolean answered;
|
||||||
|
|
||||||
ConversationRequestItem(MessageId id, GroupId groupId,
|
ConversationRequestItem(MessageId id, GroupId groupId,
|
||||||
RequestType requestType, SessionId sessionId, String text,
|
RequestType requestType, SessionId sessionId, String text,
|
||||||
@@ -53,6 +54,10 @@ class ConversationRequestItem extends ConversationNoticeInItem {
|
|||||||
return answered;
|
return answered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAnswered(boolean answered) {
|
||||||
|
this.answered = answered;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canBeOpened() {
|
public boolean canBeOpened() {
|
||||||
return canBeOpened;
|
return canBeOpened;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
|
|||||||
acceptButton.setOnClickListener(new OnClickListener() {
|
acceptButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
acceptButton.setEnabled(false);
|
||||||
|
declineButton.setEnabled(false);
|
||||||
listener.respondToRequest(item, true);
|
listener.respondToRequest(item, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -58,6 +60,8 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
|
|||||||
declineButton.setOnClickListener(new OnClickListener() {
|
declineButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
acceptButton.setEnabled(false);
|
||||||
|
declineButton.setEnabled(false);
|
||||||
listener.respondToRequest(item, false);
|
listener.respondToRequest(item, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM;
|
||||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
||||||
@@ -117,18 +116,15 @@ public class ForumActivity extends
|
|||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
// Handle presses on the action bar items
|
// Handle presses on the action bar items
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_forum_compose_post:
|
|
||||||
showTextInput(null);
|
|
||||||
return true;
|
|
||||||
case R.id.action_forum_share:
|
case R.id.action_forum_share:
|
||||||
Intent i2 = new Intent(this, ShareForumActivity.class);
|
Intent i2 = new Intent(this, ShareForumActivity.class);
|
||||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivityForResult(i2, REQUEST_SHARE_FORUM);
|
startActivityForResult(i2, REQUEST_SHARE_FORUM);
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_forum_sharing_status:
|
case R.id.action_forum_sharing_status:
|
||||||
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
||||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
startActivity(i3);
|
startActivity(i3);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import javax.annotation.Nullable;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
|
||||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
|
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
|
||||||
|
|
||||||
@@ -50,8 +51,8 @@ public class GroupActivity extends
|
|||||||
GroupController controller;
|
GroupController controller;
|
||||||
|
|
||||||
private boolean isCreator, isDissolved = false;
|
private boolean isCreator, isDissolved = false;
|
||||||
private MenuItem writeMenuItem, revealMenuItem, inviteMenuItem,
|
private MenuItem revealMenuItem, inviteMenuItem, leaveMenuItem,
|
||||||
leaveMenuItem, dissolveMenuItem;
|
dissolveMenuItem;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -139,7 +140,6 @@ public class GroupActivity extends
|
|||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.group_actions, menu);
|
inflater.inflate(R.menu.group_actions, menu);
|
||||||
|
|
||||||
writeMenuItem = menu.findItem(R.id.action_group_compose_message);
|
|
||||||
revealMenuItem = menu.findItem(R.id.action_group_reveal);
|
revealMenuItem = menu.findItem(R.id.action_group_reveal);
|
||||||
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
inviteMenuItem = menu.findItem(R.id.action_group_invite);
|
||||||
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
leaveMenuItem = menu.findItem(R.id.action_group_leave);
|
||||||
@@ -152,9 +152,6 @@ public class GroupActivity extends
|
|||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_group_compose_message:
|
|
||||||
showTextInput(null);
|
|
||||||
return true;
|
|
||||||
case R.id.action_group_member_list:
|
case R.id.action_group_member_list:
|
||||||
Intent i1 = new Intent(this, GroupMemberListActivity.class);
|
Intent i1 = new Intent(this, GroupMemberListActivity.class);
|
||||||
i1.putExtra(GROUP_ID, groupId.getBytes());
|
i1.putExtra(GROUP_ID, groupId.getBytes());
|
||||||
@@ -205,7 +202,6 @@ public class GroupActivity extends
|
|||||||
|
|
||||||
private void setGroupEnabled(boolean enabled) {
|
private void setGroupEnabled(boolean enabled) {
|
||||||
isDissolved = !enabled;
|
isDissolved = !enabled;
|
||||||
if (writeMenuItem != null) writeMenuItem.setVisible(enabled);
|
|
||||||
textInput.setSendButtonEnabled(enabled);
|
textInput.setSendButtonEnabled(enabled);
|
||||||
list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f);
|
list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f);
|
||||||
|
|
||||||
@@ -213,6 +209,8 @@ public class GroupActivity extends
|
|||||||
textInput.setVisibility(GONE);
|
textInput.setVisibility(GONE);
|
||||||
if (textInput.isKeyboardOpen()) textInput.hideSoftKeyboard();
|
if (textInput.isKeyboardOpen()) textInput.hideSoftKeyboard();
|
||||||
if (textInput.isEmojiDrawerOpen()) textInput.hideEmojiDrawer();
|
if (textInput.isEmojiDrawerOpen()) textInput.hideEmojiDrawer();
|
||||||
|
} else {
|
||||||
|
textInput.setVisibility(VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +227,6 @@ public class GroupActivity extends
|
|||||||
leaveMenuItem.setVisible(true);
|
leaveMenuItem.setVisible(true);
|
||||||
dissolveMenuItem.setVisible(false);
|
dissolveMenuItem.setVisible(false);
|
||||||
}
|
}
|
||||||
writeMenuItem.setVisible(!isDissolved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLeaveGroupDialog() {
|
private void showLeaveGroupDialog() {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
@@ -40,7 +41,7 @@ public class GroupMemberListActivity extends BriarActivity {
|
|||||||
public void onCreate(@Nullable final Bundle state) {
|
public void onCreate(@Nullable final Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
setContentView(R.layout.list);
|
setContentView(R.layout.activity_sharing_status);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||||
@@ -52,6 +53,9 @@ public class GroupMemberListActivity extends BriarActivity {
|
|||||||
list.setLayoutManager(linearLayoutManager);
|
list.setLayoutManager(linearLayoutManager);
|
||||||
adapter = new MemberListAdapter(this);
|
adapter = new MemberListAdapter(this);
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
|
|
||||||
|
TextView info = (TextView) findViewById(R.id.info);
|
||||||
|
info.setText(R.string.sharing_status_groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.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.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
|
|
||||||
@@ -25,6 +26,11 @@ public class BlogSharingStatusActivity extends SharingStatusActivity {
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getInfoText() {
|
||||||
|
return R.string.sharing_status_blog;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
protected Collection<Contact> getSharedWith() throws DbException {
|
protected Collection<Contact> getSharedWith() throws DbException {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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.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.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||||
|
|
||||||
@@ -25,6 +26,11 @@ public class ForumSharingStatusActivity extends SharingStatusActivity {
|
|||||||
component.inject(this);
|
component.inject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int getInfoText() {
|
||||||
|
return R.string.sharing_status_forum;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
protected Collection<Contact> getSharedWith() throws DbException {
|
protected Collection<Contact> getSharedWith() throws DbException {
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package org.briarproject.briar.android.sharing;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
@@ -45,7 +47,7 @@ abstract class SharingStatusActivity extends BriarActivity {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.list);
|
setContentView(R.layout.activity_sharing_status);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||||
@@ -57,6 +59,9 @@ abstract class SharingStatusActivity extends BriarActivity {
|
|||||||
list.setLayoutManager(new LinearLayoutManager(this));
|
list.setLayoutManager(new LinearLayoutManager(this));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setEmptyText(getString(R.string.nobody));
|
list.setEmptyText(getString(R.string.nobody));
|
||||||
|
|
||||||
|
TextView info = (TextView) findViewById(R.id.info);
|
||||||
|
info.setText(getInfoText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,6 +89,9 @@ abstract class SharingStatusActivity extends BriarActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
abstract int getInfoText();
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
abstract protected Collection<Contact> getSharedWith() throws DbException;
|
abstract protected Collection<Contact> getSharedWith() throws DbException;
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ import javax.inject.Inject;
|
|||||||
import static android.support.design.widget.Snackbar.make;
|
import static android.support.design.widget.Snackbar.make;
|
||||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||||
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
|
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCount;
|
import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCount;
|
||||||
|
|
||||||
@@ -55,7 +53,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
implements ThreadListListener<H>, TextInputListener, SharingListener,
|
||||||
ThreadItemListener<I> {
|
ThreadItemListener<I> {
|
||||||
|
|
||||||
protected static final String KEY_INPUT_VISIBILITY = "inputVisibility";
|
|
||||||
protected static final String KEY_REPLY_ID = "replyId";
|
protected static final String KEY_REPLY_ID = "replyId";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
@@ -89,7 +86,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
getController().setGroupId(groupId);
|
getController().setGroupId(groupId);
|
||||||
|
|
||||||
textInput = (TextInputView) findViewById(R.id.text_input_container);
|
textInput = (TextInputView) findViewById(R.id.text_input_container);
|
||||||
textInput.setVisibility(GONE);
|
|
||||||
textInput.setListener(this);
|
textInput.setListener(this);
|
||||||
list = (BriarRecyclerView) findViewById(R.id.list);
|
list = (BriarRecyclerView) findViewById(R.id.list);
|
||||||
layoutManager = new LinearLayoutManager(this);
|
layoutManager = new LinearLayoutManager(this);
|
||||||
@@ -181,8 +177,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
} else {
|
} else {
|
||||||
adapter.setItems(items);
|
adapter.setItems(items);
|
||||||
list.showData();
|
list.showData();
|
||||||
if (replyId != null)
|
updateTextInput(replyId);
|
||||||
adapter.setHighlightedItem(replyId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Concurrent update, reloading");
|
LOG.info("Concurrent update, reloading");
|
||||||
@@ -231,18 +226,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
list.stopPeriodicUpdate();
|
list.stopPeriodicUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
|
||||||
boolean visible = savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY);
|
|
||||||
textInput.setVisibility(visible ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
boolean visible = textInput.getVisibility() == VISIBLE;
|
|
||||||
outState.putBoolean(KEY_INPUT_VISIBILITY, visible);
|
|
||||||
ThreadItem replyItem = adapter.getHighlightedItem();
|
ThreadItem replyItem = adapter.getHighlightedItem();
|
||||||
if (replyItem != null) {
|
if (replyItem != null) {
|
||||||
outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes());
|
outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes());
|
||||||
@@ -262,9 +248,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if (textInput.getVisibility() == VISIBLE) {
|
if (adapter.getHighlightedItem() != null) {
|
||||||
textInput.setVisibility(GONE);
|
updateTextInput(null);
|
||||||
adapter.setHighlightedItem(null);
|
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
@@ -280,7 +265,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReplyClick(final I item) {
|
public void onReplyClick(final I item) {
|
||||||
showTextInput(item);
|
updateTextInput(item.getId());
|
||||||
if (textInput.isKeyboardOpen()) {
|
if (textInput.isKeyboardOpen()) {
|
||||||
scrollToItemAtTop(item);
|
scrollToItemAtTop(item);
|
||||||
} else {
|
} else {
|
||||||
@@ -330,20 +315,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
snackbar.show();
|
snackbar.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void showTextInput(@Nullable I replyItem) {
|
private void updateTextInput(@Nullable MessageId replyItemId) {
|
||||||
// An animation here would be an overkill because of the keyboard
|
if (replyItemId != null) {
|
||||||
// popping up.
|
textInput.setHint(R.string.forum_message_reply_hint);
|
||||||
// only clear the text when the input container was not visible
|
textInput.requestFocus();
|
||||||
if (textInput.getVisibility() != VISIBLE) {
|
textInput.showSoftKeyboard();
|
||||||
textInput.setVisibility(VISIBLE);
|
} else {
|
||||||
textInput.setText("");
|
textInput.setHint(R.string.forum_new_message_hint);
|
||||||
}
|
}
|
||||||
textInput.requestFocus();
|
adapter.setHighlightedItem(replyItemId);
|
||||||
textInput.showSoftKeyboard();
|
|
||||||
textInput.setHint(replyItem == null ? R.string.forum_new_message_hint :
|
|
||||||
R.string.forum_message_reply_hint);
|
|
||||||
adapter.setHighlightedItem(
|
|
||||||
replyItem == null ? null : replyItem.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -369,9 +349,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
|||||||
};
|
};
|
||||||
getController().createAndStoreMessage(text, replyItem, handler);
|
getController().createAndStoreMessage(text, replyItem, handler);
|
||||||
textInput.hideSoftKeyboard();
|
textInput.hideSoftKeyboard();
|
||||||
textInput.setVisibility(GONE);
|
|
||||||
textInput.setText("");
|
textInput.setText("");
|
||||||
adapter.setHighlightedItem(null);
|
updateTextInput(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract int getMaxBodyLength();
|
protected abstract int getMaxBodyLength();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
|||||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||||
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.Author.Status.OURSELVES;
|
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||||
|
|
||||||
@@ -40,6 +41,8 @@ public class AuthorView extends RelativeLayout {
|
|||||||
public static final int REBLOGGER = 1;
|
public static final int REBLOGGER = 1;
|
||||||
public static final int COMMENTER = 2;
|
public static final int COMMENTER = 2;
|
||||||
public static final int LIST = 3;
|
public static final int LIST = 3;
|
||||||
|
public static final int RSS_FEED = 4;
|
||||||
|
public static final int RSS_FEED_REBLOGGED = 5;
|
||||||
|
|
||||||
private final CircleImageView avatar;
|
private final CircleImageView avatar;
|
||||||
private final ImageView avatarIcon;
|
private final ImageView avatarIcon;
|
||||||
@@ -83,7 +86,13 @@ public class AuthorView extends RelativeLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthorStatus(Status status) {
|
public void setAuthorStatus(Status status) {
|
||||||
trustIndicator.setTrustLevel(status);
|
if (status != NONE) {
|
||||||
|
trustIndicator.setTrustLevel(status);
|
||||||
|
trustIndicator.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
trustIndicator.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
if (status == OURSELVES) {
|
if (status == OURSELVES) {
|
||||||
authorName.setTypeface(authorNameTypeface, BOLD);
|
authorName.setTypeface(authorNameTypeface, BOLD);
|
||||||
} else {
|
} else {
|
||||||
@@ -124,10 +133,17 @@ public class AuthorView extends RelativeLayout {
|
|||||||
setOnClickListener(null);
|
setOnClickListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles this view for a different persona.
|
||||||
|
*
|
||||||
|
* Attention: RSS_FEED and RSS_FEED_REBLOGGED change the avatar
|
||||||
|
* and override the one set by
|
||||||
|
* {@link AuthorView#setAuthor(Author)}.
|
||||||
|
*/
|
||||||
public void setPersona(int persona) {
|
public void setPersona(int persona) {
|
||||||
switch (persona) {
|
switch (persona) {
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
avatarIcon.setVisibility(VISIBLE);
|
avatarIcon.setVisibility(INVISIBLE);
|
||||||
date.setVisibility(VISIBLE);
|
date.setVisibility(VISIBLE);
|
||||||
setAvatarSize(R.dimen.blogs_avatar_normal_size);
|
setAvatarSize(R.dimen.blogs_avatar_normal_size);
|
||||||
setTextSize(authorName, R.dimen.text_size_small);
|
setTextSize(authorName, R.dimen.text_size_small);
|
||||||
@@ -158,6 +174,24 @@ public class AuthorView extends RelativeLayout {
|
|||||||
setCenterVertical(authorName, true);
|
setCenterVertical(authorName, true);
|
||||||
setCenterVertical(trustIndicator, true);
|
setCenterVertical(trustIndicator, true);
|
||||||
break;
|
break;
|
||||||
|
case RSS_FEED:
|
||||||
|
avatarIcon.setVisibility(INVISIBLE);
|
||||||
|
date.setVisibility(VISIBLE);
|
||||||
|
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||||
|
setAvatarSize(R.dimen.blogs_avatar_normal_size);
|
||||||
|
setTextSize(authorName, R.dimen.text_size_small);
|
||||||
|
setCenterVertical(authorName, false);
|
||||||
|
setCenterVertical(trustIndicator, false);
|
||||||
|
break;
|
||||||
|
case RSS_FEED_REBLOGGED:
|
||||||
|
avatarIcon.setVisibility(INVISIBLE);
|
||||||
|
date.setVisibility(VISIBLE);
|
||||||
|
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||||
|
setAvatarSize(R.dimen.blogs_avatar_comment_size);
|
||||||
|
setTextSize(authorName, R.dimen.text_size_tiny);
|
||||||
|
setCenterVertical(authorName, false);
|
||||||
|
setCenterVertical(trustIndicator, false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="48.0"
|
|
||||||
android:viewportWidth="48.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#06b9ff"
|
|
||||||
android:pathData="M9.1,19.3l14.9,11.8l14.9,-11.8l-1.9,-2.4l-13,10.4l-13,-10.4z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportHeight="48.0"
|
|
||||||
android:viewportWidth="48.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#06b9ff"
|
|
||||||
android:pathData="M38.9,28.7l-14.9,-11.8l-14.9,11.8l1.9,2.4l13,-10.4l13,10.4z"/>
|
|
||||||
</vector>
|
|
||||||
10
briar-android/src/main/res/drawable/ic_info.xml
Normal file
10
briar-android/src/main/res/drawable/ic_info.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:alpha="0.56"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
||||||
|
</vector>
|
||||||
25
briar-android/src/main/res/drawable/ic_rss_feed.xml
Normal file
25
briar-android/src/main/res/drawable/ic_rss_feed.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="30dp"
|
||||||
|
android:height="30dp"
|
||||||
|
android:viewportHeight="30"
|
||||||
|
android:viewportWidth="30">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffa500"
|
||||||
|
android:pathData="M0,8.88178e-16 L30,8.88178e-16 L30,30 L0,30 L0,8.88178e-16 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678
|
||||||
|
C11.9661,22.7434,10.6078,24.1017,8.9322,24.1017
|
||||||
|
C7.25663,24.1017,5.8983,22.7434,5.8983,21.0678
|
||||||
|
C5.8983,19.3922,7.25663,18.0339,8.9322,18.0339 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M5.8983,15 A9.1016949,9.1016949,0,0,1,15,24.1017 L18.0339,24.1017
|
||||||
|
A12.135593,12.135593,0,0,0,5.8983,11.9661 Z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M5.8983,8.9322 A15.169492,15.169492,0,0,1,21.0678,24.1017 L24.1017,24.1017
|
||||||
|
A18.20339,18.20339,0,0,0,5.8983,5.8983 Z"/>
|
||||||
|
</vector>
|
||||||
@@ -5,5 +5,5 @@
|
|||||||
|
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="2dp"
|
||||||
android:color="@color/forum_discussion_nested_line"/>
|
android:color="@color/thread_indicator"/>
|
||||||
</shape>
|
</shape>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:drawable="@drawable/chevron48dp_down" android:state_selected="true"/>
|
|
||||||
<item android:drawable="@drawable/chevron48dp_up"/>
|
|
||||||
</selector>
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawableLeft="@drawable/ic_info"
|
||||||
|
android:drawablePadding="@dimen/margin_medium"
|
||||||
|
android:padding="@dimen/margin_medium"
|
||||||
|
android:textColor="?android:textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_tiny"
|
||||||
|
tools:text="@string/sharing_status_forum"/>
|
||||||
|
|
||||||
|
<View style="@style/Divider.Horizontal"/>
|
||||||
|
|
||||||
|
<org.briarproject.briar.android.view.BriarRecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:scrollToEnd="false"
|
||||||
|
tools:listitem="@layout/list_item_contact"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
android:textSize="@dimen/text_size_medium"
|
android:textSize="@dimen/text_size_medium"
|
||||||
tools:text="This is a name of a RSS Feed"/>
|
tools:text="This is a name of a RSS Feed"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageButton
|
||||||
android:id="@+id/deleteButton"
|
android:id="@+id/deleteButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -3,12 +3,6 @@
|
|||||||
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:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_forum_compose_post"
|
|
||||||
android:icon="@drawable/forum_item_create_white"
|
|
||||||
android:title="@string/forum_compose_post"
|
|
||||||
app:showAsAction="always"/>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_forum_share"
|
android:id="@+id/action_forum_share"
|
||||||
android:icon="@drawable/social_share_white"
|
android:icon="@drawable/social_share_white"
|
||||||
|
|||||||
@@ -3,12 +3,6 @@
|
|||||||
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:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_group_compose_message"
|
|
||||||
android:icon="@drawable/forum_item_create_white"
|
|
||||||
android:title="@string/groups_compose_message"
|
|
||||||
app:showAsAction="always"/>
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_group_member_list"
|
android:id="@+id/action_group_member_list"
|
||||||
android:icon="@drawable/ic_group_white"
|
android:icon="@drawable/ic_group_white"
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
<enum name="reblogger" value="1"/>
|
<enum name="reblogger" value="1"/>
|
||||||
<enum name="commenter" value="2"/>
|
<enum name="commenter" value="2"/>
|
||||||
<enum name="list" value="3"/>
|
<enum name="list" value="3"/>
|
||||||
|
<enum name="rss_feed" value="4"/>
|
||||||
|
<enum name="rss_feed_reblogged" value="5"/>
|
||||||
</attr>
|
</attr>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
<!-- this is needed as preference_category_material layout uses this color as the text color -->
|
<!-- this is needed as preference_category_material layout uses this color as the text color -->
|
||||||
<color name="preference_fallback_accent_color">@color/briar_accent</color>
|
<color name="preference_fallback_accent_color">@color/briar_accent</color>
|
||||||
|
|
||||||
|
<color name="thread_indicator">#9e9e9e</color>
|
||||||
<color name="divider">#c1c1c1</color>
|
<color name="divider">#c1c1c1</color>
|
||||||
<color name="default_separator_inverted">#ffffff</color>
|
|
||||||
<color name="menu_background">#FFFFFF</color>
|
<color name="menu_background">#FFFFFF</color>
|
||||||
|
|
||||||
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
|
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
|
||||||
<color name="forum_discussion_nested_line">#cfd2d4</color>
|
|
||||||
<color name="forum_cell_highlight">#ffffff</color>
|
<color name="forum_cell_highlight">#ffffff</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -162,7 +162,6 @@
|
|||||||
<string name="groups_create_group_invitation_button">Send Invitation</string>
|
<string name="groups_create_group_invitation_button">Send Invitation</string>
|
||||||
<string name="groups_create_group_hint">Add a name for your private group</string>
|
<string name="groups_create_group_hint">Add a name for your private group</string>
|
||||||
<string name="groups_invitation_sent">Group invitation has been sent</string>
|
<string name="groups_invitation_sent">Group invitation has been sent</string>
|
||||||
<string name="groups_compose_message">Compose Message</string>
|
|
||||||
<string name="groups_message_sent">Message sent</string>
|
<string name="groups_message_sent">Message sent</string>
|
||||||
<string name="groups_member_list">Member List</string>
|
<string name="groups_member_list">Member List</string>
|
||||||
<string name="groups_invite_members">Invite Members</string>
|
<string name="groups_invite_members">Invite Members</string>
|
||||||
@@ -194,6 +193,7 @@
|
|||||||
<string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string>
|
<string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string>
|
||||||
<string name="groups_invitations_response_accepted_received">%s accepted the group invitation.</string>
|
<string name="groups_invitations_response_accepted_received">%s accepted the group invitation.</string>
|
||||||
<string name="groups_invitations_response_declined_received">%s declined the group invitation.</string>
|
<string name="groups_invitations_response_declined_received">%s declined the group invitation.</string>
|
||||||
|
<string name="sharing_status_groups">Only the creator can invite new members to the group. Below are all current members of the group.</string>
|
||||||
|
|
||||||
<!-- Private Groups Revealing Contacts -->
|
<!-- Private Groups Revealing Contacts -->
|
||||||
<string name="groups_reveal_contacts">Reveal Contacts</string>
|
<string name="groups_reveal_contacts">Reveal Contacts</string>
|
||||||
@@ -215,15 +215,10 @@
|
|||||||
<item quantity="one">%d post</item>
|
<item quantity="one">%d post</item>
|
||||||
<item quantity="other">%d posts</item>
|
<item quantity="other">%d posts</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="forum_compose_post">New Forum Post</string>
|
|
||||||
<string name="forum_new_entry_posted">Forum entry posted</string>
|
<string name="forum_new_entry_posted">Forum entry posted</string>
|
||||||
<string name="forum_new_message_hint">New Entry</string>
|
<string name="forum_new_message_hint">New Entry</string>
|
||||||
<string name="forum_message_reply_hint">New Reply</string>
|
<string name="forum_message_reply_hint">New Reply</string>
|
||||||
<string name="btn_reply">Reply</string>
|
<string name="btn_reply">Reply</string>
|
||||||
<plurals name="message_replies">
|
|
||||||
<item quantity="one">%1$d reply</item>
|
|
||||||
<item quantity="other">%1$d replies</item>
|
|
||||||
</plurals>
|
|
||||||
<string name="forum_leave">Leave Forum</string>
|
<string name="forum_leave">Leave Forum</string>
|
||||||
<string name="dialog_title_leave_forum">Confirm Leaving Forum</string>
|
<string name="dialog_title_leave_forum">Confirm Leaving Forum</string>
|
||||||
<string name="dialog_message_leave_forum">Are you sure that you want to leave this forum? Contacts you have shared this forum with might get cut off from receiving updates for this forum.</string>
|
<string name="dialog_message_leave_forum">Are you sure that you want to leave this forum? Contacts you have shared this forum with might get cut off from receiving updates for this forum.</string>
|
||||||
@@ -252,6 +247,7 @@
|
|||||||
<string name="forum_invitation_response_declined_received">%s declined the forum invitation.</string>
|
<string name="forum_invitation_response_declined_received">%s declined the forum invitation.</string>
|
||||||
|
|
||||||
<string name="sharing_status">Sharing Status</string>
|
<string name="sharing_status">Sharing Status</string>
|
||||||
|
<string name="sharing_status_forum">Any member of a forum can share it with their contacts. You are sharing this forum with the following contacts. There may also be other members who you can\'t see.</string>
|
||||||
<string name="shared_with">Shared with %1$d (%2$d online)</string>
|
<string name="shared_with">Shared with %1$d (%2$d online)</string>
|
||||||
<plurals name="forums_shared">
|
<plurals name="forums_shared">
|
||||||
<item quantity="one">%d forum shared by contacts</item>
|
<item quantity="one">%d forum shared by contacts</item>
|
||||||
@@ -291,6 +287,7 @@
|
|||||||
<string name="blogs_sharing_invitations_title">Blog Invitations</string>
|
<string name="blogs_sharing_invitations_title">Blog Invitations</string>
|
||||||
<string name="blogs_sharing_joined_toast">Subscribed to Blog</string>
|
<string name="blogs_sharing_joined_toast">Subscribed to Blog</string>
|
||||||
<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string>
|
<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string>
|
||||||
|
<string name="sharing_status_blog">Anyone who subscribes to a blog can share it with their contacts. You are sharing this blog with the following contacts. There may also be other subscribers who you can\'t see.</string>
|
||||||
|
|
||||||
<!-- RSS Feeds -->
|
<!-- RSS Feeds -->
|
||||||
<string name="blogs_rss_feeds_import">Import RSS Feed</string>
|
<string name="blogs_rss_feeds_import">Import RSS Feed</string>
|
||||||
@@ -301,6 +298,9 @@
|
|||||||
<string name="blogs_rss_feeds_manage_imported">Imported:</string>
|
<string name="blogs_rss_feeds_manage_imported">Imported:</string>
|
||||||
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
||||||
<string name="blogs_rss_feeds_manage_updated">Last Updated:</string>
|
<string name="blogs_rss_feeds_manage_updated">Last Updated:</string>
|
||||||
|
<string name="blogs_rss_remove_feed">Remove Feed</string>
|
||||||
|
<string name="blogs_rss_remove_feed_dialog_message">Are you sure you want to remove this feed and all its posts?\nAny posts you have shared will not be removed from other people\'s devices.</string>
|
||||||
|
<string name="blogs_rss_remove_feed_ok">Remove Feed</string>
|
||||||
<string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string>
|
<string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string>
|
||||||
<string name="blogs_rss_feeds_manage_empty_state">You haven\'t imported any RSS feeds.\n\nWhy don\'t you click the plus in the top right screen corner to add your first?</string>
|
<string name="blogs_rss_feeds_manage_empty_state">You haven\'t imported any RSS feeds.\n\nWhy don\'t you click the plus in the top right screen corner to add your first?</string>
|
||||||
<string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string>
|
<string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
<style name="DiscussionLevelIndicator">
|
<style name="DiscussionLevelIndicator">
|
||||||
<item name="android:layout_marginLeft">4dp</item>
|
<item name="android:layout_marginLeft">4dp</item>
|
||||||
<item name="android:background">@color/divider</item>
|
<item name="android:background">@color/thread_indicator</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BriarTabLayout" parent="Widget.Design.TabLayout">
|
<style name="BriarTabLayout" parent="Widget.Design.TabLayout">
|
||||||
|
|||||||
@@ -13,16 +13,22 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
public class Blog extends BaseGroup implements Shareable {
|
public class Blog extends BaseGroup implements Shareable {
|
||||||
|
|
||||||
private final Author author;
|
private final Author author;
|
||||||
|
private final boolean rssFeed;
|
||||||
|
|
||||||
public Blog(Group group, Author author) {
|
public Blog(Group group, Author author, boolean rssFeed) {
|
||||||
super(group);
|
super(group);
|
||||||
this.author = author;
|
this.author = author;
|
||||||
|
this.rssFeed = rssFeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Author getAuthor() {
|
public Author getAuthor() {
|
||||||
return author;
|
return author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRssFeed() {
|
||||||
|
return rssFeed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
return o instanceof Blog && super.equals(o);
|
return o instanceof Blog && super.equals(o);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class BlogCommentHeader extends BlogPostHeader {
|
|||||||
Status authorStatus, boolean read) {
|
Status authorStatus, boolean read) {
|
||||||
|
|
||||||
super(type, groupId, id, parent.getId(), timestamp,
|
super(type, groupId, id, parent.getId(), timestamp,
|
||||||
timeReceived, author, authorStatus, read);
|
timeReceived, author, authorStatus, 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");
|
||||||
@@ -43,4 +43,11 @@ public class BlogCommentHeader extends BlogPostHeader {
|
|||||||
public BlogPostHeader getParent() {
|
public BlogPostHeader getParent() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BlogPostHeader getRootPost() {
|
||||||
|
if (parent instanceof BlogCommentHeader)
|
||||||
|
return ((BlogCommentHeader) parent).getRootPost();
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public interface BlogConstants {
|
|||||||
String KEY_AUTHOR_NAME = "name";
|
String KEY_AUTHOR_NAME = "name";
|
||||||
String KEY_PUBLIC_KEY = "publicKey";
|
String KEY_PUBLIC_KEY = "publicKey";
|
||||||
String KEY_AUTHOR = "author";
|
String KEY_AUTHOR = "author";
|
||||||
|
String KEY_RSS_FEED = "rssFeed";
|
||||||
String KEY_READ = "read";
|
String KEY_READ = "read";
|
||||||
String KEY_COMMENT = "comment";
|
String KEY_COMMENT = "comment";
|
||||||
String KEY_ORIGINAL_MSG_ID = "originalMessageId";
|
String KEY_ORIGINAL_MSG_ID = "originalMessageId";
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ public interface BlogFactory {
|
|||||||
*/
|
*/
|
||||||
Blog createBlog(Author author);
|
Blog createBlog(Author author);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a RSS feed blog for a given author.
|
||||||
|
*/
|
||||||
|
Blog createFeedBlog(Author author);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a blog with the given Group
|
* Parses a blog with the given Group
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ public interface BlogManager {
|
|||||||
*/
|
*/
|
||||||
void removeBlog(Blog b) throws DbException;
|
void removeBlog(Blog b) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and deletes a blog with the given {@link Transaction}.
|
||||||
|
*/
|
||||||
|
void removeBlog(Transaction txn, Blog b) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a local blog post.
|
* Stores a local blog post.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,21 +17,23 @@ public class BlogPostHeader extends PostHeader {
|
|||||||
private final MessageType type;
|
private final MessageType type;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final long timeReceived;
|
private final long timeReceived;
|
||||||
|
private final boolean rssFeed;
|
||||||
|
|
||||||
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 read) {
|
Author author, Status authorStatus, boolean rssFeed, boolean read) {
|
||||||
super(id, parentId, timestamp, author, authorStatus, read);
|
super(id, parentId, timestamp, author, authorStatus, read);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.timeReceived = timeReceived;
|
this.timeReceived = timeReceived;
|
||||||
|
this.rssFeed = rssFeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 read) {
|
Status authorStatus, boolean rssFeed, boolean read) {
|
||||||
this(type, groupId, id, null, timestamp, timeReceived, author,
|
this(type, groupId, id, null, timestamp, timeReceived, author,
|
||||||
authorStatus, read);
|
authorStatus, rssFeed, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageType getType() {
|
public MessageType getType() {
|
||||||
@@ -45,4 +47,9 @@ public class BlogPostHeader extends PostHeader {
|
|||||||
public long getTimeReceived() {
|
public long getTimeReceived() {
|
||||||
return timeReceived;
|
return timeReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRssFeed() {
|
||||||
|
return rssFeed;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,18 @@ public abstract class BaseMessageHeader {
|
|||||||
private final MessageId id;
|
private final MessageId id;
|
||||||
private final GroupId groupId;
|
private final GroupId groupId;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private final boolean local, read, sent, seen;
|
private final boolean local, sent, seen, read;
|
||||||
|
|
||||||
public BaseMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
public BaseMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
||||||
boolean local, boolean read, boolean sent, boolean seen) {
|
boolean local, boolean sent, boolean seen, boolean read) {
|
||||||
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.local = local;
|
this.local = local;
|
||||||
this.read = read;
|
|
||||||
this.sent = sent;
|
this.sent = sent;
|
||||||
this.seen = seen;
|
this.seen = seen;
|
||||||
|
this.read = read;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getId() {
|
public MessageId getId() {
|
||||||
@@ -43,10 +43,6 @@ public abstract class BaseMessageHeader {
|
|||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRead() {
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSent() {
|
public boolean isSent() {
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
@@ -55,4 +51,8 @@ public abstract class BaseMessageHeader {
|
|||||||
return seen;
|
return seen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRead() {
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,31 @@
|
|||||||
package org.briarproject.briar.api.feed;
|
package org.briarproject.briar.api.feed;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
|
||||||
import org.briarproject.bramble.api.data.BdfEntry;
|
|
||||||
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.blog.Blog;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_BLOG_GROUP_ID;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_ADDED;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_AUTHOR;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_DESC;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_LAST_ENTRY;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_TITLE;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_UPDATED;
|
|
||||||
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_URL;
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class Feed {
|
public class Feed {
|
||||||
|
|
||||||
private final String url;
|
private final String url;
|
||||||
private final GroupId blogId;
|
private final Blog blog;
|
||||||
|
private final LocalAuthor localAuthor;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String title, description, author;
|
private final String description, author;
|
||||||
private final long added, updated, lastEntryTime;
|
private final long added, updated, lastEntryTime;
|
||||||
|
|
||||||
public Feed(String url, GroupId blogId, @Nullable String title,
|
public Feed(String url, Blog blog, LocalAuthor localAuthor,
|
||||||
@Nullable String description, @Nullable String author,
|
@Nullable String description, @Nullable String author, long added,
|
||||||
long added, long updated, long lastEntryTime) {
|
long updated, long lastEntryTime) {
|
||||||
|
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.blogId = blogId;
|
this.blog = blog;
|
||||||
this.title = title;
|
this.localAuthor = localAuthor;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.added = added;
|
this.added = added;
|
||||||
@@ -42,13 +33,13 @@ public class Feed {
|
|||||||
this.lastEntryTime = lastEntryTime;
|
this.lastEntryTime = lastEntryTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Feed(String url, GroupId blogId, @Nullable String title,
|
public Feed(String url, Blog blog, LocalAuthor localAuthor,
|
||||||
@Nullable String description, @Nullable String author, long added) {
|
@Nullable String description, @Nullable String author, long added) {
|
||||||
this(url, blogId, title, description, author, added, 0L, 0L);
|
this(url, blog, localAuthor, description, author, added, 0L, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Feed(String url, GroupId blogId, long added) {
|
public Feed(String url, Blog blog, LocalAuthor localAuthor, long added) {
|
||||||
this(url, blogId, null, null, null, added, 0L, 0L);
|
this(url, blog, localAuthor, null, null, added, 0L, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
@@ -56,39 +47,19 @@ public class Feed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GroupId getBlogId() {
|
public GroupId getBlogId() {
|
||||||
return blogId;
|
return blog.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BdfDictionary toBdfDictionary() {
|
public Blog getBlog() {
|
||||||
BdfDictionary d = BdfDictionary.of(
|
return blog;
|
||||||
new BdfEntry(KEY_FEED_URL, url),
|
|
||||||
new BdfEntry(KEY_BLOG_GROUP_ID, blogId.getBytes()),
|
|
||||||
new BdfEntry(KEY_FEED_ADDED, added),
|
|
||||||
new BdfEntry(KEY_FEED_UPDATED, updated),
|
|
||||||
new BdfEntry(KEY_FEED_LAST_ENTRY, lastEntryTime)
|
|
||||||
);
|
|
||||||
if (title != null) d.put(KEY_FEED_TITLE, title);
|
|
||||||
if (description != null) d.put(KEY_FEED_DESC, description);
|
|
||||||
if (author != null) d.put(KEY_FEED_AUTHOR, author);
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Feed from(BdfDictionary d) throws FormatException {
|
public LocalAuthor getLocalAuthor() {
|
||||||
String url = d.getString(KEY_FEED_URL);
|
return localAuthor;
|
||||||
GroupId blogId = new GroupId(d.getRaw(KEY_BLOG_GROUP_ID));
|
|
||||||
String title = d.getOptionalString(KEY_FEED_TITLE);
|
|
||||||
String desc = d.getOptionalString(KEY_FEED_DESC);
|
|
||||||
String author = d.getOptionalString(KEY_FEED_AUTHOR);
|
|
||||||
long added = d.getLong(KEY_FEED_ADDED, 0L);
|
|
||||||
long updated = d.getLong(KEY_FEED_UPDATED, 0L);
|
|
||||||
long lastEntryTime = d.getLong(KEY_FEED_LAST_ENTRY, 0L);
|
|
||||||
return new Feed(url, blogId, title, desc, author, added, updated,
|
|
||||||
lastEntryTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return title;
|
return blog.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -118,20 +89,9 @@ public class Feed {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o instanceof Feed) {
|
if (o instanceof Feed) {
|
||||||
Feed f = (Feed) o;
|
Feed f = (Feed) o;
|
||||||
return url.equals(f.url) && blogId.equals(f.getBlogId()) &&
|
return blog.equals(f.blog);
|
||||||
equalsWithNull(title, f.getTitle()) &&
|
|
||||||
equalsWithNull(description, f.getDescription()) &&
|
|
||||||
equalsWithNull(author, f.getAuthor()) &&
|
|
||||||
added == f.getAdded() &&
|
|
||||||
updated == f.getUpdated() &&
|
|
||||||
lastEntryTime == f.getLastEntryTime();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean equalsWithNull(@Nullable Object a, @Nullable Object b) {
|
|
||||||
if (a == b) return true;
|
|
||||||
if (a == null || b == null) return false;
|
|
||||||
return a.equals(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ public interface FeedConstants {
|
|||||||
// group metadata keys
|
// group metadata keys
|
||||||
String KEY_FEEDS = "feeds";
|
String KEY_FEEDS = "feeds";
|
||||||
String KEY_FEED_URL = "feedURL";
|
String KEY_FEED_URL = "feedURL";
|
||||||
String KEY_BLOG_GROUP_ID = "blogGroupId";
|
String KEY_BLOG_TITLE = "blogTitle";
|
||||||
String KEY_FEED_TITLE = "feedTitle";
|
String KEY_PUBLIC_KEY = "publicKey";
|
||||||
|
String KEY_PRIVATE_KEY = "privateKey";
|
||||||
String KEY_FEED_DESC = "feedDesc";
|
String KEY_FEED_DESC = "feedDesc";
|
||||||
String KEY_FEED_AUTHOR = "feedAuthor";
|
String KEY_FEED_AUTHOR = "feedAuthor";
|
||||||
String KEY_FEED_ADDED = "feedAdded";
|
String KEY_FEED_ADDED = "feedAdded";
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.api.feed;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -17,14 +16,14 @@ public interface FeedManager {
|
|||||||
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed");
|
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.feed");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an RSS feed.
|
* Adds an RSS feed as a new dedicated blog.
|
||||||
*/
|
*/
|
||||||
void addFeed(String url, GroupId g) throws DbException, IOException;
|
void addFeed(String url) throws DbException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an RSS feed.
|
* Removes an RSS feed.
|
||||||
*/
|
*/
|
||||||
void removeFeed(String url) throws DbException;
|
void removeFeed(Feed feed) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all added RSS feeds
|
* Returns a list of all added RSS feeds
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class IntroductionMessage extends BaseMessageHeader {
|
|||||||
GroupId groupId, int role, long time, boolean local, boolean sent,
|
GroupId groupId, int role, long time, boolean local, boolean sent,
|
||||||
boolean seen, boolean read) {
|
boolean seen, boolean read) {
|
||||||
|
|
||||||
super(messageId, groupId, time, local, read, sent, seen);
|
super(messageId, groupId, time, local, sent, seen, read);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class PrivateMessageHeader extends BaseMessageHeader {
|
|||||||
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
public PrivateMessageHeader(MessageId id, GroupId groupId, long timestamp,
|
||||||
boolean local, boolean read, boolean sent, boolean seen) {
|
boolean local, boolean read, boolean sent, boolean seen) {
|
||||||
|
|
||||||
super(id, groupId, timestamp, local, read, sent, seen);
|
super(id, groupId, timestamp, local, sent, seen, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class InvitationMessage extends BaseMessageHeader {
|
|||||||
boolean local, boolean sent, boolean seen, boolean read,
|
boolean local, boolean sent, boolean seen, boolean read,
|
||||||
SessionId sessionId, ContactId contactId) {
|
SessionId sessionId, ContactId contactId) {
|
||||||
|
|
||||||
super(id, groupId, time, local, read, sent, seen);
|
super(id, groupId, time, local, sent, seen, read);
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.contactId = contactId;
|
this.contactId = contactId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar;
|
|||||||
|
|
||||||
import org.briarproject.briar.blog.BlogModule;
|
import org.briarproject.briar.blog.BlogModule;
|
||||||
import org.briarproject.briar.client.BriarClientModule;
|
import org.briarproject.briar.client.BriarClientModule;
|
||||||
|
import org.briarproject.briar.feed.DnsModule;
|
||||||
import org.briarproject.briar.feed.FeedModule;
|
import org.briarproject.briar.feed.FeedModule;
|
||||||
import org.briarproject.briar.forum.ForumModule;
|
import org.briarproject.briar.forum.ForumModule;
|
||||||
import org.briarproject.briar.introduction.IntroductionModule;
|
import org.briarproject.briar.introduction.IntroductionModule;
|
||||||
@@ -16,6 +17,7 @@ import dagger.Module;
|
|||||||
BlogModule.class,
|
BlogModule.class,
|
||||||
BriarClientModule.class,
|
BriarClientModule.class,
|
||||||
FeedModule.class,
|
FeedModule.class,
|
||||||
|
DnsModule.class,
|
||||||
ForumModule.class,
|
ForumModule.class,
|
||||||
GroupInvitationModule.class,
|
GroupInvitationModule.class,
|
||||||
IntroductionModule.class,
|
IntroductionModule.class,
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import org.briarproject.briar.api.blog.BlogFactory;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
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.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class BlogFactoryImpl implements BlogFactory {
|
class BlogFactoryImpl implements BlogFactory {
|
||||||
@@ -33,28 +36,45 @@ class BlogFactoryImpl implements BlogFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Blog createBlog(Author a) {
|
public Blog createBlog(Author a) {
|
||||||
|
return createBlog(a, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Blog createFeedBlog(Author a) {
|
||||||
|
return createBlog(a, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Blog createBlog(Author a, boolean rssFeed) {
|
||||||
try {
|
try {
|
||||||
BdfList blog = BdfList.of(
|
BdfList blog = BdfList.of(
|
||||||
a.getName(),
|
a.getName(),
|
||||||
a.getPublicKey()
|
a.getPublicKey(),
|
||||||
|
rssFeed
|
||||||
);
|
);
|
||||||
byte[] descriptor = clientHelper.toByteArray(blog);
|
byte[] descriptor = clientHelper.toByteArray(blog);
|
||||||
Group g = groupFactory
|
Group g = groupFactory
|
||||||
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
||||||
return new Blog(g, a);
|
return new Blog(g, a, rssFeed);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Blog parseBlog(Group g) throws FormatException {
|
public Blog parseBlog(Group group) throws FormatException {
|
||||||
byte[] descriptor = g.getDescriptor();
|
byte[] descriptor = group.getDescriptor();
|
||||||
// Author Name, Public Key
|
// Author name, public key, RSS feed
|
||||||
BdfList blog = clientHelper.toList(descriptor);
|
BdfList blog = clientHelper.toList(descriptor);
|
||||||
Author a =
|
String name = blog.getString(0);
|
||||||
authorFactory.createAuthor(blog.getString(0), blog.getRaw(1));
|
if (name.length() > MAX_AUTHOR_NAME_LENGTH)
|
||||||
return new Blog(g, a);
|
throw new IllegalArgumentException();
|
||||||
|
byte[] publicKey = blog.getRaw(1);
|
||||||
|
if (publicKey.length > MAX_PUBLIC_KEY_LENGTH)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
|
||||||
|
Author author = authorFactory.createAuthor(name, publicKey);
|
||||||
|
boolean rssFeed = blog.getBoolean(2);
|
||||||
|
return new Blog(group, author, rssFeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_
|
|||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
||||||
@@ -224,6 +225,11 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeBlog(Transaction txn, Blog b) throws DbException {
|
||||||
|
removeBlog(txn, b, false);
|
||||||
|
}
|
||||||
|
|
||||||
private void removeBlog(Transaction txn, Blog b, boolean forced)
|
private void removeBlog(Transaction txn, Blog b, boolean forced)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
if (!forced && !canBeRemoved(txn, b.getId()))
|
if (!forced && !canBeRemoved(txn, b.getId()))
|
||||||
@@ -248,15 +254,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
@Override
|
@Override
|
||||||
public void addLocalPost(Transaction txn, BlogPost p) throws DbException {
|
public void addLocalPost(Transaction txn, BlogPost p) throws DbException {
|
||||||
try {
|
try {
|
||||||
|
GroupId groupId = p.getMessage().getGroupId();
|
||||||
|
Blog b = getBlog(txn, groupId);
|
||||||
|
|
||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(KEY_TYPE, POST.getInt());
|
meta.put(KEY_TYPE, POST.getInt());
|
||||||
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
|
meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
|
||||||
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
|
meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
|
||||||
meta.put(KEY_READ, true);
|
meta.put(KEY_READ, true);
|
||||||
|
meta.put(KEY_RSS_FEED, b.isRssFeed());
|
||||||
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
|
clientHelper.addLocalMessage(txn, p.getMessage(), meta, true);
|
||||||
|
|
||||||
// broadcast event about new post
|
// broadcast event about new post
|
||||||
GroupId groupId = p.getMessage().getGroupId();
|
|
||||||
MessageId postId = p.getMessage().getId();
|
MessageId postId = p.getMessage().getId();
|
||||||
BlogPostHeader h =
|
BlogPostHeader h =
|
||||||
getPostHeaderFromMetadata(txn, groupId, postId, meta);
|
getPostHeaderFromMetadata(txn, groupId, postId, meta);
|
||||||
@@ -345,6 +354,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
wMessage = blogPostFactory
|
wMessage = blogPostFactory
|
||||||
.wrapPost(groupId, wDescriptor, wTimestamp, body);
|
.wrapPost(groupId, wDescriptor, wTimestamp, body);
|
||||||
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
|
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
|
||||||
|
meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed());
|
||||||
} else if (type == COMMENT) {
|
} else if (type == COMMENT) {
|
||||||
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
|
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
|
||||||
byte[] wDescriptor = wGroup.getDescriptor();
|
byte[] wDescriptor = wGroup.getDescriptor();
|
||||||
@@ -363,6 +373,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
// Re-wrap wrapped post without adding another wrapping layer
|
// Re-wrap wrapped post without adding another wrapping layer
|
||||||
wMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
|
wMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
|
||||||
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
|
meta.put(KEY_TYPE, WRAPPED_POST.getInt());
|
||||||
|
meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed());
|
||||||
} else if (type == WRAPPED_COMMENT) {
|
} else if (type == WRAPPED_COMMENT) {
|
||||||
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
|
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
|
||||||
MessageId wrappedId =
|
MessageId wrappedId =
|
||||||
@@ -593,8 +604,11 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
String name = d.getString(KEY_AUTHOR_NAME);
|
String name = d.getString(KEY_AUTHOR_NAME);
|
||||||
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
||||||
Author author = new Author(authorId, name, publicKey);
|
Author author = new Author(authorId, name, publicKey);
|
||||||
|
boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false);
|
||||||
Status authorStatus;
|
Status authorStatus;
|
||||||
if (authorStatuses.containsKey(authorId)) {
|
if (isFeedPost) {
|
||||||
|
authorStatus = Status.NONE;
|
||||||
|
} else if (authorStatuses.containsKey(authorId)) {
|
||||||
authorStatus = authorStatuses.get(authorId);
|
authorStatus = authorStatuses.get(authorId);
|
||||||
} else {
|
} else {
|
||||||
authorStatus = identityManager.getAuthorStatus(txn, authorId);
|
authorStatus = identityManager.getAuthorStatus(txn, authorId);
|
||||||
@@ -611,7 +625,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
|||||||
timestamp, timeReceived, author, authorStatus, read);
|
timestamp, timeReceived, author, authorStatus, read);
|
||||||
} else {
|
} else {
|
||||||
return new BlogPostHeader(type, groupId, id, timestamp,
|
return new BlogPostHeader(type, groupId, id, timestamp,
|
||||||
timeReceived, author, authorStatus, read);
|
timeReceived, author, authorStatus, isFeedPost, read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_
|
|||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
||||||
@@ -123,6 +124,7 @@ class BlogPostValidator extends BdfMessageValidator {
|
|||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
|
meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
|
||||||
meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
|
meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
|
||||||
|
meta.put(KEY_RSS_FEED, b.isRssFeed());
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +201,7 @@ class BlogPostValidator extends BdfMessageValidator {
|
|||||||
// Get and Validate the Wrapped Message
|
// Get and Validate the Wrapped Message
|
||||||
Group wGroup = groupFactory
|
Group wGroup = groupFactory
|
||||||
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
|
||||||
|
Blog wBlog = blogFactory.parseBlog(wGroup);
|
||||||
BdfList wBodyList = BdfList.of(POST.getInt(), content, signature);
|
BdfList wBodyList = BdfList.of(POST.getInt(), content, signature);
|
||||||
byte[] wBody = clientHelper.toByteArray(wBodyList);
|
byte[] wBody = clientHelper.toByteArray(wBodyList);
|
||||||
Message wMessage =
|
Message wMessage =
|
||||||
@@ -211,6 +214,7 @@ class BlogPostValidator extends BdfMessageValidator {
|
|||||||
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
|
meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
|
||||||
meta.put(KEY_TIMESTAMP, wTimestamp);
|
meta.put(KEY_TIMESTAMP, wTimestamp);
|
||||||
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
|
meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
|
||||||
|
meta.put(KEY_RSS_FEED, wBlog.isRssFeed());
|
||||||
return new BdfMessageContext(meta);
|
return new BdfMessageContext(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||||
import static org.briarproject.briar.api.client.QueueMessage.MAX_QUEUE_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.briar.api.client.QueueMessage.MAX_QUEUE_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||||
|
|
||||||
@@ -39,11 +40,14 @@ class QueueMessageFactoryImpl implements QueueMessageFactory {
|
|||||||
ByteUtils.writeUint64(queuePosition, raw, MESSAGE_HEADER_LENGTH);
|
ByteUtils.writeUint64(queuePosition, raw, MESSAGE_HEADER_LENGTH);
|
||||||
System.arraycopy(body, 0, raw, QUEUE_MESSAGE_HEADER_LENGTH,
|
System.arraycopy(body, 0, raw, QUEUE_MESSAGE_HEADER_LENGTH,
|
||||||
body.length);
|
body.length);
|
||||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
byte[] timeBytes = new byte[INT_64_BYTES];
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
|
byte[] bodyBytes = new byte[body.length + INT_64_BYTES];
|
||||||
|
System.arraycopy(raw, MESSAGE_HEADER_LENGTH, bodyBytes, 0,
|
||||||
|
body.length + INT_64_BYTES);
|
||||||
MessageId id = new MessageId(
|
MessageId id = new MessageId(
|
||||||
crypto.hash(MessageId.LABEL, groupId.getBytes(), timeBytes,
|
crypto.hash(MessageId.LABEL, groupId.getBytes(), timeBytes,
|
||||||
body));
|
bodyBytes));
|
||||||
return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
|
return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import okhttp3.Dns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a dedicated module, so it can be replaced for testing.
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public class DnsModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
Dns provideDns(NoDns noDns) {
|
||||||
|
return noDns;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import com.rometools.rome.feed.synd.SyndFeed;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.briar.api.feed.Feed;
|
||||||
|
|
||||||
|
interface FeedFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new feed based on the feed url
|
||||||
|
* and the metadata of an existing {@link SyndFeed}.
|
||||||
|
*/
|
||||||
|
Feed createFeed(String url, SyndFeed feed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new updated feed, based on the given existing feed,
|
||||||
|
* new metadata from the given {@link SyndFeed}
|
||||||
|
* and the time of the last feed entry.
|
||||||
|
*/
|
||||||
|
Feed createFeed(Feed feed, SyndFeed f, long lastEntryTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-serializes a {@link BdfDictionary} into a {@link Feed}.
|
||||||
|
*/
|
||||||
|
Feed createFeed(BdfDictionary d) throws FormatException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes a {@link Feed} into a {@link BdfDictionary}.
|
||||||
|
*/
|
||||||
|
BdfDictionary feedToBdfDictionary(Feed feed);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import com.rometools.rome.feed.synd.SyndFeed;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||||
|
import org.briarproject.bramble.api.data.BdfEntry;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
|
import org.briarproject.briar.api.blog.BlogFactory;
|
||||||
|
import org.briarproject.briar.api.feed.Feed;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_BLOG_TITLE;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_ADDED;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_AUTHOR;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_DESC;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_LAST_ENTRY;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_UPDATED;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_URL;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_PRIVATE_KEY;
|
||||||
|
import static org.briarproject.briar.api.feed.FeedConstants.KEY_PUBLIC_KEY;
|
||||||
|
|
||||||
|
class FeedFactoryImpl implements FeedFactory {
|
||||||
|
|
||||||
|
private final CryptoComponent cryptoComponent;
|
||||||
|
private final AuthorFactory authorFactory;
|
||||||
|
private final BlogFactory blogFactory;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
FeedFactoryImpl(CryptoComponent cryptoComponent,
|
||||||
|
AuthorFactory authorFactory, BlogFactory blogFactory, Clock clock) {
|
||||||
|
this.cryptoComponent = cryptoComponent;
|
||||||
|
this.authorFactory = authorFactory;
|
||||||
|
this.blogFactory = blogFactory;
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Feed createFeed(String url, SyndFeed syndFeed) {
|
||||||
|
String title = syndFeed.getTitle();
|
||||||
|
if (title == null) title = "RSS";
|
||||||
|
title = StringUtils.truncateUtf8(title, MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
|
||||||
|
KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
|
||||||
|
LocalAuthor localAuthor = authorFactory
|
||||||
|
.createLocalAuthor(title,
|
||||||
|
keyPair.getPublic().getEncoded(),
|
||||||
|
keyPair.getPrivate().getEncoded());
|
||||||
|
Blog blog = blogFactory.createFeedBlog(localAuthor);
|
||||||
|
long added = clock.currentTimeMillis();
|
||||||
|
|
||||||
|
return new Feed(url, blog, localAuthor, added);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Feed createFeed(Feed feed, SyndFeed f, long lastEntryTime) {
|
||||||
|
long updated = clock.currentTimeMillis();
|
||||||
|
return new Feed(feed.getUrl(), feed.getBlog(), feed.getLocalAuthor(),
|
||||||
|
f.getDescription(), f.getAuthor(), feed.getAdded(), updated,
|
||||||
|
lastEntryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Feed createFeed(BdfDictionary d) throws FormatException {
|
||||||
|
String url = d.getString(KEY_FEED_URL);
|
||||||
|
|
||||||
|
String blogTitle = d.getString(KEY_BLOG_TITLE);
|
||||||
|
byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
|
||||||
|
byte[] privateKey = d.getRaw(KEY_PRIVATE_KEY);
|
||||||
|
LocalAuthor localAuthor = authorFactory
|
||||||
|
.createLocalAuthor(blogTitle, publicKey, privateKey);
|
||||||
|
Blog blog = blogFactory.createFeedBlog(localAuthor);
|
||||||
|
|
||||||
|
String desc = d.getOptionalString(KEY_FEED_DESC);
|
||||||
|
String author = d.getOptionalString(KEY_FEED_AUTHOR);
|
||||||
|
long added = d.getLong(KEY_FEED_ADDED, 0L);
|
||||||
|
long updated = d.getLong(KEY_FEED_UPDATED, 0L);
|
||||||
|
long lastEntryTime = d.getLong(KEY_FEED_LAST_ENTRY, 0L);
|
||||||
|
|
||||||
|
return new Feed(url, blog, localAuthor, desc, author, added,
|
||||||
|
updated, lastEntryTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfDictionary feedToBdfDictionary(Feed feed) {
|
||||||
|
BdfDictionary d = BdfDictionary.of(
|
||||||
|
new BdfEntry(KEY_FEED_URL, feed.getUrl()),
|
||||||
|
new BdfEntry(KEY_BLOG_TITLE, feed.getLocalAuthor().getName()),
|
||||||
|
new BdfEntry(KEY_PUBLIC_KEY,
|
||||||
|
feed.getLocalAuthor().getPublicKey()),
|
||||||
|
new BdfEntry(KEY_PRIVATE_KEY,
|
||||||
|
feed.getLocalAuthor().getPrivateKey()),
|
||||||
|
new BdfEntry(KEY_FEED_ADDED, feed.getAdded()),
|
||||||
|
new BdfEntry(KEY_FEED_UPDATED, feed.getUpdated()),
|
||||||
|
new BdfEntry(KEY_FEED_LAST_ENTRY, feed.getLastEntryTime())
|
||||||
|
);
|
||||||
|
if (feed.getDescription() != null)
|
||||||
|
d.put(KEY_FEED_DESC, feed.getDescription());
|
||||||
|
if (feed.getAuthor() != null) d.put(KEY_FEED_AUTHOR, feed.getAuthor());
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ 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.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -31,6 +30,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPost;
|
import org.briarproject.briar.api.blog.BlogPost;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
@@ -39,8 +39,6 @@ import org.briarproject.briar.api.feed.FeedManager;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -75,12 +73,12 @@ import static org.briarproject.briar.util.HtmlUtils.clean;
|
|||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class FeedManagerImpl implements FeedManager, Client, EventListener {
|
class FeedManagerImpl implements FeedManager, Client, EventListener,
|
||||||
|
BlogManager.RemoveBlogHook {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(FeedManagerImpl.class.getName());
|
Logger.getLogger(FeedManagerImpl.class.getName());
|
||||||
|
|
||||||
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
|
||||||
private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
|
private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
|
||||||
|
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
@@ -88,31 +86,33 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final ContactGroupFactory contactGroupFactory;
|
private final ContactGroupFactory contactGroupFactory;
|
||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
private final IdentityManager identityManager;
|
|
||||||
private final BlogManager blogManager;
|
private final BlogManager blogManager;
|
||||||
private final BlogPostFactory blogPostFactory;
|
private final BlogPostFactory blogPostFactory;
|
||||||
|
private final FeedFactory feedFactory;
|
||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
private final Dns noDnsLookups;
|
||||||
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
|
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler,
|
FeedManagerImpl(@Scheduler ScheduledExecutorService scheduler,
|
||||||
@IoExecutor Executor ioExecutor, DatabaseComponent db,
|
@IoExecutor Executor ioExecutor, DatabaseComponent db,
|
||||||
ContactGroupFactory contactGroupFactory, ClientHelper clientHelper,
|
ContactGroupFactory contactGroupFactory, ClientHelper clientHelper,
|
||||||
IdentityManager identityManager, BlogManager blogManager,
|
BlogManager blogManager, BlogPostFactory blogPostFactory,
|
||||||
BlogPostFactory blogPostFactory, SocketFactory torSocketFactory,
|
FeedFactory feedFactory, SocketFactory torSocketFactory,
|
||||||
Clock clock) {
|
Clock clock, Dns noDnsLookups) {
|
||||||
|
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.clientHelper = clientHelper;
|
this.clientHelper = clientHelper;
|
||||||
this.identityManager = identityManager;
|
|
||||||
this.blogManager = blogManager;
|
this.blogManager = blogManager;
|
||||||
this.blogPostFactory = blogPostFactory;
|
this.blogPostFactory = blogPostFactory;
|
||||||
|
this.feedFactory = feedFactory;
|
||||||
this.torSocketFactory = torSocketFactory;
|
this.torSocketFactory = torSocketFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
this.noDnsLookups = noDnsLookups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -158,21 +158,21 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFeed(String url, GroupId g) throws DbException, IOException {
|
public void addFeed(String url) throws DbException, IOException {
|
||||||
LOG.info("Adding new RSS feed...");
|
// fetch syndication feed to get its metadata
|
||||||
|
SyndFeed f;
|
||||||
// TODO check for existing feed?
|
|
||||||
// fetch feed to get its metadata
|
|
||||||
Feed feed = new Feed(url, g, clock.currentTimeMillis());
|
|
||||||
try {
|
try {
|
||||||
feed = fetchFeed(feed, false);
|
f = fetchSyndFeed(url);
|
||||||
} catch (FeedException e) {
|
} catch (FeedException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// store feed
|
Feed feed = feedFactory.createFeed(url, f);
|
||||||
|
|
||||||
|
// store feed and new blog
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
|
blogManager.addBlog(txn, feed.getBlog());
|
||||||
List<Feed> feeds = getFeeds(txn);
|
List<Feed> feeds = getFeeds(txn);
|
||||||
feeds.add(feed);
|
feeds.add(feed);
|
||||||
storeFeeds(txn, feeds);
|
storeFeeds(txn, feeds);
|
||||||
@@ -181,10 +181,10 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch feed again, post entries this time
|
// fetch feed again and post entries
|
||||||
Feed updatedFeed;
|
Feed updatedFeed;
|
||||||
try {
|
try {
|
||||||
updatedFeed = fetchFeed(feed, true);
|
updatedFeed = fetchFeed(feed);
|
||||||
} catch (FeedException e) {
|
} catch (FeedException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
@@ -203,27 +203,35 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeFeed(String url) throws DbException {
|
public void removeFeed(Feed feed) throws DbException {
|
||||||
LOG.info("Removing RSS feed...");
|
LOG.info("Removing RSS feed...");
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
List<Feed> feeds = getFeeds(txn);
|
// this will call removingBlog() where the feed itself gets removed
|
||||||
boolean found = false;
|
blogManager.removeBlog(txn, feed.getBlog());
|
||||||
for (Feed feed : feeds) {
|
|
||||||
if (feed.getUrl().equals(url)) {
|
|
||||||
found = true;
|
|
||||||
feeds.remove(feed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) throw new DbException();
|
|
||||||
storeFeeds(txn, feeds);
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removingBlog(Transaction txn, Blog b) throws DbException {
|
||||||
|
if (!b.isRssFeed()) return;
|
||||||
|
|
||||||
|
// delete blog's RSS feed if we have it
|
||||||
|
boolean found = false;
|
||||||
|
List<Feed> feeds = getFeeds(txn);
|
||||||
|
for (Feed f : feeds) {
|
||||||
|
if (f.getBlogId().equals(b.getId())) {
|
||||||
|
found = true;
|
||||||
|
feeds.remove(f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) storeFeeds(txn, feeds);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Feed> getFeeds() throws DbException {
|
public List<Feed> getFeeds() throws DbException {
|
||||||
List<Feed> feeds;
|
List<Feed> feeds;
|
||||||
@@ -246,7 +254,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
for (Object object : d.getList(KEY_FEEDS)) {
|
for (Object object : d.getList(KEY_FEEDS)) {
|
||||||
if (!(object instanceof BdfDictionary))
|
if (!(object instanceof BdfDictionary))
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
feeds.add(Feed.from((BdfDictionary) object));
|
feeds.add(feedFactory.createFeed((BdfDictionary) object));
|
||||||
}
|
}
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -259,7 +267,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
|
|
||||||
BdfList feedList = new BdfList();
|
BdfList feedList = new BdfList();
|
||||||
for (Feed feed : feeds) {
|
for (Feed feed : feeds) {
|
||||||
feedList.add(feed.toBdfDictionary());
|
feedList.add(feedFactory.feedToBdfDictionary(feed));
|
||||||
}
|
}
|
||||||
BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
BdfDictionary gm = BdfDictionary.of(new BdfEntry(KEY_FEEDS, feedList));
|
||||||
try {
|
try {
|
||||||
@@ -300,7 +308,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
List<Feed> newFeeds = new ArrayList<Feed>(feeds.size());
|
List<Feed> newFeeds = new ArrayList<Feed>(feeds.size());
|
||||||
for (Feed feed : feeds) {
|
for (Feed feed : feeds) {
|
||||||
try {
|
try {
|
||||||
newFeeds.add(fetchFeed(feed, true));
|
newFeeds.add(fetchFeed(feed));
|
||||||
} catch (FeedException e) {
|
} catch (FeedException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
LOG.log(WARNING, e.toString(), e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
@@ -323,49 +331,52 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
LOG.info("Done updating RSS feeds");
|
LOG.info("Done updating RSS feeds");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Feed fetchFeed(Feed feed, boolean post)
|
private SyndFeed fetchSyndFeed(String url)
|
||||||
throws FeedException, IOException, DbException {
|
throws FeedException, IOException {
|
||||||
String title, description, author;
|
// fetch feed
|
||||||
long updated = clock.currentTimeMillis();
|
SyndFeed f = getSyndFeed(getFeedInputStream(url));
|
||||||
long lastEntryTime = feed.getLastEntryTime();
|
|
||||||
|
|
||||||
SyndFeed f = getSyndFeed(getFeedInputStream(feed.getUrl()));
|
|
||||||
title = StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle();
|
|
||||||
if (title != null) title = clean(title, STRIP_ALL);
|
|
||||||
description = StringUtils.isNullOrEmpty(f.getDescription()) ? null :
|
|
||||||
f.getDescription();
|
|
||||||
if (description != null) description = clean(description, STRIP_ALL);
|
|
||||||
author =
|
|
||||||
StringUtils.isNullOrEmpty(f.getAuthor()) ? null : f.getAuthor();
|
|
||||||
if (author != null) author = clean(author, STRIP_ALL);
|
|
||||||
|
|
||||||
if (f.getEntries().size() == 0)
|
if (f.getEntries().size() == 0)
|
||||||
throw new FeedException("Feed has no entries");
|
throw new FeedException("Feed has no entries");
|
||||||
|
|
||||||
|
// clean title
|
||||||
|
String title =
|
||||||
|
StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle();
|
||||||
|
if (title != null) title = clean(title, STRIP_ALL);
|
||||||
|
f.setTitle(title);
|
||||||
|
|
||||||
|
// clean description
|
||||||
|
String description =
|
||||||
|
StringUtils.isNullOrEmpty(f.getDescription()) ? null :
|
||||||
|
f.getDescription();
|
||||||
|
if (description != null) description = clean(description, STRIP_ALL);
|
||||||
|
f.setDescription(description);
|
||||||
|
|
||||||
|
// clean author
|
||||||
|
String author =
|
||||||
|
StringUtils.isNullOrEmpty(f.getAuthor()) ? null : f.getAuthor();
|
||||||
|
if (author != null) author = clean(author, STRIP_ALL);
|
||||||
|
f.setAuthor(author);
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Feed fetchFeed(Feed feed)
|
||||||
|
throws FeedException, IOException, DbException {
|
||||||
|
// fetch and clean feed
|
||||||
|
SyndFeed f = fetchSyndFeed(feed.getUrl());
|
||||||
|
|
||||||
// sort and add new entries
|
// sort and add new entries
|
||||||
if (post) {
|
long lastEntryTime = postFeedEntries(feed, f.getEntries());
|
||||||
lastEntryTime = postFeedEntries(feed, f.getEntries());
|
|
||||||
}
|
return feedFactory.createFeed(feed, f, lastEntryTime);
|
||||||
return new Feed(feed.getUrl(), feed.getBlogId(), title, description,
|
|
||||||
author, feed.getAdded(), updated, lastEntryTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream getFeedInputStream(String url) throws IOException {
|
private InputStream getFeedInputStream(String url) throws IOException {
|
||||||
// Don't make local DNS lookups
|
|
||||||
Dns noLookups = new Dns() {
|
|
||||||
@Override
|
|
||||||
public List<InetAddress> lookup(String hostname)
|
|
||||||
throws UnknownHostException {
|
|
||||||
InetAddress unspecified =
|
|
||||||
InetAddress.getByAddress(hostname, UNSPECIFIED_ADDRESS);
|
|
||||||
return Collections.singletonList(unspecified);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build HTTP Client
|
// Build HTTP Client
|
||||||
OkHttpClient client = new OkHttpClient.Builder()
|
OkHttpClient client = new OkHttpClient.Builder()
|
||||||
.socketFactory(torSocketFactory)
|
.socketFactory(torSocketFactory)
|
||||||
.dns(noLookups)
|
.dns(noDnsLookups) // Don't make local DNS lookups
|
||||||
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
|
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -422,9 +433,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
|
|
||||||
// build post body
|
// build post body
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
if (feed.getTitle() != null) {
|
b.append("<h3>").append(feed.getTitle()).append("</h3>");
|
||||||
b.append("<h3>").append(feed.getTitle()).append("</h3>");
|
|
||||||
}
|
|
||||||
if (!StringUtils.isNullOrEmpty(entry.getTitle())) {
|
if (!StringUtils.isNullOrEmpty(entry.getTitle())) {
|
||||||
b.append("<h1>").append(entry.getTitle()).append("</h1>");
|
b.append("<h1>").append(entry.getTitle()).append("</h1>");
|
||||||
}
|
}
|
||||||
@@ -457,13 +467,17 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
|
|||||||
|
|
||||||
// get other information for post
|
// get other information for post
|
||||||
GroupId groupId = feed.getBlogId();
|
GroupId groupId = feed.getBlogId();
|
||||||
long time = clock.currentTimeMillis();
|
long time, now = clock.currentTimeMillis();
|
||||||
|
Date date = entry.getUpdatedDate();
|
||||||
|
if (date == null) date = entry.getPublishedDate();
|
||||||
|
if (date == null) time = now;
|
||||||
|
else time = Math.max(0, Math.min(date.getTime(), now));
|
||||||
String body = getPostBody(b.toString());
|
String body = getPostBody(b.toString());
|
||||||
try {
|
try {
|
||||||
// create and store post
|
// create and store post
|
||||||
LocalAuthor author = identityManager.getLocalAuthor(txn);
|
LocalAuthor localAuthor = feed.getLocalAuthor();
|
||||||
BlogPost post = blogPostFactory
|
BlogPost post = blogPostFactory
|
||||||
.createBlogPost(groupId, time, null, author, body);
|
.createBlogPost(groupId, time, null, localAuthor, body);
|
||||||
blogManager.addLocalPost(txn, post);
|
blogManager.addLocalPost(txn, post);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.feed;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.feed.FeedManager;
|
import org.briarproject.briar.api.feed.FeedManager;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -21,11 +22,18 @@ public class FeedModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
FeedManager provideFeedManager(FeedManagerImpl feedManager,
|
FeedManager provideFeedManager(FeedManagerImpl feedManager,
|
||||||
LifecycleManager lifecycleManager, EventBus eventBus) {
|
LifecycleManager lifecycleManager, EventBus eventBus,
|
||||||
|
BlogManager blogManager) {
|
||||||
|
|
||||||
lifecycleManager.registerClient(feedManager);
|
lifecycleManager.registerClient(feedManager);
|
||||||
eventBus.addListener(feedManager);
|
eventBus.addListener(feedManager);
|
||||||
|
blogManager.registerRemoveBlogHook(feedManager);
|
||||||
return feedManager;
|
return feedManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
FeedFactory provideFeedFactory(FeedFactoryImpl feedFactory) {
|
||||||
|
return feedFactory;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import okhttp3.Dns;
|
||||||
|
|
||||||
|
class NoDns implements Dns {
|
||||||
|
|
||||||
|
private static final byte[] UNSPECIFIED_ADDRESS = new byte[4];
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public NoDns() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<InetAddress> lookup(String hostname)
|
||||||
|
throws UnknownHostException {
|
||||||
|
InetAddress unspecified =
|
||||||
|
InetAddress.getByAddress(hostname, UNSPECIFIED_ADDRESS);
|
||||||
|
return Collections.singletonList(unspecified);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,14 +39,20 @@ class BlogSharingValidator extends SharingValidator {
|
|||||||
@Override
|
@Override
|
||||||
protected GroupId validateDescriptor(BdfList descriptor)
|
protected GroupId validateDescriptor(BdfList descriptor)
|
||||||
throws FormatException {
|
throws FormatException {
|
||||||
checkSize(descriptor, 2);
|
checkSize(descriptor, 3);
|
||||||
String name = descriptor.getString(0);
|
String name = descriptor.getString(0);
|
||||||
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
|
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
|
||||||
byte[] publicKey = descriptor.getRaw(1);
|
byte[] publicKey = descriptor.getRaw(1);
|
||||||
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
boolean rssFeed = descriptor.getBoolean(2);
|
||||||
|
|
||||||
Author author = authorFactory.createAuthor(name, publicKey);
|
Author author = authorFactory.createAuthor(name, publicKey);
|
||||||
Blog blog = blogFactory.createBlog(author);
|
Blog blog;
|
||||||
|
if (rssFeed) {
|
||||||
|
blog = blogFactory.createFeedBlog(author);
|
||||||
|
} else {
|
||||||
|
blog = blogFactory.createBlog(author);
|
||||||
|
}
|
||||||
return blog.getId();
|
return blog.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
|
|||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
||||||
@@ -198,12 +199,17 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
new BdfEntry(KEY_TYPE, POST.getInt()),
|
new BdfEntry(KEY_TYPE, POST.getInt()),
|
||||||
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
|
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
|
||||||
new BdfEntry(KEY_AUTHOR, authorMeta),
|
new BdfEntry(KEY_AUTHOR, authorMeta),
|
||||||
new BdfEntry(KEY_READ, true)
|
new BdfEntry(KEY_READ, true),
|
||||||
|
new BdfEntry(KEY_RSS_FEED, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(false);
|
oneOf(db).startTransaction(false);
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
|
oneOf(db).getGroup(txn, blog1.getId());
|
||||||
|
will(returnValue(blog1.getGroup()));
|
||||||
|
oneOf(blogFactory).parseBlog(blog1.getGroup());
|
||||||
|
will(returnValue(blog1));
|
||||||
oneOf(clientHelper)
|
oneOf(clientHelper)
|
||||||
.addLocalMessage(txn, message, meta, true);
|
.addLocalMessage(txn, message, meta, true);
|
||||||
oneOf(identityManager)
|
oneOf(identityManager)
|
||||||
@@ -297,7 +303,7 @@ public class BlogManagerImplTest extends BriarTestCase {
|
|||||||
final LocalAuthor localAuthor =
|
final LocalAuthor localAuthor =
|
||||||
new LocalAuthor(authorId, "Author", publicKey, privateKey,
|
new LocalAuthor(authorId, "Author", publicKey, privateKey,
|
||||||
created);
|
created);
|
||||||
return new Blog(group, localAuthor);
|
return new Blog(group, localAuthor, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BdfDictionary authorToBdfDictionary(Author a) {
|
private BdfDictionary authorToBdfDictionary(Author a) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.briarproject.briar.blog;
|
package org.briarproject.briar.blog;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
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;
|
||||||
import org.briarproject.briar.api.blog.Blog;
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
@@ -20,6 +23,9 @@ import java.util.Iterator;
|
|||||||
|
|
||||||
import static junit.framework.Assert.assertFalse;
|
import static junit.framework.Assert.assertFalse;
|
||||||
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_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomString;
|
import static org.briarproject.bramble.test.TestUtils.getRandomString;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||||
@@ -32,7 +38,8 @@ public class BlogManagerIntegrationTest
|
|||||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||||
|
|
||||||
private BlogManager blogManager0, blogManager1;
|
private BlogManager blogManager0, blogManager1;
|
||||||
private Blog blog0, blog1;
|
private Blog blog0, blog1, rssBlog;
|
||||||
|
private LocalAuthor rssAuthor;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
@@ -44,12 +51,22 @@ public class BlogManagerIntegrationTest
|
|||||||
|
|
||||||
author0 = identityManager0.getLocalAuthor();
|
author0 = identityManager0.getLocalAuthor();
|
||||||
author1 = identityManager1.getLocalAuthor();
|
author1 = identityManager1.getLocalAuthor();
|
||||||
|
rssAuthor = c0.getAuthorFactory().createLocalAuthor(
|
||||||
|
getRandomString(MAX_AUTHOR_NAME_LENGTH),
|
||||||
|
getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||||
|
getRandomBytes(123));
|
||||||
|
|
||||||
blogManager0 = c0.getBlogManager();
|
blogManager0 = c0.getBlogManager();
|
||||||
blogManager1 = c1.getBlogManager();
|
blogManager1 = c1.getBlogManager();
|
||||||
|
|
||||||
blog0 = blogFactory.createBlog(author0);
|
blog0 = blogFactory.createBlog(author0);
|
||||||
blog1 = blogFactory.createBlog(author1);
|
blog1 = blogFactory.createBlog(author1);
|
||||||
|
|
||||||
|
rssBlog = blogFactory.createFeedBlog(rssAuthor);
|
||||||
|
Transaction txn = db0.startTransaction(false);
|
||||||
|
blogManager0.addBlog(txn, rssBlog);
|
||||||
|
db0.commitTransaction(txn);
|
||||||
|
db0.endTransaction(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,11 +91,12 @@ public class BlogManagerIntegrationTest
|
|||||||
@Test
|
@Test
|
||||||
public void testPersonalBlogInitialisation() throws Exception {
|
public void testPersonalBlogInitialisation() throws Exception {
|
||||||
Collection<Blog> blogs0 = blogManager0.getBlogs();
|
Collection<Blog> blogs0 = blogManager0.getBlogs();
|
||||||
assertEquals(3, blogs0.size());
|
assertEquals(4, blogs0.size());
|
||||||
Iterator<Blog> i0 = blogs0.iterator();
|
Iterator<Blog> i0 = blogs0.iterator();
|
||||||
assertEquals(author0, i0.next().getAuthor());
|
assertEquals(author0, i0.next().getAuthor());
|
||||||
assertEquals(author1, i0.next().getAuthor());
|
assertEquals(author1, i0.next().getAuthor());
|
||||||
assertEquals(author2, i0.next().getAuthor());
|
assertEquals(author2, i0.next().getAuthor());
|
||||||
|
assertEquals(rssAuthor, i0.next().getAuthor());
|
||||||
|
|
||||||
Collection<Blog> blogs1 = blogManager1.getBlogs();
|
Collection<Blog> blogs1 = blogManager1.getBlogs();
|
||||||
assertEquals(2, blogs1.size());
|
assertEquals(2, blogs1.size());
|
||||||
@@ -95,11 +113,14 @@ public class BlogManagerIntegrationTest
|
|||||||
assertEquals(blog0, blogManager1.getBlog(blog0.getId()));
|
assertEquals(blog0, blogManager1.getBlog(blog0.getId()));
|
||||||
assertEquals(blog1, blogManager0.getBlog(blog1.getId()));
|
assertEquals(blog1, blogManager0.getBlog(blog1.getId()));
|
||||||
assertEquals(blog1, blogManager1.getBlog(blog1.getId()));
|
assertEquals(blog1, blogManager1.getBlog(blog1.getId()));
|
||||||
|
assertEquals(rssBlog, blogManager0.getBlog(rssBlog.getId()));
|
||||||
|
|
||||||
assertEquals(1, blogManager0.getBlogs(author0).size());
|
assertEquals(1, blogManager0.getBlogs(author0).size());
|
||||||
assertEquals(1, blogManager1.getBlogs(author0).size());
|
assertEquals(1, blogManager1.getBlogs(author0).size());
|
||||||
assertEquals(1, blogManager0.getBlogs(author1).size());
|
assertEquals(1, blogManager0.getBlogs(author1).size());
|
||||||
assertEquals(1, blogManager1.getBlogs(author1).size());
|
assertEquals(1, blogManager1.getBlogs(author1).size());
|
||||||
|
assertEquals(1, blogManager0.getBlogs(rssAuthor).size());
|
||||||
|
assertEquals(0, blogManager1.getBlogs(rssAuthor).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -279,9 +300,8 @@ public class BlogManagerIntegrationTest
|
|||||||
assertEquals(POST, headers1.iterator().next().getType());
|
assertEquals(POST, headers1.iterator().next().getType());
|
||||||
|
|
||||||
// 1 reblogs that blog post
|
// 1 reblogs that blog post
|
||||||
blogManager1
|
blogManager1.addLocalComment(author1, blog1.getId(), null,
|
||||||
.addLocalComment(author1, blog1.getId(), null,
|
headers1.iterator().next());
|
||||||
headers1.iterator().next());
|
|
||||||
|
|
||||||
// sync comment over
|
// sync comment over
|
||||||
sync1To0(2, true);
|
sync1To0(2, true);
|
||||||
@@ -304,8 +324,7 @@ public class BlogManagerIntegrationTest
|
|||||||
sync0To1(3, true);
|
sync0To1(3, true);
|
||||||
|
|
||||||
// check that comment arrived
|
// check that comment arrived
|
||||||
headers1 =
|
headers1 = blogManager1.getPostHeaders(blog0.getId());
|
||||||
blogManager1.getPostHeaders(blog0.getId());
|
|
||||||
assertEquals(2, headers1.size());
|
assertEquals(2, headers1.size());
|
||||||
|
|
||||||
// get header of comment
|
// get header of comment
|
||||||
@@ -393,4 +412,63 @@ public class BlogManagerIntegrationTest
|
|||||||
assertEquals(2, headers0.size());
|
assertEquals(2, headers0.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFeedPost() throws Exception {
|
||||||
|
assertTrue(rssBlog.isRssFeed());
|
||||||
|
|
||||||
|
// add a feed post to rssBlog
|
||||||
|
final String body = getRandomString(42);
|
||||||
|
BlogPost p = blogPostFactory
|
||||||
|
.createBlogPost(rssBlog.getId(), clock.currentTimeMillis(),
|
||||||
|
null, author0, body);
|
||||||
|
blogManager0.addLocalPost(p);
|
||||||
|
|
||||||
|
// make sure it got saved as an RSS feed post
|
||||||
|
Collection<BlogPostHeader> headers =
|
||||||
|
blogManager0.getPostHeaders(rssBlog.getId());
|
||||||
|
assertEquals(1, headers.size());
|
||||||
|
BlogPostHeader header = headers.iterator().next();
|
||||||
|
assertEquals(POST, header.getType());
|
||||||
|
assertEquals(Author.Status.NONE, header.getAuthorStatus());
|
||||||
|
assertTrue(header.isRssFeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFeedReblog() throws Exception {
|
||||||
|
// add a feed post to rssBlog
|
||||||
|
final String body = getRandomString(42);
|
||||||
|
BlogPost p = blogPostFactory
|
||||||
|
.createBlogPost(rssBlog.getId(), clock.currentTimeMillis(),
|
||||||
|
null, author0, body);
|
||||||
|
blogManager0.addLocalPost(p);
|
||||||
|
|
||||||
|
// reblog feed post to own blog
|
||||||
|
Collection<BlogPostHeader> headers =
|
||||||
|
blogManager0.getPostHeaders(rssBlog.getId());
|
||||||
|
assertEquals(1, headers.size());
|
||||||
|
BlogPostHeader header = headers.iterator().next();
|
||||||
|
blogManager0.addLocalComment(author0, blog0.getId(), null, header);
|
||||||
|
|
||||||
|
// make sure it got saved as an RSS feed post
|
||||||
|
headers = blogManager0.getPostHeaders(blog0.getId());
|
||||||
|
assertEquals(1, headers.size());
|
||||||
|
BlogCommentHeader commentHeader =
|
||||||
|
(BlogCommentHeader) headers.iterator().next();
|
||||||
|
assertEquals(COMMENT, commentHeader.getType());
|
||||||
|
assertTrue(commentHeader.getParent().isRssFeed());
|
||||||
|
|
||||||
|
// reblog reblogged post again to own blog
|
||||||
|
blogManager0
|
||||||
|
.addLocalComment(author0, blog0.getId(), null, commentHeader);
|
||||||
|
|
||||||
|
// make sure it got saved as an RSS feed post
|
||||||
|
headers = blogManager0.getPostHeaders(blog0.getId());
|
||||||
|
assertEquals(2, headers.size());
|
||||||
|
for (BlogPostHeader h : headers) {
|
||||||
|
assertTrue(h instanceof BlogCommentHeader);
|
||||||
|
assertEquals(COMMENT, h.getType());
|
||||||
|
assertTrue(((BlogCommentHeader) h).getRootPost().isRssFeed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_
|
|||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||||
|
import static org.briarproject.briar.api.blog.BlogConstants.KEY_RSS_FEED;
|
||||||
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT;
|
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT;
|
||||||
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST;
|
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
||||||
@@ -50,7 +51,7 @@ import static org.junit.Assert.assertFalse;
|
|||||||
public class BlogPostValidatorTest extends BriarTestCase {
|
public class BlogPostValidatorTest extends BriarTestCase {
|
||||||
|
|
||||||
private final Mockery context = new Mockery();
|
private final Mockery context = new Mockery();
|
||||||
private final Blog blog;
|
private final Blog blog, rssBlog;
|
||||||
private final BdfDictionary authorDict;
|
private final BdfDictionary authorDict;
|
||||||
private final ClientId clientId;
|
private final ClientId clientId;
|
||||||
private final byte[] descriptor;
|
private final byte[] descriptor;
|
||||||
@@ -79,7 +80,8 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
|
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
|
||||||
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
|
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
|
||||||
);
|
);
|
||||||
blog = new Blog(group, author);
|
blog = new Blog(group, author, false);
|
||||||
|
rssBlog = new Blog(group, author, true);
|
||||||
|
|
||||||
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
@@ -97,17 +99,28 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testValidateProperBlogPost()
|
public void testValidateProperBlogPost()
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
testValidateProperBlogPost(blog, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateProperRssBlogPost()
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
testValidateProperBlogPost(rssBlog, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testValidateProperBlogPost(Blog b, boolean rssFeed)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||||
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
|
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
|
||||||
|
|
||||||
BdfList signed =
|
BdfList signed = BdfList.of(b.getId(), message.getTimestamp(), body);
|
||||||
BdfList.of(blog.getId(), message.getTimestamp(), body);
|
expectCrypto(b, SIGNING_LABEL_POST, signed, sigBytes);
|
||||||
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
|
|
||||||
final BdfDictionary result =
|
final BdfDictionary result =
|
||||||
validator.validateMessage(message, group, m).getDictionary();
|
validator.validateMessage(message, group, m).getDictionary();
|
||||||
|
|
||||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||||
assertFalse(result.getBoolean(KEY_READ));
|
assertFalse(result.getBoolean(KEY_READ));
|
||||||
|
assertEquals(rssFeed, result.getBoolean(KEY_RSS_FEED));
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,14 +150,12 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
MessageId pOriginalId = new MessageId(TestUtils.getRandomId());
|
MessageId pOriginalId = new MessageId(TestUtils.getRandomId());
|
||||||
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
||||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||||
BdfList m =
|
BdfList m = BdfList.of(COMMENT.getInt(), comment, pOriginalId,
|
||||||
BdfList.of(COMMENT.getInt(), comment, pOriginalId, currentId,
|
currentId, sigBytes);
|
||||||
sigBytes);
|
|
||||||
|
|
||||||
BdfList signed =
|
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
|
||||||
BdfList.of(blog.getId(), message.getTimestamp(), comment,
|
comment, pOriginalId, currentId);
|
||||||
pOriginalId, currentId);
|
expectCrypto(blog, SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
|
||||||
final BdfDictionary result =
|
final BdfDictionary result =
|
||||||
validator.validateMessage(message, group, m).getDictionary();
|
validator.validateMessage(message, group, m).getDictionary();
|
||||||
|
|
||||||
@@ -164,14 +175,12 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
MessageId originalId = new MessageId(TestUtils.getRandomId());
|
MessageId originalId = new MessageId(TestUtils.getRandomId());
|
||||||
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
||||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||||
BdfList m =
|
BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, currentId,
|
||||||
BdfList.of(COMMENT.getInt(), null, originalId, currentId,
|
sigBytes);
|
||||||
sigBytes);
|
|
||||||
|
|
||||||
BdfList signed =
|
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(), null,
|
||||||
BdfList.of(blog.getId(), message.getTimestamp(), null,
|
originalId, currentId);
|
||||||
originalId, currentId);
|
expectCrypto(blog, SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
|
||||||
final BdfDictionary result =
|
final BdfDictionary result =
|
||||||
validator.validateMessage(message, group, m).getDictionary();
|
validator.validateMessage(message, group, m).getDictionary();
|
||||||
|
|
||||||
@@ -182,22 +191,33 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testValidateProperWrappedPost()
|
public void testValidateProperWrappedPost()
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
testValidateProperWrappedPost(blog, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateProperWrappedRssPost()
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
|
testValidateProperWrappedPost(rssBlog, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testValidateProperWrappedPost(final Blog b, boolean rssFeed)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
// group descriptor, timestamp, content, signature
|
// group descriptor, timestamp, content, signature
|
||||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||||
BdfList m =
|
BdfList m = BdfList.of(WRAPPED_POST.getInt(), descriptor,
|
||||||
BdfList.of(WRAPPED_POST.getInt(), descriptor,
|
message.getTimestamp(), body, sigBytes);
|
||||||
message.getTimestamp(), body, sigBytes);
|
|
||||||
|
|
||||||
BdfList signed =
|
BdfList signed = BdfList.of(b.getId(), message.getTimestamp(), body);
|
||||||
BdfList.of(blog.getId(), message.getTimestamp(), body);
|
expectCrypto(b, SIGNING_LABEL_POST, signed, sigBytes);
|
||||||
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
|
|
||||||
|
|
||||||
final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
|
final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
|
||||||
final byte[] originalBody = TestUtils.getRandomBytes(42);
|
final byte[] originalBody = TestUtils.getRandomBytes(42);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(groupFactory).createGroup(clientId, descriptor);
|
oneOf(groupFactory).createGroup(clientId, descriptor);
|
||||||
will(returnValue(blog.getGroup()));
|
will(returnValue(b.getGroup()));
|
||||||
|
oneOf(blogFactory).parseBlog(b.getGroup());
|
||||||
|
will(returnValue(b));
|
||||||
oneOf(clientHelper).toByteArray(originalList);
|
oneOf(clientHelper).toByteArray(originalList);
|
||||||
will(returnValue(originalBody));
|
will(returnValue(originalBody));
|
||||||
oneOf(messageFactory)
|
oneOf(messageFactory)
|
||||||
@@ -210,6 +230,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
validator.validateMessage(message, group, m).getDictionary();
|
validator.validateMessage(message, group, m).getDictionary();
|
||||||
|
|
||||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||||
|
assertEquals(rssFeed, result.getBoolean(KEY_RSS_FEED));
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +250,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
|
|
||||||
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
|
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
|
||||||
comment, originalId, oldId);
|
comment, originalId, oldId);
|
||||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
expectCrypto(blog, SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||||
|
|
||||||
final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
|
final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
|
||||||
originalId, oldId, sigBytes);
|
originalId, oldId, sigBytes);
|
||||||
@@ -257,11 +278,12 @@ public class BlogPostValidatorTest extends BriarTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectCrypto(final String label, final BdfList signed,
|
private void expectCrypto(final Blog b, final String label,
|
||||||
final byte[] sig) throws IOException, GeneralSecurityException {
|
final BdfList signed, final byte[] sig)
|
||||||
|
throws IOException, GeneralSecurityException {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(blogFactory).parseBlog(group);
|
oneOf(blogFactory).parseBlog(group);
|
||||||
will(returnValue(blog));
|
will(returnValue(b));
|
||||||
oneOf(clientHelper)
|
oneOf(clientHelper)
|
||||||
.verifySignature(label, sig, author.getPublicKey(), signed);
|
.verifySignature(label, sig, author.getPublicKey(), signed);
|
||||||
}});
|
}});
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
|
import org.briarproject.bramble.crypto.CryptoModule;
|
||||||
|
import org.briarproject.bramble.identity.IdentityModule;
|
||||||
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
|
import org.briarproject.bramble.system.SystemModule;
|
||||||
|
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.briar.api.blog.Blog;
|
||||||
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
|
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||||
|
import org.briarproject.briar.api.feed.Feed;
|
||||||
|
import org.briarproject.briar.api.feed.FeedManager;
|
||||||
|
import org.briarproject.briar.blog.BlogModule;
|
||||||
|
import org.briarproject.briar.test.BriarTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class FeedManagerIntegrationTest extends BriarTestCase {
|
||||||
|
|
||||||
|
private LifecycleManager lifecycleManager;
|
||||||
|
private FeedManager feedManager;
|
||||||
|
private BlogManager blogManager;
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File testFile = new File(testDir, "feedTest");
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
FeedManagerIntegrationTestComponent component =
|
||||||
|
DaggerFeedManagerIntegrationTestComponent.builder()
|
||||||
|
.testDatabaseModule(new TestDatabaseModule(testFile))
|
||||||
|
.build();
|
||||||
|
component.inject(this);
|
||||||
|
injectEagerSingletons(component);
|
||||||
|
|
||||||
|
lifecycleManager = component.getLifecycleManager();
|
||||||
|
lifecycleManager.startServices("feedTest");
|
||||||
|
lifecycleManager.waitForStartup();
|
||||||
|
|
||||||
|
feedManager = component.getFeedManager();
|
||||||
|
blogManager = component.getBlogManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFeedImportAndRemoval() throws Exception {
|
||||||
|
// initially, there's only the one personal blog
|
||||||
|
Collection<Blog> blogs = blogManager.getBlogs();
|
||||||
|
assertEquals(1, blogs.size());
|
||||||
|
Blog personalBlog = blogs.iterator().next();
|
||||||
|
|
||||||
|
// add feed into a dedicated blog
|
||||||
|
String url = "https://www.schneier.com/blog/atom.xml";
|
||||||
|
feedManager.addFeed(url);
|
||||||
|
|
||||||
|
// then there's the feed's blog now
|
||||||
|
blogs = blogManager.getBlogs();
|
||||||
|
assertEquals(2, blogs.size());
|
||||||
|
Blog feedBlog = null;
|
||||||
|
for (Blog blog : blogs) {
|
||||||
|
if (!blog.equals(personalBlog)) feedBlog = blog;
|
||||||
|
}
|
||||||
|
assertNotNull(feedBlog);
|
||||||
|
|
||||||
|
// check the feed got saved as expected
|
||||||
|
Collection<Feed> feeds = feedManager.getFeeds();
|
||||||
|
assertEquals(1, feeds.size());
|
||||||
|
Feed feed = feeds.iterator().next();
|
||||||
|
assertTrue(feed.getLastEntryTime() > 0);
|
||||||
|
assertTrue(feed.getAdded() > 0);
|
||||||
|
assertTrue(feed.getUpdated() > 0);
|
||||||
|
assertEquals(url, feed.getUrl());
|
||||||
|
assertEquals(feedBlog, feed.getBlog());
|
||||||
|
assertEquals("Schneier on Security", feed.getTitle());
|
||||||
|
assertEquals("A blog covering security and security technology.",
|
||||||
|
feed.getDescription());
|
||||||
|
assertEquals(feed.getTitle(), feed.getBlog().getName());
|
||||||
|
assertEquals(feed.getTitle(), feed.getLocalAuthor().getName());
|
||||||
|
|
||||||
|
// check the feed entries have been added to the blog as expected
|
||||||
|
Collection<BlogPostHeader> headers =
|
||||||
|
blogManager.getPostHeaders(feedBlog.getId());
|
||||||
|
for (BlogPostHeader header : headers) {
|
||||||
|
assertTrue(header.isRssFeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
// now let's remove the feed's blog again
|
||||||
|
blogManager.removeBlog(feedBlog);
|
||||||
|
blogs = blogManager.getBlogs();
|
||||||
|
assertEquals(1, blogs.size());
|
||||||
|
assertEquals(personalBlog, blogs.iterator().next());
|
||||||
|
assertEquals(0, feedManager.getFeeds().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
lifecycleManager.stopServices();
|
||||||
|
lifecycleManager.waitForShutdown();
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void injectEagerSingletons(
|
||||||
|
FeedManagerIntegrationTestComponent component) {
|
||||||
|
component.inject(new FeedModule.EagerSingletons());
|
||||||
|
component.inject(new BlogModule.EagerSingletons());
|
||||||
|
component.inject(new ContactModule.EagerSingletons());
|
||||||
|
component.inject(new CryptoModule.EagerSingletons());
|
||||||
|
component.inject(new IdentityModule.EagerSingletons());
|
||||||
|
component.inject(new LifecycleModule.EagerSingletons());
|
||||||
|
component.inject(new SyncModule.EagerSingletons());
|
||||||
|
component.inject(new SystemModule.EagerSingletons());
|
||||||
|
component.inject(new TransportModule.EagerSingletons());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.briarproject.briar.feed;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.client.ClientModule;
|
||||||
|
import org.briarproject.bramble.contact.ContactModule;
|
||||||
|
import org.briarproject.bramble.crypto.CryptoModule;
|
||||||
|
import org.briarproject.bramble.data.DataModule;
|
||||||
|
import org.briarproject.bramble.db.DatabaseModule;
|
||||||
|
import org.briarproject.bramble.event.EventModule;
|
||||||
|
import org.briarproject.bramble.identity.IdentityModule;
|
||||||
|
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||||
|
import org.briarproject.bramble.sync.SyncModule;
|
||||||
|
import org.briarproject.bramble.system.SystemModule;
|
||||||
|
import org.briarproject.bramble.test.TestDatabaseModule;
|
||||||
|
import org.briarproject.bramble.test.TestPluginConfigModule;
|
||||||
|
import org.briarproject.bramble.test.TestSeedProviderModule;
|
||||||
|
import org.briarproject.bramble.test.TestSocksModule;
|
||||||
|
import org.briarproject.bramble.transport.TransportModule;
|
||||||
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
|
import org.briarproject.briar.api.feed.FeedManager;
|
||||||
|
import org.briarproject.briar.blog.BlogModule;
|
||||||
|
import org.briarproject.briar.client.BriarClientModule;
|
||||||
|
import org.briarproject.briar.test.TestDnsModule;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
TestDatabaseModule.class,
|
||||||
|
TestPluginConfigModule.class,
|
||||||
|
TestSeedProviderModule.class,
|
||||||
|
TestSocksModule.class,
|
||||||
|
TestDnsModule.class,
|
||||||
|
LifecycleModule.class,
|
||||||
|
BriarClientModule.class,
|
||||||
|
ClientModule.class,
|
||||||
|
ContactModule.class,
|
||||||
|
CryptoModule.class,
|
||||||
|
BlogModule.class,
|
||||||
|
FeedModule.class,
|
||||||
|
DataModule.class,
|
||||||
|
DatabaseModule.class,
|
||||||
|
EventModule.class,
|
||||||
|
IdentityModule.class,
|
||||||
|
SyncModule.class,
|
||||||
|
SystemModule.class,
|
||||||
|
TransportModule.class
|
||||||
|
})
|
||||||
|
interface FeedManagerIntegrationTestComponent {
|
||||||
|
|
||||||
|
void inject(FeedManagerIntegrationTest testCase);
|
||||||
|
|
||||||
|
void inject(FeedModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(BlogModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(ContactModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(CryptoModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(IdentityModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(LifecycleModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(SyncModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(SystemModule.EagerSingletons init);
|
||||||
|
|
||||||
|
void inject(TransportModule.EagerSingletons init);
|
||||||
|
|
||||||
|
LifecycleManager getLifecycleManager();
|
||||||
|
|
||||||
|
FeedManager getFeedManager();
|
||||||
|
|
||||||
|
BlogManager getBlogManager();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import org.briarproject.bramble.api.event.EventListener;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
@@ -360,6 +361,14 @@ public class IntroductionIntegrationTest
|
|||||||
eventWaiter.await(TIMEOUT, 1);
|
eventWaiter.await(TIMEOUT, 1);
|
||||||
assertTrue(listener0.response1Received);
|
assertTrue(listener0.response1Received);
|
||||||
|
|
||||||
|
// sync fake transport properties back to 1, so Message ACK can arrive
|
||||||
|
// and the assertDefaultUiMessages() check at the end will not fail
|
||||||
|
TransportProperties tp = new TransportProperties(
|
||||||
|
Collections.singletonMap("key", "value"));
|
||||||
|
c0.getTransportPropertyManager()
|
||||||
|
.mergeLocalProperties(new TransportId("fake"), tp);
|
||||||
|
sync0To1(1, true);
|
||||||
|
|
||||||
// sync second response
|
// sync second response
|
||||||
sync2To0(1, true);
|
sync2To0(1, true);
|
||||||
eventWaiter.await(TIMEOUT, 1);
|
eventWaiter.await(TIMEOUT, 1);
|
||||||
@@ -836,14 +845,32 @@ public class IntroductionIntegrationTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertDefaultUiMessages() throws DbException {
|
private void assertDefaultUiMessages() throws DbException {
|
||||||
assertEquals(2, introductionManager0.getIntroductionMessages(
|
Collection<IntroductionMessage> messages =
|
||||||
contactId1From0).size());
|
introductionManager0.getIntroductionMessages(contactId1From0);
|
||||||
assertEquals(2, introductionManager0.getIntroductionMessages(
|
assertEquals(2, messages.size());
|
||||||
contactId2From0).size());
|
assertMessagesAreAcked(messages);
|
||||||
assertEquals(2, introductionManager1.getIntroductionMessages(
|
|
||||||
contactId0From1).size());
|
messages = introductionManager0.getIntroductionMessages(
|
||||||
assertEquals(2, introductionManager2.getIntroductionMessages(
|
contactId2From0);
|
||||||
contactId0From2).size());
|
assertEquals(2, messages.size());
|
||||||
|
assertMessagesAreAcked(messages);
|
||||||
|
|
||||||
|
messages = introductionManager1.getIntroductionMessages(
|
||||||
|
contactId0From1);
|
||||||
|
assertEquals(2, messages.size());
|
||||||
|
assertMessagesAreAcked(messages);
|
||||||
|
|
||||||
|
messages = introductionManager2.getIntroductionMessages(
|
||||||
|
contactId0From2);
|
||||||
|
assertEquals(2, messages.size());
|
||||||
|
assertMessagesAreAcked(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMessagesAreAcked(
|
||||||
|
Collection<IntroductionMessage> messages) {
|
||||||
|
for (IntroductionMessage msg : messages) {
|
||||||
|
if (msg.isLocal()) assertTrue(msg.isSeen());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addListeners(boolean accept1, boolean accept2) {
|
private void addListeners(boolean accept1, boolean accept2) {
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
private final byte[] publicKey =
|
private final byte[] publicKey =
|
||||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
private final Author author = new Author(authorId, authorName, publicKey);
|
private final Author author = new Author(authorId, authorName, publicKey);
|
||||||
private final Blog blog = new Blog(group, author);
|
private final Blog blog = new Blog(group, author, false);
|
||||||
private final BdfList descriptor = BdfList.of(authorName, publicKey);
|
private final BdfList descriptor = BdfList.of(authorName, publicKey, false);
|
||||||
private final String content =
|
private final String content =
|
||||||
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
|
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsNullBlogName() throws Exception {
|
public void testRejectsNullBlogName() throws Exception {
|
||||||
BdfList invalidDescriptor = BdfList.of(null, publicKey);
|
BdfList invalidDescriptor = BdfList.of(null, publicKey, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -72,7 +72,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsNonStringBlogName() throws Exception {
|
public void testRejectsNonStringBlogName() throws Exception {
|
||||||
BdfList invalidDescriptor = BdfList.of(123, publicKey);
|
BdfList invalidDescriptor = BdfList.of(123, publicKey, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -80,7 +80,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsTooShortBlogName() throws Exception {
|
public void testRejectsTooShortBlogName() throws Exception {
|
||||||
BdfList invalidDescriptor = BdfList.of("", publicKey);
|
BdfList invalidDescriptor = BdfList.of("", publicKey, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -89,7 +89,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testAcceptsMinLengthBlogName() throws Exception {
|
public void testAcceptsMinLengthBlogName() throws Exception {
|
||||||
String shortBlogName = TestUtils.getRandomString(1);
|
String shortBlogName = TestUtils.getRandomString(1);
|
||||||
BdfList validDescriptor = BdfList.of(shortBlogName, publicKey);
|
BdfList validDescriptor = BdfList.of(shortBlogName, publicKey, false);
|
||||||
expectCreateBlog(shortBlogName, publicKey);
|
expectCreateBlog(shortBlogName, publicKey);
|
||||||
expectEncodeMetadata(INVITE);
|
expectEncodeMetadata(INVITE);
|
||||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||||
@@ -102,7 +102,8 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
public void testRejectsTooLongBlogName() throws Exception {
|
public void testRejectsTooLongBlogName() throws Exception {
|
||||||
String invalidBlogName =
|
String invalidBlogName =
|
||||||
TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1);
|
TestUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1);
|
||||||
BdfList invalidDescriptor = BdfList.of(invalidBlogName, publicKey);
|
BdfList invalidDescriptor =
|
||||||
|
BdfList.of(invalidBlogName, publicKey, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -110,7 +111,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsNullPublicKey() throws Exception {
|
public void testRejectsNullPublicKey() throws Exception {
|
||||||
BdfList invalidDescriptor = BdfList.of(authorName, null);
|
BdfList invalidDescriptor = BdfList.of(authorName, null, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -118,7 +119,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
|
|
||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsNonRawPublicKey() throws Exception {
|
public void testRejectsNonRawPublicKey() throws Exception {
|
||||||
BdfList invalidDescriptor = BdfList.of(authorName, 123);
|
BdfList invalidDescriptor = BdfList.of(authorName, 123, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -127,7 +128,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
@Test(expected = FormatException.class)
|
@Test(expected = FormatException.class)
|
||||||
public void testRejectsTooLongPublicKey() throws Exception {
|
public void testRejectsTooLongPublicKey() throws Exception {
|
||||||
byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
|
byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
|
||||||
BdfList invalidDescriptor = BdfList.of(authorName, invalidKey);
|
BdfList invalidDescriptor = BdfList.of(authorName, invalidKey, false);
|
||||||
v.validateMessage(message, group,
|
v.validateMessage(message, group,
|
||||||
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
|
||||||
null));
|
null));
|
||||||
@@ -136,7 +137,7 @@ public class BlogSharingValidatorTest extends SharingValidatorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testAcceptsMinLengthPublicKey() throws Exception {
|
public void testAcceptsMinLengthPublicKey() throws Exception {
|
||||||
byte[] key = TestUtils.getRandomBytes(1);
|
byte[] key = TestUtils.getRandomBytes(1);
|
||||||
BdfList validDescriptor = BdfList.of(authorName, key);
|
BdfList validDescriptor = BdfList.of(authorName, key, false);
|
||||||
|
|
||||||
expectCreateBlog(authorName, key);
|
expectCreateBlog(authorName, key);
|
||||||
expectEncodeMetadata(INVITE);
|
expectEncodeMetadata(INVITE);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.client.ClientHelper;
|
|||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||||
@@ -136,4 +137,5 @@ public interface BriarIntegrationTestComponent {
|
|||||||
|
|
||||||
TransportPropertyManager getTransportPropertyManager();
|
TransportPropertyManager getTransportPropertyManager();
|
||||||
|
|
||||||
|
AuthorFactory getAuthorFactory();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.briar.test;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import okhttp3.Dns;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class TestDnsModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
Dns provideDns() {
|
||||||
|
return Dns.SYSTEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user