Compare commits

..

32 Commits

Author SHA1 Message Date
akwizgran
d311557f09 Check whether first visible message ID is null before storing. 2017-05-29 10:05:49 +01:00
akwizgran
3449677b24 Bumped version number and expiry date. 2017-05-19 12:07:29 +01:00
akwizgran
1ad3a6646e Merge branch '941-store-correct-parent-id' into 'master'
Store correct original parent ID when rewrapping blog posts

See merge request !534
2017-05-12 09:53:27 +00:00
akwizgran
2d10f6b2bd Merge branch '884-emoji-text-view-layout-bug' into 'master'
Remove ellipsizing support from EmojiTextView

Closes #884

See merge request !533
2017-05-12 09:35:33 +00:00
akwizgran
5b05424d83 Merge branch 'master' into '941-store-correct-parent-id'
# Conflicts:
#   briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
2017-05-12 09:34:24 +00:00
akwizgran
0826022d82 Merge branch 'bring_annotations_in_line' into 'master'
Bring nullable annotation imports in line

See merge request !536
2017-05-12 09:33:00 +00:00
akwizgran
a901bfb9cb Merge branch '948-vector-crash' into 'master'
Remove scientific notation from vector drawables to prevent crashes

Closes #948

See merge request !537
2017-05-12 09:28:59 +00:00
akwizgran
03cdce122a Merge branch '947-bluetooth-address-crash' into 'master'
Don't crash on empty bluetooth addresses

See merge request !538
2017-05-12 09:26:57 +00:00
goapunk
f2e0e16969 Bring nullable annotation imports in line
Signed-off-by: goapunk <noobie@goapunks.net>
2017-05-12 10:06:56 +02:00
Torsten Grote
0c441e2ff3 Don't crash on empty bluetooth addresses 2017-05-10 15:06:09 -03:00
Torsten Grote
21302304a5 Remove scientific notation from vector drawables to prevent crashes
Details: http://stackoverflow.com/a/40829348
2017-05-10 14:56:59 -03:00
Torsten Grote
6839d8b844 Merge branch 'wifi-manager-memory-leak' into 'master'
Use application context to get WifiManager

See merge request !535
2017-05-10 17:01:52 +00:00
Torsten Grote
aee65a716c Merge branch '798-remove-contact-blogs' into 'master'
Allow to remove pre-shared blogs of our contacts

Closes #798

See merge request !529
2017-05-10 16:58:38 +00:00
Torsten Grote
6a07d8f2c9 Allow to remove pre-shared blogs of our contacts 2017-05-10 13:50:07 -03:00
Ernir Erlingsson
3c1ea81cd0 Merge branch '853-disabled-menu-items' into 'master'
Remove theme default color override

Closes #853

See merge request !527
2017-05-06 20:26:15 +00:00
Ernir Erlingsson
025f417bc7 Merge branch '894-list-position-restore' into 'master'
save and restore list position for threaded lists

Closes #894 and #946

See merge request !528
2017-05-06 19:37:02 +00:00
Ernir Erlingsson
c9dcd906c9 final pre-merge fixes 2017-05-06 21:36:25 +02:00
Ernir Erlingsson
7024e04d15 fixed final akwizgran comments 2017-05-06 21:31:53 +02:00
akwizgran
0b8ac947db Use application context to get WifiManager. 2017-05-05 15:43:27 +01:00
Ernir Erlingsson
948410a064 fixed unread buttons for threaded lists and akwizgran's comments 2017-05-05 14:49:53 +02:00
akwizgran
2841339cac Merge branch '468-ci' into 'master'
Set up basic CI

Closes #468

See merge request !530
2017-05-05 09:11:06 +00:00
Torsten Grote
e8e82bd805 Update Translations 2017-05-04 10:26:49 -03:00
Ernir Erlingsson
6876f40a0e Merge branch 'fix_groupname_validation' into 'master'
Fix groupname validation

See merge request !531
2017-05-04 07:26:55 +00:00
Ernir Erlingsson
5f4e1ecdfd improvements after code review #1
fix
2017-05-02 11:42:55 +02:00
Ernir Erlingsson
044719432a list position save and restore now implemented for threaded lists 2017-05-02 11:42:55 +02:00
Ernir Erlingsson
d1a929da85 bumped expire date 2017-05-02 11:42:15 +02:00
goapunk
2a8978a60d fix group name validation
Signed-off-by: goapunk <noobie@goapunks.net>
2017-04-29 16:49:37 +02:00
Torsten Grote
c0afad7a26 Set up basic CI 2017-04-28 13:24:41 -03:00
akwizgran
37281c6c23 Remove ellipsizing support from EmojiTextView.
This is a workaround for a layout bug.
2017-04-28 15:39:24 +01:00
akwizgran
76a5e25656 Added tests for wrapping and rewrapping blog posts. 2017-04-19 12:16:18 +01:00
akwizgran
3575b74837 Store correct original parent ID when rewrapping blog posts. 2017-04-19 12:15:34 +01:00
Torsten Grote
f1c7996960 Remove theme default color override 2017-04-18 09:14:00 -03:00
60 changed files with 1429 additions and 685 deletions

20
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,20 @@
image: registry.gitlab.com/fdroid/ci-images:client-latest
cache:
paths:
- .gradle/wrapper
- .gradle/caches
before_script:
- export GRADLE_USER_HOME=$PWD/.gradle
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle`
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK}
test:
script:
- ./gradlew test
after_script:
# this file changes every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/

View File

@@ -12,8 +12,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 22 targetSdkVersion 22
versionCode 1 versionCode 14
versionName "1.0" versionName "0.14"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }

View File

@@ -19,7 +19,8 @@ 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.duplex.AbstractBluetoothPlugin; import org.briarproject.bramble.api.plugin.TransportId;
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;
@@ -74,8 +75,7 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class DroidtoothPlugin<C, S> class DroidtoothPlugin implements DuplexPlugin {
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,10 +84,16 @@ class DroidtoothPlugin<C, S>
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;
@@ -98,15 +104,29 @@ class DroidtoothPlugin<C, S>
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
protected void close(S ss) throws IOException { public TransportId getId() {
((BluetoothServerSocket)ss).close(); 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
@@ -174,14 +194,14 @@ class DroidtoothPlugin<C, S>
BluetoothServerSocket ss; BluetoothServerSocket ss;
try { try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord( ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", UUID.fromString(getUuid())); "RFCOMM", 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((S)ss); tryToClose(ss);
return; return;
} }
LOG.info("Socket bound"); LOG.info("Socket bound");
@@ -193,6 +213,29 @@ class DroidtoothPlugin<C, S>
}); });
} }
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;
@@ -218,9 +261,9 @@ class DroidtoothPlugin<C, S>
@Override @Override
public void stop() { public void stop() {
this.running = false; running = false;
if (receiver != null) appContext.unregisterReceiver(receiver); if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose((S)socket); tryToClose(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");
@@ -233,20 +276,42 @@ class DroidtoothPlugin<C, S>
return running && adapter != null && adapter.isEnabled(); return running && adapter != null && adapter.isEnabled();
} }
protected Runnable returnPollRunnable(final String address, final String uuid, @Override
final ContactId c) { public boolean shouldPoll() {
return new Runnable() { return true;
@Override }
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
}
}; @Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@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
@@ -282,17 +347,37 @@ class DroidtoothPlugin<C, S>
LOG.info("Failed to connect to " + scrubMacAddress(address) LOG.info("Failed to connect to " + scrubMacAddress(address)
+ ": " + e); + ": " + e);
} }
tryToClose((S)s); tryToClose(s);
return null; return null;
} }
} }
private void tryToClose(@Nullable Closeable c) {
public DuplexTransportConnection connectToAddress(String address, String uuid) { try {
BluetoothSocket s = connect(address, uuid); if (c != null) c.close();
return s == null ? null : wrapSocket(s); } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
@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;
BluetoothSocket s = connect(address, uuid);
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override @Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r, public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
@@ -341,7 +426,7 @@ class DroidtoothPlugin<C, S>
return null; return null;
} finally { } finally {
// Closing the socket will terminate the listener task // Closing the socket will terminate the listener task
tryToClose((S)ss); tryToClose(ss);
closeSockets(futures, chosen); closeSockets(futures, chosen);
} }
} }
@@ -373,6 +458,11 @@ class DroidtoothPlugin<C, S>
}); });
} }
@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;
@@ -397,6 +487,31 @@ class DroidtoothPlugin<C, S>
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 {
@@ -408,7 +523,7 @@ class DroidtoothPlugin<C, S>
bind(); bind();
} else if (state == STATE_OFF) { } else if (state == STATE_OFF) {
LOG.info("Bluetooth disabled"); LOG.info("Bluetooth disabled");
tryToClose((S)socket); tryToClose(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) {

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.api; package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@@ -53,6 +54,12 @@ public class Bytes implements Comparable<Bytes> {
return aBytes.length - bBytes.length; return aBytes.length - bBytes.length;
} }
@Override
public String toString() {
return getClass().getSimpleName() +
"(" + StringUtils.toHexString(getBytes()) + ")";
}
public static class BytesComparator implements Comparator<Bytes> { public static class BytesComparator implements Comparator<Bytes> {
@Override @Override

View File

@@ -1,190 +0,0 @@
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);
}
}

View File

@@ -19,7 +19,7 @@ public class PrivacyUtils {
@Nullable @Nullable
public static String scrubMacAddress(@Nullable String address) { public static String scrubMacAddress(@Nullable String address) {
if (address == null) return null; if (address == null || address.length() == 0) return null;
// this is a fake address we need to know about // this is a fake address we need to know about
if (address.equals("02:00:00:00:00:00")) return address; if (address.equals("02:00:00:00:00:00")) return address;
// keep first and last octet of MAC address // keep first and last octet of MAC address

View File

@@ -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.duplex.AbstractBluetoothPlugin; import org.briarproject.bramble.api.plugin.TransportId;
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,22 +57,46 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> { class BluetoothPlugin implements DuplexPlugin {
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, BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
Backoff backoff, int maxLatency, this.ioExecutor = ioExecutor;
DuplexPluginCallback callback) { this.secureRandom = secureRandom;
super(ioExecutor, secureRandom, backoff, maxLatency, callback); this.backoff = backoff;
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
@@ -115,7 +139,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return; return;
} }
if (!running) { if (!running) {
tryToClose((S)ss); tryToClose(ss);
return; return;
} }
socket = ss; socket = ss;
@@ -129,16 +153,29 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
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 void tryToClose(@Nullable StreamConnectionNotifier ss) { private String getUuid() {
// try { String uuid = callback.getLocalProperties().get(PROP_UUID);
// if (ss != null) ss.close(); if (uuid == null) {
// } catch (IOException e) { byte[] random = new byte[UUID_BYTES];
// if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); secureRandom.nextBytes(random);
// } finally { uuid = UUID.nameUUIDFromBytes(random).toString();
// callback.transportDisabled(); TransportProperties p = new TransportProperties();
// } 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) {
@@ -159,33 +196,54 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
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
protected void close(S ss) throws IOException { public void stop() {
((StreamConnection)ss).close(); running = false;
tryToClose(socket);
} }
@Override @Override
public Runnable returnPollRunnable(final String address, final String uuid, public boolean isRunning() {
final ContactId c) { return running;
}
return new Runnable() { @Override
@Override public boolean shouldPoll() {
public void run() { return true;
if (!running) return; }
StreamConnection s = connect(makeUrl(address, uuid));
if (s != null) { @Override
backoff.reset(); public int getPollingInterval() {
callback.outgoingConnectionCreated(c, wrapSocket(s)); return backoff.getPollingInterval();
}
@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) {
@@ -201,13 +259,25 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
} }
@Override @Override
protected DuplexTransportConnection connectToAddress(String address, String uuid) { public DuplexTransportConnection createConnection(ContactId c) {
String url = makeUrl(address, uuid); if (!running) 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;
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) {
@@ -227,7 +297,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null; return null;
} }
if (!running) { if (!running) {
tryToClose((S)ss); tryToClose(ss);
return null; return null;
} }
// Create the background tasks // Create the background tasks
@@ -260,7 +330,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null; return null;
} finally { } finally {
// Closing the socket will terminate the listener task // Closing the socket will terminate the listener task
tryToClose((S)ss); tryToClose(ss);
closeSockets(futures, chosen); closeSockets(futures, chosen);
} }
} }
@@ -292,6 +362,11 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
}); });
} }
@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;
@@ -310,7 +385,7 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
return null; return null;
} }
if (!running) { if (!running) {
tryToClose((S)ss); tryToClose(ss);
return null; return null;
} }
BdfList descriptor = new BdfList(); BdfList descriptor = new BdfList();
@@ -320,6 +395,33 @@ class BluetoothPlugin<C, S> extends AbstractBluetoothPlugin<C, S> {
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 {

View File

@@ -82,6 +82,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 22 targetSdkVersion 22
versionCode 14
versionName "0.14"
resValue "string", "app_package", "org.briarproject.briar" resValue "string", "app_package", "org.briarproject.briar"
buildConfigField "String", "GitHash", "\"${getGitHash()}\"" buildConfigField "String", "GitHash", "\"${getGitHash()}\""
} }

View File

@@ -1,9 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest
package="org.briarproject.briar" package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android">
android:versionCode="13"
android:versionName="0.13">
<uses-feature android:name="android.hardware.bluetooth"/> <uses-feature android:name="android.hardware.bluetooth"/>
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera" />

View File

@@ -32,6 +32,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.api.feed.FeedManager;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
@@ -78,6 +79,8 @@ public interface AndroidComponent
@DatabaseExecutor @DatabaseExecutor
Executor databaseExecutor(); Executor databaseExecutor();
MessageTracker messageTracker();
LifecycleManager lifecycleManager(); LifecycleManager lifecycleManager();
IdentityManager identityManager(); IdentityManager identityManager();

View File

@@ -10,7 +10,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature; import android.content.pm.Signature;
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;
@@ -38,6 +37,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.transition.Slide; import android.transition.Slide;
@@ -21,6 +20,7 @@ import org.briarproject.briar.android.panic.ExitActivity;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.blog;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
@@ -15,6 +14,7 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity; import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@MethodsNotNullByDefault @MethodsNotNullByDefault

View File

@@ -169,7 +169,7 @@ class BlogControllerImpl extends BaseControllerImpl
LocalAuthor a = identityManager.getLocalAuthor(); LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getBlog(groupId); Blog b = blogManager.getBlog(groupId);
boolean ours = a.getId().equals(b.getAuthor().getId()); boolean ours = a.getId().equals(b.getAuthor().getId());
boolean removable = blogManager.canBeRemoved(groupId); boolean removable = blogManager.canBeRemoved(b);
BlogItem blog = new BlogItem(b, ours, removable); BlogItem blog = new BlogItem(b, ours, removable);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.contact; package org.briarproject.briar.android.contact;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
@@ -13,6 +12,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener; import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import javax.annotation.Nullable;
import im.delight.android.identicons.IdenticonDrawable; import im.delight.android.identicons.IdenticonDrawable;
@UiThread @UiThread

View File

@@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(DbControllerImpl.class.getName()); Logger.getLogger(DbControllerImpl.class.getName());
private final Executor dbExecutor; protected final Executor dbExecutor;
private final LifecycleManager lifecycleManager; private final LifecycleManager lifecycleManager;
@Inject @Inject

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android.controller; package org.briarproject.briar.android.controller;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -15,6 +13,7 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@NotNullByDefault @NotNullByDefault

View File

@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.forum.ForumController.ForumListener; import org.briarproject.briar.android.forum.ForumController.ForumListener;
import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
@@ -55,10 +56,10 @@ class ForumControllerImpl extends
LifecycleManager lifecycleManager, IdentityManager identityManager, LifecycleManager lifecycleManager, IdentityManager identityManager,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
ForumManager forumManager, ForumSharingManager forumSharingManager, ForumManager forumManager, ForumSharingManager forumSharingManager,
EventBus eventBus, Clock clock, EventBus eventBus, Clock clock, MessageTracker messageTracker,
AndroidNotificationManager notificationManager) { AndroidNotificationManager notificationManager) {
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
eventBus, clock, notificationManager); eventBus, clock, notificationManager, messageTracker);
this.forumManager = forumManager; this.forumManager = forumManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.fragment;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils; import android.text.TextUtils;
@@ -19,6 +18,8 @@ import org.briarproject.briar.android.activity.BaseActivity;
import java.util.ArrayList; import java.util.ArrayList;
import javax.annotation.Nullable;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class SFDialogFragment extends DialogFragment { public class SFDialogFragment extends DialogFragment {

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.fragment; package org.briarproject.briar.android.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -9,6 +8,8 @@ import android.view.ViewGroup;
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 javax.annotation.Nullable;
public class SignOutFragment extends BaseFragment { public class SignOutFragment extends BaseFragment {
private static final String TAG = SignOutFragment.class.getName(); private static final String TAG = SignOutFragment.class.getName();

View File

@@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener;
import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.android.threaded.ThreadListControllerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
@@ -60,9 +61,10 @@ class GroupControllerImpl extends
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
PrivateGroupManager privateGroupManager, PrivateGroupManager privateGroupManager,
GroupMessageFactory groupMessageFactory, EventBus eventBus, GroupMessageFactory groupMessageFactory, EventBus eventBus,
Clock clock, AndroidNotificationManager notificationManager) { MessageTracker messageTracker, Clock clock,
AndroidNotificationManager notificationManager) {
super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor,
eventBus, clock, notificationManager); eventBus, clock, notificationManager, messageTracker);
this.privateGroupManager = privateGroupManager; this.privateGroupManager = privateGroupManager;
this.groupMessageFactory = groupMessageFactory; this.groupMessageFactory = groupMessageFactory;
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.privategroup.conversation; package org.briarproject.briar.android.privategroup.conversation;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
@@ -12,6 +11,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.threaded.ThreadItem; import org.briarproject.briar.android.threaded.ThreadItem;
import org.briarproject.briar.api.privategroup.GroupMessageHeader; import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@UiThread @UiThread

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
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;
@@ -14,6 +13,8 @@ import org.briarproject.briar.android.controller.handler.UiResultExceptionHandle
import org.briarproject.briar.android.privategroup.conversation.GroupActivity; import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.briar.android.sharing.BaseMessageFragment.MessageFragmentListener;
import javax.annotation.Nullable;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class CreateGroupActivity extends BaseGroupInviteActivity implements public class CreateGroupActivity extends BaseGroupInviteActivity implements

View File

@@ -10,6 +10,7 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import org.briarproject.bramble.util.StringUtils;
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.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
@@ -84,9 +85,9 @@ public class CreateGroupFragment extends BaseFragment {
private void validateName() { private void validateName() {
String name = this.name.getText().toString(); String name = this.name.getText().toString();
if (name.length() < 1 || name.length() > MAX_GROUP_NAME_LENGTH) if (name.length() < 1 || StringUtils.utf8IsTooLong(name, MAX_GROUP_NAME_LENGTH))
button.setEnabled(false); button.setEnabled(false);
else if(!button.isEnabled()) else if (!button.isEnabled())
button.setEnabled(true); button.setEnabled(true);
} }

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android.privategroup.memberlist; package org.briarproject.briar.android.privategroup.memberlist;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.Author.Status; import org.briarproject.bramble.api.identity.Author.Status;
@@ -9,6 +7,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.Visibility; import org.briarproject.briar.api.privategroup.Visibility;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe @NotThreadSafe

View File

@@ -146,7 +146,7 @@ public class BriarReportPrimer implements ReportPrimer {
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI); NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
boolean wifiAvailable = wifi != null && wifi.isAvailable(); boolean wifiAvailable = wifi != null && wifi.isAvailable();
// Is wifi enabled? // Is wifi enabled?
o = ctx.getSystemService(WIFI_SERVICE); o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
WifiManager wm = (WifiManager) o; WifiManager wm = (WifiManager) o;
boolean wifiEnabled = wm != null && boolean wifiEnabled = wm != null &&
wm.getWifiState() == WIFI_STATE_ENABLED; wm.getWifiState() == WIFI_STATE_ENABLED;

View File

@@ -2,7 +2,6 @@ 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.StringRes; 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;
@@ -25,6 +24,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;

View File

@@ -30,8 +30,8 @@ public class SplashScreenActivity extends BaseActivity {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SplashScreenActivity.class.getName()); Logger.getLogger(SplashScreenActivity.class.getName());
// This build expires on 1 May 2017 // This build expires on 1 July 2017
private static final long EXPIRY_DATE = 1493593200 * 1000L; private static final long EXPIRY_DATE = 1498863600 * 1000L;
@Inject @Inject
protected ConfigController configController; protected ConfigController configController;

View File

@@ -1,6 +1,6 @@
package org.briarproject.briar.android.threaded; package org.briarproject.briar.android.threaded;
import android.support.annotation.Nullable; import android.os.Handler;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@@ -14,6 +14,8 @@ import org.briarproject.briar.android.util.VersionedAdapter;
import java.util.Collection; import java.util.Collection;
import javax.annotation.Nullable;
import static android.support.v7.widget.RecyclerView.NO_POSITION; import static android.support.v7.widget.RecyclerView.NO_POSITION;
@UiThread @UiThread
@@ -26,6 +28,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
protected final NestedTreeList<I> items = new NestedTreeList<>(); protected final NestedTreeList<I> items = new NestedTreeList<>();
private final ThreadItemListener<I> listener; private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager; private final LinearLayoutManager layoutManager;
private final Handler handler = new Handler();
private volatile int revision = 0; private volatile int revision = 0;
@@ -64,6 +67,17 @@ public class ThreadItemAdapter<I extends ThreadItem>
revision++; revision++;
} }
void setItemWithIdVisible(MessageId messageId) {
int pos = 0;
for (I item : items) {
if (item.getId().equals(messageId)) {
layoutManager.scrollToPosition(pos);
break;
}
pos++;
}
}
public void setItems(Collection<I> items) { public void setItems(Collection<I> items) {
this.items.clear(); this.items.clear();
this.items.addAll(items); this.items.addAll(items);
@@ -144,7 +158,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
/** /**
* Returns the position of the first unread item below the current viewport * Returns the position of the first unread item below the current viewport
*/ */
public int getVisibleUnreadPosBottom() { int getVisibleUnreadPosBottom() {
final int positionBottom = layoutManager.findLastVisibleItemPosition(); final int positionBottom = layoutManager.findLastVisibleItemPosition();
if (positionBottom == NO_POSITION) return NO_POSITION; if (positionBottom == NO_POSITION) return NO_POSITION;
for (int i = positionBottom + 1; i < items.size(); i++) { for (int i = positionBottom + 1; i < items.size(); i++) {
@@ -156,7 +170,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
/** /**
* Returns the position of the first unread item above the current viewport * Returns the position of the first unread item above the current viewport
*/ */
public int getVisibleUnreadPosTop() { int getVisibleUnreadPosTop() {
final int positionTop = layoutManager.findFirstVisibleItemPosition(); final int positionTop = layoutManager.findFirstVisibleItemPosition();
int position = NO_POSITION; int position = NO_POSITION;
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {

View File

@@ -0,0 +1,15 @@
package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.List;
import javax.annotation.Nullable;
public interface ThreadItemList<I extends ThreadItem> extends List<I> {
@Nullable
MessageId getFirstVisibleItemId();
void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId);
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.briar.android.threaded;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.ArrayList;
import javax.annotation.Nullable;
public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I>
implements ThreadItemList<I> {
private MessageId bottomVisibleItemId;
@Override
public MessageId getFirstVisibleItemId() {
return bottomVisibleItemId;
}
public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) {
this.bottomVisibleItemId = bottomVisibleItemId;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.threaded;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
@@ -26,6 +25,7 @@ import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.SharingController.SharingListener; import org.briarproject.briar.android.controller.SharingController.SharingListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener; import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextInputView;
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.support.design.widget.Snackbar.make; import static android.support.design.widget.Snackbar.make;
@@ -51,7 +52,7 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader> public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
extends BriarActivity extends BriarActivity
implements ThreadListListener<H>, TextInputListener, SharingListener, implements ThreadListListener<H>, TextInputListener, SharingListener,
ThreadItemListener<I> { ThreadItemListener<I>, ThreadListDataSource {
protected static final String KEY_REPLY_ID = "replyId"; protected static final String KEY_REPLY_ID = "replyId";
@@ -68,6 +69,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
private MessageId replyId; private MessageId replyId;
protected abstract ThreadListController<G, I, H> getController(); protected abstract ThreadListController<G, I, H> getController();
@Inject @Inject
protected SharingController sharingController; protected SharingController sharingController;
@@ -104,6 +106,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
updateUnreadCount(); updateUnreadCount();
} }
} }
@Override @Override
public void onScrollStateChanged(RecyclerView recyclerView, public void onScrollStateChanged(RecyclerView recyclerView,
int newState) { int newState) {
@@ -144,6 +147,18 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
loadSharingContacts(); loadSharingContacts();
} }
@Override
@Nullable
public MessageId getFirstVisibleMessageId() {
if (layoutManager != null && adapter != null) {
int position =
layoutManager.findFirstVisibleItemPosition();
I i = adapter.getItemAt(position);
return i == null ? null : i.getId();
}
return null;
}
protected abstract A createAdapter(LinearLayoutManager layoutManager); protected abstract A createAdapter(LinearLayoutManager layoutManager);
protected void loadNamedGroup() { protected void loadNamedGroup() {
@@ -167,16 +182,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
protected void loadItems() { protected void loadItems() {
final int revision = adapter.getRevision(); final int revision = adapter.getRevision();
getController().loadItems( getController().loadItems(
new UiResultExceptionHandler<Collection<I>, DbException>(this) { new UiResultExceptionHandler<ThreadItemList<I>, DbException>(
this) {
@Override @Override
public void onResultUi(Collection<I> items) { public void onResultUi(ThreadItemList<I> items) {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
adapter.incrementRevision(); adapter.incrementRevision();
if (items.isEmpty()) { if (items.isEmpty()) {
list.showData(); list.showData();
} else { } else {
adapter.setItems(items); initList(items);
list.showData();
updateTextInput(replyId); updateTextInput(replyId);
} }
} else { } else {
@@ -192,6 +207,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
}); });
} }
private void initList(final ThreadItemList<I> items) {
adapter.setItems(items);
MessageId messageId = items.getFirstVisibleItemId();
if (messageId != null)
adapter.setItemWithIdVisible(messageId);
updateUnreadCount();
list.showData();
}
protected void loadSharingContacts() { protected void loadSharingContacts() {
getController().loadSharingContacts( getController().loadSharingContacts(
new UiResultExceptionHandler<Collection<ContactId>, DbException>( new UiResultExceptionHandler<Collection<ContactId>, DbException>(

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId;
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.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.DestroyableContext; import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.controller.ActivityLifecycleController; import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
@@ -30,7 +31,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void loadItem(H header, ResultExceptionHandler<I, DbException> handler); void loadItem(H header, ResultExceptionHandler<I, DbException> handler);
void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler); void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler);
void markItemRead(I item); void markItemRead(I item);
@@ -41,7 +42,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void deleteNamedGroup(ExceptionHandler<DbException> handler); void deleteNamedGroup(ExceptionHandler<DbException> handler);
interface ThreadListListener<H> extends DestroyableContext { interface ThreadListListener<H> extends ThreadListDataSource {
@UiThread @UiThread
void onHeaderReceived(H header); void onHeaderReceived(H header);
@@ -52,4 +53,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
void onInvitationAccepted(ContactId c); void onInvitationAccepted(ContactId c);
} }
interface ThreadListDataSource extends DestroyableContext {
@UiThread @Nullable
MessageId getFirstVisibleMessageId();
}
} }

View File

@@ -22,14 +22,13 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.NamedGroup; import org.briarproject.briar.api.client.NamedGroup;
import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.client.ThreadedMessage; import org.briarproject.briar.api.client.ThreadedMessage;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -55,18 +54,21 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
protected final AndroidNotificationManager notificationManager; protected final AndroidNotificationManager notificationManager;
protected final Executor cryptoExecutor; protected final Executor cryptoExecutor;
protected final Clock clock; protected final Clock clock;
private final MessageTracker messageTracker;
protected volatile L listener; protected volatile L listener;
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor, protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, IdentityManager identityManager, LifecycleManager lifecycleManager, IdentityManager identityManager,
@CryptoExecutor Executor cryptoExecutor, EventBus eventBus, @CryptoExecutor Executor cryptoExecutor, EventBus eventBus,
Clock clock, AndroidNotificationManager notificationManager) { Clock clock, AndroidNotificationManager notificationManager,
MessageTracker messageTracker) {
super(dbExecutor, lifecycleManager); super(dbExecutor, lifecycleManager);
this.identityManager = identityManager; this.identityManager = identityManager;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.notificationManager = notificationManager; this.notificationManager = notificationManager;
this.clock = clock; this.clock = clock;
this.eventBus = eventBus; this.eventBus = eventBus;
this.messageTracker = messageTracker;
} }
@Override @Override
@@ -97,6 +99,20 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override @Override
public void onActivityDestroy() { public void onActivityDestroy() {
final MessageId first = listener.getFirstVisibleMessageId();
if (first != null) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
messageTracker.storeMessageId(groupId, first);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
} }
@CallSuper @CallSuper
@@ -144,7 +160,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override @Override
public void loadItems( public void loadItems(
final ResultExceptionHandler<Collection<I>, DbException> handler) { final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) {
checkGroupId(); checkGroupId();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
@@ -293,11 +309,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@DatabaseExecutor @DatabaseExecutor
protected abstract void deleteNamedGroup(G groupItem) throws DbException; protected abstract void deleteNamedGroup(G groupItem) throws DbException;
private List<I> buildItems(Collection<H> headers) { private ThreadItemList<I> buildItems(Collection<H> headers)
List<I> items = new ArrayList<>(); throws DbException {
ThreadItemList<I> items = new ThreadItemListImpl<>();
for (H h : headers) { for (H h : headers) {
items.add(buildItem(h, bodyCache.get(h.getId()))); items.add(buildItem(h, bodyCache.get(h.getId())));
} }
MessageId msgId = messageTracker.loadStoredMessageId(groupId);
if (LOG.isLoggable(INFO))
LOG.info("Loaded last top visible message id " + msgId);
items.setFirstVisibleId(msgId);
return items; return items;
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.util; package org.briarproject.briar.android.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
@@ -23,6 +22,8 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ArticleMovementMethod; import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.briar.android.widget.LinkDialogFragment;
import javax.annotation.Nullable;
import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE; import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.view;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -13,6 +12,8 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import javax.annotation.Nullable;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
public class UnreadMessageButton extends FrameLayout { public class UnreadMessageButton extends FrameLayout {
@@ -36,8 +37,7 @@ public class UnreadMessageButton extends FrameLayout {
LayoutInflater inflater = (LayoutInflater) context LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater inflater.inflate(R.layout.unread_message_button, this, true);
.inflate(R.layout.unread_message_button, this, true);
fab = (FloatingActionButton) findViewById(R.id.fab); fab = (FloatingActionButton) findViewById(R.id.fab);
unread = (TextView) findViewById(R.id.unreadCountView); unread = (TextView) findViewById(R.id.unreadCountView);
@@ -64,15 +64,11 @@ public class UnreadMessageButton extends FrameLayout {
public void setUnreadCount(int count) { public void setUnreadCount(int count) {
if (count == 0) { if (count == 0) {
fab.setVisibility(GONE); setVisibility(INVISIBLE);
// fab.hide();
unread.setVisibility(GONE);
} else { } else {
// FIXME: Use animations when upgrading to support library 24.2.0 // FIXME: Use animations when upgrading to support library 24.2.0
// https://code.google.com/p/android/issues/detail?id=216469 // https://code.google.com/p/android/issues/detail?id=216469
fab.setVisibility(VISIBLE); setVisibility(VISIBLE);
// if (!fab.isShown()) fab.show();
unread.setVisibility(VISIBLE);
unread.setText(String.valueOf(count)); unread.setText(String.valueOf(count));
} }
} }

View File

@@ -1,29 +1,21 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context; import android.content.Context;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.text.TextUtils; import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.widget.TextView;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable; import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static android.text.TextUtils.TruncateAt.END;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.widget.TextView.BufferType.SPANNABLE; import static android.widget.TextView.BufferType.SPANNABLE;
@UiThread @UiThread
public class EmojiTextView extends TextView { public class EmojiTextView extends AppCompatTextView {
private CharSequence source;
private boolean needsEllipsizing;
public EmojiTextView(Context context) { public EmojiTextView(Context context) {
this(context, null); this(context, null);
@@ -42,13 +34,9 @@ public class EmojiTextView extends TextView {
@Override @Override
public void setText(@Nullable CharSequence text, BufferType type) { public void setText(@Nullable CharSequence text, BufferType type) {
source = EmojiProvider.getInstance(getContext()).emojify(text, this); CharSequence source =
EmojiProvider.getInstance(getContext()).emojify(text, this);
setTextEllipsized(source); super.setText(source, SPANNABLE);
}
private void setTextEllipsized(final @Nullable CharSequence source) {
super.setText(needsEllipsizing ? ellipsize(source) : source, SPANNABLE);
} }
@Override @Override
@@ -57,26 +45,6 @@ public class EmojiTextView extends TextView {
else super.invalidateDrawable(drawable); else super.invalidateDrawable(drawable);
} }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int size = MeasureSpec.getSize(widthMeasureSpec);
final int mode = MeasureSpec.getMode(widthMeasureSpec);
if (getEllipsize() == END &&
!TextUtils.isEmpty(source) &&
(mode == AT_MOST || mode == EXACTLY) &&
getPaint().breakText(source, 0, source.length() - 1, true, size,
null) != source.length()) {
needsEllipsizing = true;
FontMetricsInt font = getPaint().getFontMetricsInt();
int height = Math.abs(font.top - font.bottom);
super.onMeasure(MeasureSpec.makeMeasureSpec(size, EXACTLY),
MeasureSpec.makeMeasureSpec(height, EXACTLY));
} else {
needsEllipsizing = false;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override @Override
protected void onLayout(boolean changed, int left, int top, int right, protected void onLayout(boolean changed, int left, int top, int right,
int bottom) { int bottom) {
@@ -89,20 +57,6 @@ public class EmojiTextView extends TextView {
if (size > drawingCacheSize) { if (size > drawingCacheSize) {
setLayerType(LAYER_TYPE_NONE, null); setLayerType(LAYER_TYPE_NONE, null);
} }
if (changed) setTextEllipsized(source);
super.onLayout(changed, left, top, right, bottom); super.onLayout(changed, left, top, right, bottom);
} }
@Nullable
public CharSequence ellipsize(@Nullable CharSequence text) {
if (TextUtils.isEmpty(text) || getWidth() == 0 ||
getEllipsize() != END) {
return text;
} else {
return TextUtils.ellipsize(text, getPaint(),
getWidth() - getPaddingRight() - getPaddingLeft(), END);
}
}
} }

View File

@@ -7,7 +7,7 @@
<path <path
android:fillColor="#ffa500" android:fillColor="#ffa500"
android:pathData="M0,8.88178e-16 L30,8.88178e-16 L30,30 L0,30 L0,8.88178e-16 Z"/> android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/>
<path <path
android:fillColor="#ffffff" android:fillColor="#ffffff"
android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678 android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678

View File

@@ -12,9 +12,9 @@
android:fillColor="#87c214" android:fillColor="#87c214"
android:pathData="M64.9004,0 C55.2004,0,47.1992,7.99922,47.1992,17.6992 L47.1992,40.1992 android:pathData="M64.9004,0 C55.2004,0,47.1992,7.99922,47.1992,17.6992 L47.1992,40.1992
L90.8008,40.1992 L90.8008,17.6992 L90.8008,40.1992 L90.8008,17.6992
C90.8008,7.99922,82.8992,-4.73695e-15,73.1992,0 L64.9004,0 Z M161.9,0 C90.8008,7.99922,82.8992,0,73.1992,0 L64.9004,0 Z M161.9,0
C152.2,0,144.199,7.99922,144.199,17.6992 L144.199,137.199 L187.801,137.199 C152.2,0,144.199,7.99922,144.199,17.6992 L144.199,137.199 L187.801,137.199
L187.801,17.6992 C187.801,7.99922,179.899,-4.73695e-15,170.199,0 L161.9,0 Z L187.801,17.6992 C187.801,7.99922,179.899,0,170.199,0 L161.9,0 Z
M47.1992,97.8008 L47.1992,217.301 C47.1992,227.001,55.1004,235,64.9004,235 M47.1992,97.8008 L47.1992,217.301 C47.1992,227.001,55.1004,235,64.9004,235
L73.1992,235 C82.8992,235,90.9004,227.001,90.9004,217.301 L90.9004,97.8008 L73.1992,235 C82.8992,235,90.9004,227.001,90.9004,217.301 L90.9004,97.8008
L47.1992,97.8008 Z M144.199,194.801 L144.199,217.301 L47.1992,97.8008 Z M144.199,194.801 L144.199,217.301
@@ -25,12 +25,12 @@ C179.899,235,187.9,227.001,187.9,217.301 L187.9,194.801 L144.199,194.801 Z"/>
android:pathData="M144.2,144.2 L187.9,144.2 L187.9,187.9 L144.2,187.9 L144.2,144.2 Z"/> android:pathData="M144.2,144.2 L187.9,144.2 L187.9,187.9 L144.2,187.9 L144.2,144.2 Z"/>
<path <path
android:fillColor="#95d220" android:fillColor="#95d220"
android:pathData="M17.6992,47.1992 C7.99922,47.1992,2.36848e-15,55.1004,0,64.9004 L0,73.1992 android:pathData="M17.6992,47.1992 C7.99922,47.1992,0,55.1004,0,64.9004 L0,73.1992
C0,82.8992,7.89922,90.9004,17.6992,90.9004 L137.199,90.9004 L137.199,47.1992 C0,82.8992,7.89922,90.9004,17.6992,90.9004 L137.199,90.9004 L137.199,47.1992
L17.6992,47.1992 Z M194.801,47.1992 L194.801,90.9004 L217.301,90.9004 L17.6992,47.1992 Z M194.801,47.1992 L194.801,90.9004 L217.301,90.9004
C227.001,90.9004,235,82.9992,235,73.1992 L235,64.9004 C227.001,90.9004,235,82.9992,235,73.1992 L235,64.9004
C235,55.1004,227.001,47.1992,217.301,47.1992 L194.801,47.1992 Z M17.6992,144.199 C235,55.1004,227.001,47.1992,217.301,47.1992 L194.801,47.1992 Z M17.6992,144.199
C7.99922,144.199,2.36848e-15,152.1,0,161.9 L0,170.199 C7.99922,144.199,0,152.1,0,161.9 L0,170.199
C0,179.899,7.89922,187.9,17.6992,187.9 L40.1992,187.9 L40.1992,144.199 C0,179.899,7.89922,187.9,17.6992,187.9 L40.1992,187.9 L40.1992,144.199
L17.6992,144.199 Z M97.8008,144.199 L97.8008,187.9 L217.301,187.9 L17.6992,144.199 Z M97.8008,144.199 L97.8008,187.9 L217.301,187.9
C227.001,187.9,235,179.899,235,170.199 L235,161.9 C227.001,187.9,235,179.899,235,170.199 L235,161.9

View File

@@ -151,7 +151,6 @@
<string name="groups_create_group_invitation_button">Einladung schicken</string> <string name="groups_create_group_invitation_button">Einladung schicken</string>
<string name="groups_create_group_hint">Gebe Deiner privaten Gruppe einen Namen</string> <string name="groups_create_group_hint">Gebe Deiner privaten Gruppe einen Namen</string>
<string name="groups_invitation_sent">Gruppeneinladung versendet</string> <string name="groups_invitation_sent">Gruppeneinladung versendet</string>
<string name="groups_compose_message">Nachricht erstellen</string>
<string name="groups_message_sent">Nachricht gesendet</string> <string name="groups_message_sent">Nachricht gesendet</string>
<string name="groups_member_list">Mitglieder</string> <string name="groups_member_list">Mitglieder</string>
<string name="groups_invite_members">Mitglieder einladen</string> <string name="groups_invite_members">Mitglieder einladen</string>
@@ -174,14 +173,22 @@
<string name="groups_invitations_invitation_received">%1$s hat dich eingeladen der Gruppe \"%2$s\" beizutreten.</string> <string name="groups_invitations_invitation_received">%1$s hat dich eingeladen der Gruppe \"%2$s\" beizutreten.</string>
<string name="groups_invitations_joined">Gruppe beigetreten</string> <string name="groups_invitations_joined">Gruppe beigetreten</string>
<string name="groups_invitations_declined">Gruppeneinladung abgelehnt</string> <string name="groups_invitations_declined">Gruppeneinladung abgelehnt</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d offene Gruppeneinladung</item>
<item quantity="other">%d offene Gruppeneinladungen</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Gruppeneinladung von %s angenommen.</string> <string name="groups_invitations_response_accepted_sent">Gruppeneinladung von %s angenommen.</string>
<string name="groups_invitations_response_declined_sent">Gruppeneinladung von %s abgelehnt.</string> <string name="groups_invitations_response_declined_sent">Gruppeneinladung von %s abgelehnt.</string>
<string name="groups_invitations_response_accepted_received">%s hat die Einladung zur Gruppe angenommen.</string> <string name="groups_invitations_response_accepted_received">%s hat die Einladung zur Gruppe angenommen.</string>
<string name="groups_invitations_response_declined_received">%s hat die Einladung zur Gruppe abgelehnt.</string> <string name="groups_invitations_response_declined_received">%s hat die Einladung zur Gruppe abgelehnt.</string>
<string name="sharing_status_groups">Nur der Ersteller kann neue Mitglieder zu einer Gruppe einladen. HIer alle aktuellen Mitglieder dieser Gruppe.</string>
<!--Private Groups Revealing Contacts--> <!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Kontakte teilen</string> <string name="groups_reveal_contacts">Kontakte teilen</string>
<string name="groups_reveal_dialog_message">Kontakte können mit allen derzeitigen und zukünftigen Mitgliedern dieser Gruppe geteilt werden.\n\nDas beschleunigt die Verbindung zu der Gruppe und macht sie zusätzlich zuverlässiger, da Kommunikation mit den Mitgliedern auch dann erfolgen kann, wenn der Ersteller der Gruppe offline ist.</string> <string name="groups_reveal_dialog_message">Kontakte können mit allen derzeitigen und zukünftigen Mitgliedern dieser Gruppe geteilt werden.\n\nDas beschleunigt die Verbindung zu der Gruppe und macht sie zusätzlich zuverlässiger, da Kommunikation mit den Mitgliedern auch dann erfolgen kann, wenn der Ersteller der Gruppe offline ist.</string>
<string name="groups_reveal_visible">Verbindung zum Kontakt ist für die Gruppe sichtbar</string> <string name="groups_reveal_visible">Verbindung zum Kontakt ist für die Gruppe sichtbar</string>
<string name="groups_reveal_visible_revealed_by_us">Beziehung zum Kontakt ist für diese Gruppe sichtbar (selbst offengelegt)</string>
<string name="groups_reveal_visible_revealed_by_contact">Beziehung zum Kontakt ist für diese Gruppe sichtbar (offengelegt durch %s)</string>
<string name="groups_reveal_invisible">Beziehung zum Kontakt ist für diese Gruppe nicht sichtbar</string>
<!--Forums--> <!--Forums-->
<string name="no_forums">Du hast noch keine Foren.\n\nWarum erstellst du nicht einfach selbst ein neues Forum, indem du auf das \"+\"-Icon am oberen Bildschirmrand tippst?\n\nDu kannst auch deine Kontakte auffordern, Foren mit dir zu teilen.</string> <string name="no_forums">Du hast noch keine Foren.\n\nWarum erstellst du nicht einfach selbst ein neues Forum, indem du auf das \"+\"-Icon am oberen Bildschirmrand tippst?\n\nDu kannst auch deine Kontakte auffordern, Foren mit dir zu teilen.</string>
<string name="create_forum_title">Neues Forum</string> <string name="create_forum_title">Neues Forum</string>
@@ -194,15 +201,10 @@
<item quantity="one">%d Beitrag</item> <item quantity="one">%d Beitrag</item>
<item quantity="other">%d Beiträge</item> <item quantity="other">%d Beiträge</item>
</plurals> </plurals>
<string name="forum_compose_post">Neuer Forenbeitrag</string>
<string name="forum_new_entry_posted">Forenbeitrag wurde veröffentlicht.</string> <string name="forum_new_entry_posted">Forenbeitrag wurde veröffentlicht.</string>
<string name="forum_new_message_hint">Neuer Eintrag</string> <string name="forum_new_message_hint">Neuer Eintrag</string>
<string name="forum_message_reply_hint">Neue Antwort</string> <string name="forum_message_reply_hint">Neue Antwort</string>
<string name="btn_reply">Antworten</string> <string name="btn_reply">Antworten</string>
<plurals name="message_replies">
<item quantity="one">%1$d Antwort</item>
<item quantity="other">%1$d Antworten</item>
</plurals>
<string name="forum_leave">Forum verlassen</string> <string name="forum_leave">Forum verlassen</string>
<string name="dialog_title_leave_forum">Verlassen des Forums bestätigen</string> <string name="dialog_title_leave_forum">Verlassen des Forums bestätigen</string>
<string name="dialog_message_leave_forum">Bist du sicher, dass du dieses Forum verlassen willst? Kontakte, die du mit diesem Forum geteilt hast, werden keine Updates von diesem Forum mehr bekommen.</string> <string name="dialog_message_leave_forum">Bist du sicher, dass du dieses Forum verlassen willst? Kontakte, die du mit diesem Forum geteilt hast, werden keine Updates von diesem Forum mehr bekommen.</string>
@@ -229,6 +231,8 @@
<string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung akzeptiert.</string> <string name="forum_invitation_response_accepted_received">%s hat die Forumeinladung akzeptiert.</string>
<string name="forum_invitation_response_declined_received">%s hat die Forumseinladung abgelehnt.</string> <string name="forum_invitation_response_declined_received">%s hat die Forumseinladung abgelehnt.</string>
<string name="sharing_status">Sharing Status</string> <string name="sharing_status">Sharing Status</string>
<string name="sharing_status_forum">Jedes Mitglied eines Forums kann dieses mit seinen Kontakten teilen. Du teilst dieses Forum mit den folgenden Kontakten. Möglicherweise gibt es Mitglieder die nicht sichtbar sind.</string>
<string name="shared_with">Geteilt mit %1$d (%2$d online)</string>
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d Forum von Kontaken geteilt</item> <item quantity="one">%d Forum von Kontaken geteilt</item>
<item quantity="other">%d Foren von Kontakten geteilt</item> <item quantity="other">%d Foren von Kontakten geteilt</item>
@@ -265,6 +269,7 @@
<string name="blogs_sharing_invitations_title">Blogeinladungen</string> <string name="blogs_sharing_invitations_title">Blogeinladungen</string>
<string name="blogs_sharing_joined_toast">Blog abonniert</string> <string name="blogs_sharing_joined_toast">Blog abonniert</string>
<string name="blogs_sharing_declined_toast">Blogeinladung abgelehnt</string> <string name="blogs_sharing_declined_toast">Blogeinladung abgelehnt</string>
<string name="sharing_status_blog">Jeder Abonnent eines Blogs kann diesen mit seinen Kontakten teilen. Du teilst diesen Blog mit den folgenden Kontakten. Möglicherweise gibt es Abonnenten die nicht sichtbar sind.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">RSS-Feed importieren</string> <string name="blogs_rss_feeds_import">RSS-Feed importieren</string>
<string name="blogs_rss_feeds_import_button">Importieren</string> <string name="blogs_rss_feeds_import_button">Importieren</string>
@@ -274,6 +279,9 @@
<string name="blogs_rss_feeds_manage_imported">Importiert:</string> <string name="blogs_rss_feeds_manage_imported">Importiert:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string> <string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Letzte Aktualisierung:</string> <string name="blogs_rss_feeds_manage_updated">Letzte Aktualisierung:</string>
<string name="blogs_rss_remove_feed">Feed entfernen</string>
<string name="blogs_rss_remove_feed_dialog_message">Soll der Feed mit allen Posts wirklich gelöscht werden?\nGeteilte Posts werden dabei nicht von anderen Geräten gelöscht.</string>
<string name="blogs_rss_remove_feed_ok">Feed entfernen</string>
<string name="blogs_rss_feeds_manage_delete_error">Der Feed konnte nicht gelöscht werden!</string> <string name="blogs_rss_feeds_manage_delete_error">Der Feed konnte nicht gelöscht werden!</string>
<string name="blogs_rss_feeds_manage_empty_state">Du hast bisher noch keine RSS Feeds importiert. Tippe auf das \"+\"-Icon am oberen Bildschirmrand um einen neuen Feed hinzuzufügen.</string> <string name="blogs_rss_feeds_manage_empty_state">Du hast bisher noch keine RSS Feeds importiert. Tippe auf das \"+\"-Icon am oberen Bildschirmrand um einen neuen Feed hinzuzufügen.</string>
<string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string> <string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string>
@@ -282,6 +290,10 @@
<string name="bluetooth_setting">Über Bluetooth verbinden</string> <string name="bluetooth_setting">Über Bluetooth verbinden</string>
<string name="bluetooth_setting_enabled">Sobald Kontakte in der Nähe sind</string> <string name="bluetooth_setting_enabled">Sobald Kontakte in der Nähe sind</string>
<string name="bluetooth_setting_disabled">Nur beim Hinzufügen von Kontakten</string> <string name="bluetooth_setting_disabled">Nur beim Hinzufügen von Kontakten</string>
<string name="tor_network_setting">Über Tor verbinden</string>
<string name="tor_network_setting_never">Nie</string>
<string name="tor_network_setting_wifi">Nur WLAN</string>
<string name="tor_network_setting_always">WLAN oder mobile Datenverbindung</string>
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="security_settings_title">Sicherheit</string> <string name="security_settings_title">Sicherheit</string>
<string name="change_password">Passwort ändern</string> <string name="change_password">Passwort ändern</string>
@@ -341,4 +353,9 @@
<string name="dev_report_saved">Der Bericht wurde gespeichert. Er wird verschickt, wenn Du Dich das nächste Mal bei Briar anmeldest.</string> <string name="dev_report_saved">Der Bericht wurde gespeichert. Er wird verschickt, wenn Du Dich das nächste Mal bei Briar anmeldest.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Von Briar abmelden ...</string> <string name="progress_title_logout">Von Briar abmelden ...</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Bildschirmfilter erkannt</string>
<string name="screen_filter_body">Diese Apps können andere Apps überlagern:\n\n%1$s \n\nBriar reagiert im Fall einer Überlagerung nicht auf Benutzereingaben.
Falls dadurch Probleme in der Verwendung von Briar entstehen, versuche diese Apps zu deaktivieren.\n</string>
<string name="checkbox_dont_show_again">Nicht noch einmal für diese Apps warnen</string>
</resources> </resources>

View File

@@ -151,7 +151,6 @@
<string name="groups_create_group_invitation_button">Enviar invitación</string> <string name="groups_create_group_invitation_button">Enviar invitación</string>
<string name="groups_create_group_hint">Dar nombre al grupo privado</string> <string name="groups_create_group_hint">Dar nombre al grupo privado</string>
<string name="groups_invitation_sent">Se ha mandado la invitación de grupo</string> <string name="groups_invitation_sent">Se ha mandado la invitación de grupo</string>
<string name="groups_compose_message">Escribir mensaje</string>
<string name="groups_message_sent">Mensaje enviado</string> <string name="groups_message_sent">Mensaje enviado</string>
<string name="groups_member_list">Integrantes</string> <string name="groups_member_list">Integrantes</string>
<string name="groups_invite_members">Invitar miembros</string> <string name="groups_invite_members">Invitar miembros</string>
@@ -182,6 +181,7 @@
<string name="groups_invitations_response_declined_sent">Declinaste la invitación de grupo de %s.</string> <string name="groups_invitations_response_declined_sent">Declinaste la invitación de grupo de %s.</string>
<string name="groups_invitations_response_accepted_received">%s ha aceptado la invitación al grupo.</string> <string name="groups_invitations_response_accepted_received">%s ha aceptado la invitación al grupo.</string>
<string name="groups_invitations_response_declined_received">%s ha declinado la invitación al grupo.</string> <string name="groups_invitations_response_declined_received">%s ha declinado la invitación al grupo.</string>
<string name="sharing_status_groups">Solo el creador puede invitar nuevos miembros al grupo. Abajo se listan todos los participantes actuales.</string>
<!--Private Groups Revealing Contacts--> <!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Revelar contactos</string> <string name="groups_reveal_contacts">Revelar contactos</string>
<string name="groups_reveal_dialog_message">Puedes elegir si revelar los contactos a los actuales y futuros integrantes de este grupo.\n\nRevelar los contactos mejora la velocidad y la fiabilidad de tu conexión, porque puedes comunicarte con los contactos revelados incluso cuando el creador del grupo no se encuentra conectado.</string> <string name="groups_reveal_dialog_message">Puedes elegir si revelar los contactos a los actuales y futuros integrantes de este grupo.\n\nRevelar los contactos mejora la velocidad y la fiabilidad de tu conexión, porque puedes comunicarte con los contactos revelados incluso cuando el creador del grupo no se encuentra conectado.</string>
@@ -201,15 +201,10 @@
<item quantity="one">%d publicación</item> <item quantity="one">%d publicación</item>
<item quantity="other">%d publicaciones</item> <item quantity="other">%d publicaciones</item>
</plurals> </plurals>
<string name="forum_compose_post">Nueva publicación en el foro</string>
<string name="forum_new_entry_posted">Entrada en el foro publicada</string> <string name="forum_new_entry_posted">Entrada en el foro publicada</string>
<string name="forum_new_message_hint">Nueva entrada</string> <string name="forum_new_message_hint">Nueva entrada</string>
<string name="forum_message_reply_hint">Nueva respuesta</string> <string name="forum_message_reply_hint">Nueva respuesta</string>
<string name="btn_reply">Responder</string> <string name="btn_reply">Responder</string>
<plurals name="message_replies">
<item quantity="one">%1$d respuesta</item>
<item quantity="other">%1$d respuestas</item>
</plurals>
<string name="forum_leave">Abandonar foro</string> <string name="forum_leave">Abandonar foro</string>
<string name="dialog_title_leave_forum">Confirmación abandono del foro</string> <string name="dialog_title_leave_forum">Confirmación abandono del foro</string>
<string name="dialog_message_leave_forum">¿Seguro que quieres abandonar el foro? Puede que los contactos que invitaste al foro dejen de recibir actualizaciones del mismo.</string> <string name="dialog_message_leave_forum">¿Seguro que quieres abandonar el foro? Puede que los contactos que invitaste al foro dejen de recibir actualizaciones del mismo.</string>
@@ -236,6 +231,7 @@
<string name="forum_invitation_response_accepted_received">%s aceptó la invitación al foro.</string> <string name="forum_invitation_response_accepted_received">%s aceptó la invitación al foro.</string>
<string name="forum_invitation_response_declined_received">%s rechazó la invitación al foro.</string> <string name="forum_invitation_response_declined_received">%s rechazó la invitación al foro.</string>
<string name="sharing_status">Estado de la compartición</string> <string name="sharing_status">Estado de la compartición</string>
<string name="sharing_status_forum">Cualquier miembro de un foro puede compartirlo con sus contactos. Estás compartiendo este foro con los contactos listados a continuación. Puede haber otros miembros que no puedes ver.</string>
<string name="shared_with">Compartido con %1$d (%2$d en línea)</string> <string name="shared_with">Compartido con %1$d (%2$d en línea)</string>
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d foro compartido por contactos</item> <item quantity="one">%d foro compartido por contactos</item>
@@ -273,6 +269,7 @@
<string name="blogs_sharing_invitations_title">Invitaciones a blogs</string> <string name="blogs_sharing_invitations_title">Invitaciones a blogs</string>
<string name="blogs_sharing_joined_toast">Suscrito al blog</string> <string name="blogs_sharing_joined_toast">Suscrito al blog</string>
<string name="blogs_sharing_declined_toast">Rechazada invitación al blog</string> <string name="blogs_sharing_declined_toast">Rechazada invitación al blog</string>
<string name="sharing_status_blog">Cualquiera que se suscriba a un blog puede compartirlo con sus contactos. Estás compartiendo este blog con contactos listados a continuación. Puede haber otros suscriptores que no puedes ver.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importar canal RSS</string> <string name="blogs_rss_feeds_import">Importar canal RSS</string>
<string name="blogs_rss_feeds_import_button">Importar</string> <string name="blogs_rss_feeds_import_button">Importar</string>
@@ -282,6 +279,9 @@
<string name="blogs_rss_feeds_manage_imported">Importado:</string> <string name="blogs_rss_feeds_manage_imported">Importado:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string> <string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Última actualización:</string> <string name="blogs_rss_feeds_manage_updated">Última actualización:</string>
<string name="blogs_rss_remove_feed">Eliminar canal RSS</string>
<string name="blogs_rss_remove_feed_dialog_message">¿Seguro que quieres borrar este canal RSS y todas sus publicaciones?\nNo se eliminará ningún artículo que hayas compartido de los dispositivos de otras personas.</string>
<string name="blogs_rss_remove_feed_ok">Eliminar canal RSS</string>
<string name="blogs_rss_feeds_manage_delete_error">¡El canal no pudo ser eliminado!</string> <string name="blogs_rss_feeds_manage_delete_error">¡El canal no pudo ser eliminado!</string>
<string name="blogs_rss_feeds_manage_empty_state">No has importado ningún canal RSS.\n\n¿Por qué no pulsas el signo más de la esquina superior derecha para añadir el primero?</string> <string name="blogs_rss_feeds_manage_empty_state">No has importado ningún canal RSS.\n\n¿Por qué no pulsas el signo más de la esquina superior derecha para añadir el primero?</string>
<string name="blogs_rss_feeds_manage_error">Hubo un problema cargando tus canales RSS. Por favor, prueba más tarde.</string> <string name="blogs_rss_feeds_manage_error">Hubo un problema cargando tus canales RSS. Por favor, prueba más tarde.</string>
@@ -290,6 +290,10 @@
<string name="bluetooth_setting">Connectar mediante Bluetooth</string> <string name="bluetooth_setting">Connectar mediante Bluetooth</string>
<string name="bluetooth_setting_enabled">Cuando haya contactos cerca</string> <string name="bluetooth_setting_enabled">Cuando haya contactos cerca</string>
<string name="bluetooth_setting_disabled">Solo al añadir contactos</string> <string name="bluetooth_setting_disabled">Solo al añadir contactos</string>
<string name="tor_network_setting">Conectar a través de Tor</string>
<string name="tor_network_setting_never">Nunca</string>
<string name="tor_network_setting_wifi">Solo con wifi</string>
<string name="tor_network_setting_always">Con wifi o datos móviles</string>
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="security_settings_title">Seguridad</string> <string name="security_settings_title">Seguridad</string>
<string name="change_password">Cambiar contraseña</string> <string name="change_password">Cambiar contraseña</string>
@@ -349,4 +353,9 @@
<string name="dev_report_saved">Informe guardado. Se enviará la próxima vez que inicies Briar.</string> <string name="dev_report_saved">Informe guardado. Se enviará la próxima vez que inicies Briar.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Saliendo de Briar…</string> <string name="progress_title_logout">Saliendo de Briar…</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Filtro de pantalla detectado</string>
<string name="screen_filter_body">Las siguientes aplicaciones pueden mostrarse por encima de otras:\n\n%1$s \n\nBriar no reaccionará a los toques mientras otra aplicación se muestre encima.
Si experiencia algún problema, pruebe a desactivar esas aplicaciones cuando use Briar.\n</string>
<string name="checkbox_dont_show_again">No mostrar más para estas aplicaciones</string>
</resources> </resources>

View File

@@ -19,7 +19,7 @@
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le \"cloud\", par conséquent, votre mot de passe ne peut être réinitialisé. Voulez-vous supprimer votre compte et démarrer à nouveau ?\n\nAttention : vos identités, contacts et messages seront définitivement perdus.</string> <string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil et non sur le \"cloud\", par conséquent, votre mot de passe ne peut être réinitialisé. Voulez-vous supprimer votre compte et démarrer à nouveau ?\n\nAttention : vos identités, contacts et messages seront définitivement perdus.</string>
<string name="startup_failed_notification_title">Briar n\'a pas pu démarrer</string> <string name="startup_failed_notification_title">Briar n\'a pas pu démarrer</string>
<string name="startup_failed_notification_text">Vous devrez peut-être réinstaller Briar.</string> <string name="startup_failed_notification_text">Vous devrez peut-être réinstaller Briar.</string>
<string name="startup_failed_activity_title">Echec de démarrage de Briar.</string> <string name="startup_failed_activity_title">Échec de démarrage de Briar</string>
<string name="startup_failed_db_error">Pour une raison indéterminée, votre base de donnée Briar est corrompue et irrécupérable. Vos comptes, données et contacts sont perdus. Vous devez malheureusement réinstaller Briar et configurer un nouveau compte.</string> <string name="startup_failed_db_error">Pour une raison indéterminée, votre base de donnée Briar est corrompue et irrécupérable. Vos comptes, données et contacts sont perdus. Vous devez malheureusement réinstaller Briar et configurer un nouveau compte.</string>
<string name="startup_failed_service_error">Briar n\'a pas pu démarrer un module nécessaire. Réinstaller Briar résout généralement ce problème. Veuillez noter que vous perdrez votre compte et toutes les données associées puisque Briar n\'utilise pas de serveurs centralisés pour enregistrer les données.</string> <string name="startup_failed_service_error">Briar n\'a pas pu démarrer un module nécessaire. Réinstaller Briar résout généralement ce problème. Veuillez noter que vous perdrez votre compte et toutes les données associées puisque Briar n\'utilise pas de serveurs centralisés pour enregistrer les données.</string>
<string name="expiry_warning">Ce logiciel est arrivé à expiration.\nVeuillez installer une version plus récente.</string> <string name="expiry_warning">Ce logiciel est arrivé à expiration.\nVeuillez installer une version plus récente.</string>
@@ -151,7 +151,6 @@
<string name="groups_create_group_invitation_button">Envoyer invitation</string> <string name="groups_create_group_invitation_button">Envoyer invitation</string>
<string name="groups_create_group_hint">Ajouter un nom au groupe privé</string> <string name="groups_create_group_hint">Ajouter un nom au groupe privé</string>
<string name="groups_invitation_sent">Invitation envoyée au groupe</string> <string name="groups_invitation_sent">Invitation envoyée au groupe</string>
<string name="groups_compose_message">Composer message</string>
<string name="groups_message_sent">Message envoyé</string> <string name="groups_message_sent">Message envoyé</string>
<string name="groups_member_list">Liste de participants</string> <string name="groups_member_list">Liste de participants</string>
<string name="groups_invite_members">Inviter des participants</string> <string name="groups_invite_members">Inviter des participants</string>
@@ -182,6 +181,7 @@
<string name="groups_invitations_response_declined_sent">Vous avez refusé l\'invitation du groupe de %s.</string> <string name="groups_invitations_response_declined_sent">Vous avez refusé l\'invitation du groupe de %s.</string>
<string name="groups_invitations_response_accepted_received">%s a accepté l\'invitation du groupe.</string> <string name="groups_invitations_response_accepted_received">%s a accepté l\'invitation du groupe.</string>
<string name="groups_invitations_response_declined_received">%s a décliné l\'invitation du groupe.</string> <string name="groups_invitations_response_declined_received">%s a décliné l\'invitation du groupe.</string>
<string name="sharing_status_groups">Seul l\'initiateur du groupe peut inviter de nouveaux membres. Voici la liste des membres actuels.</string>
<!--Private Groups Revealing Contacts--> <!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Dévoiler les contacts</string> <string name="groups_reveal_contacts">Dévoiler les contacts</string>
<string name="groups_reveal_dialog_message">Vous pouvez choisir de dévoiler les contacts à tous les membres actuels et futurs de ce groupe.\n\nDévoiler les contacts vous assure une connexion au groupe plus rapide et plus fiable car vous pouvez communiquer avec les contacts dévoilés même si l\'initiateur du groupe est hors ligne.</string> <string name="groups_reveal_dialog_message">Vous pouvez choisir de dévoiler les contacts à tous les membres actuels et futurs de ce groupe.\n\nDévoiler les contacts vous assure une connexion au groupe plus rapide et plus fiable car vous pouvez communiquer avec les contacts dévoilés même si l\'initiateur du groupe est hors ligne.</string>
@@ -201,15 +201,10 @@
<item quantity="one">%d message</item> <item quantity="one">%d message</item>
<item quantity="other">%d messages</item> <item quantity="other">%d messages</item>
</plurals> </plurals>
<string name="forum_compose_post">Nouveau post de forum</string>
<string name="forum_new_entry_posted">Entrée de forum postée</string> <string name="forum_new_entry_posted">Entrée de forum postée</string>
<string name="forum_new_message_hint">Nouvelle entrée</string> <string name="forum_new_message_hint">Nouvelle entrée</string>
<string name="forum_message_reply_hint">Nouvelle réponse</string> <string name="forum_message_reply_hint">Nouvelle réponse</string>
<string name="btn_reply">Répondre</string> <string name="btn_reply">Répondre</string>
<plurals name="message_replies">
<item quantity="one">%1$d réponse</item>
<item quantity="other">%1$d réponses</item>
</plurals>
<string name="forum_leave">Quitter le forum</string> <string name="forum_leave">Quitter le forum</string>
<string name="dialog_title_leave_forum">Confirmer la sortie du forum</string> <string name="dialog_title_leave_forum">Confirmer la sortie du forum</string>
<string name="dialog_message_leave_forum">Êtes-vous sûr de vouloir quitter ce forum ? Les contacts avec qui vous l\'avez partagé pourraient ne plus recevoir ses mises à jour.</string> <string name="dialog_message_leave_forum">Êtes-vous sûr de vouloir quitter ce forum ? Les contacts avec qui vous l\'avez partagé pourraient ne plus recevoir ses mises à jour.</string>
@@ -236,6 +231,7 @@
<string name="forum_invitation_response_accepted_received">%s a accepté l\'invitation au forum.</string> <string name="forum_invitation_response_accepted_received">%s a accepté l\'invitation au forum.</string>
<string name="forum_invitation_response_declined_received">%s a décliné l\'invitation au forum.</string> <string name="forum_invitation_response_declined_received">%s a décliné l\'invitation au forum.</string>
<string name="sharing_status">Etat de partage</string> <string name="sharing_status">Etat de partage</string>
<string name="sharing_status_forum">Tous les participants d\'un forum peuvent le partager avec leurs contacts. Vous partagez ce forum avec les contacts suivants. Il y a peut-être d\'autres participants que vous ne pouvez voir.</string>
<string name="shared_with">Partagé avec %1$d (%2$d en ligne)</string> <string name="shared_with">Partagé avec %1$d (%2$d en ligne)</string>
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d forum partagé par des contacts</item> <item quantity="one">%d forum partagé par des contacts</item>
@@ -273,6 +269,7 @@
<string name="blogs_sharing_invitations_title">Invitations au blog</string> <string name="blogs_sharing_invitations_title">Invitations au blog</string>
<string name="blogs_sharing_joined_toast">Abonné au Blog</string> <string name="blogs_sharing_joined_toast">Abonné au Blog</string>
<string name="blogs_sharing_declined_toast">Invitation au blog refusée</string> <string name="blogs_sharing_declined_toast">Invitation au blog refusée</string>
<string name="sharing_status_blog">Quiconque est abonné à un blog peut le partager avec ses contacts. Vous partagez ce blog avec les contacts suivants. Il y a peut-être d\'autres abonnés que vous ne pouvez voir.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Import de flux RSS</string> <string name="blogs_rss_feeds_import">Import de flux RSS</string>
<string name="blogs_rss_feeds_import_button">Importer</string> <string name="blogs_rss_feeds_import_button">Importer</string>
@@ -282,6 +279,9 @@
<string name="blogs_rss_feeds_manage_imported">Importé :</string> <string name="blogs_rss_feeds_manage_imported">Importé :</string>
<string name="blogs_rss_feeds_manage_author">Auteur :</string> <string name="blogs_rss_feeds_manage_author">Auteur :</string>
<string name="blogs_rss_feeds_manage_updated">Dernière mise à jour :</string> <string name="blogs_rss_feeds_manage_updated">Dernière mise à jour :</string>
<string name="blogs_rss_remove_feed">Supprimer le flux</string>
<string name="blogs_rss_remove_feed_dialog_message">Êtes-vous sûr de vouloir supprimer ce flux et tous ses messages ?\nLes postes que vous avez partagés ne seront pas supprimés des appareils des autres personnes.</string>
<string name="blogs_rss_remove_feed_ok">Supprimer le flux</string>
<string name="blogs_rss_feeds_manage_delete_error">Le flux n\'a pas pu être supprimé !</string> <string name="blogs_rss_feeds_manage_delete_error">Le flux n\'a pas pu être supprimé !</string>
<string name="blogs_rss_feeds_manage_empty_state">Vous n\'avez importé aucun flux RSS.\n\nPourquoi ne pas cliquer le plus dans le coin en haut à droite pour ajouter votre premier ?</string> <string name="blogs_rss_feeds_manage_empty_state">Vous n\'avez importé aucun flux RSS.\n\nPourquoi ne pas cliquer le plus dans le coin en haut à droite pour ajouter votre premier ?</string>
<string name="blogs_rss_feeds_manage_error">Problème lors du chargement de vos flux. Réessayer ultérieurement.</string> <string name="blogs_rss_feeds_manage_error">Problème lors du chargement de vos flux. Réessayer ultérieurement.</string>
@@ -353,4 +353,9 @@
<string name="dev_report_saved">Rapport enregistré. Il sera envoyé lors de votre prochaine connexion à Briar.</string> <string name="dev_report_saved">Rapport enregistré. Il sera envoyé lors de votre prochaine connexion à Briar.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Déconnexion de Briar ...</string> <string name="progress_title_logout">Déconnexion de Briar ...</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Filtre d\'écran détecté</string>
<string name="screen_filter_body">Les applications suivantes ont l\'autorisation de s\'afficher par dessus d\'autres :\n\n%1$s \n\nBriar ne répondra pas aux touches lorsqu\'une autre application s\'affiche par dessus.
En cas de problèmes, essayer d\'arrêter ces applications durant l\'utilisation de Briar.\n</string>
<string name="checkbox_dont_show_again">Ne plus m\'avertir à propos de ces applications</string>
</resources> </resources>

View File

@@ -124,7 +124,6 @@
<string name="groups_create_group_invitation_button">Invia invito</string> <string name="groups_create_group_invitation_button">Invia invito</string>
<string name="groups_create_group_hint">Inserisci un nome per il tuo gruppo privato</string> <string name="groups_create_group_hint">Inserisci un nome per il tuo gruppo privato</string>
<string name="groups_invitation_sent">Invito a partecipare al gruppo spedito</string> <string name="groups_invitation_sent">Invito a partecipare al gruppo spedito</string>
<string name="groups_compose_message">Scrivi un messaggio</string>
<string name="groups_message_sent">Messaggio inviato</string> <string name="groups_message_sent">Messaggio inviato</string>
<string name="groups_member_list">Lista membri</string> <string name="groups_member_list">Lista membri</string>
<string name="groups_invite_members">Invita Membri</string> <string name="groups_invite_members">Invita Membri</string>
@@ -152,10 +151,6 @@
<item quantity="other">%d post</item> <item quantity="other">%d post</item>
</plurals> </plurals>
<string name="forum_message_reply_hint">Nuova Risposta</string> <string name="forum_message_reply_hint">Nuova Risposta</string>
<plurals name="message_replies">
<item quantity="one">risposta di %1$d</item>
<item quantity="other">risposte di %1$d</item>
</plurals>
<string name="forum_leave">Lascia Forum</string> <string name="forum_leave">Lascia Forum</string>
<string name="dialog_title_leave_forum">Conferma l\'abbandono del forum</string> <string name="dialog_title_leave_forum">Conferma l\'abbandono del forum</string>
<string name="dialog_button_leave">Lascia</string> <string name="dialog_button_leave">Lascia</string>
@@ -261,4 +256,5 @@
<string name="send_report">Invia report</string> <string name="send_report">Invia report</string>
<string name="close">Chiudi</string> <string name="close">Chiudi</string>
<!--Sign Out--> <!--Sign Out-->
<!--Screen Filters & Tapjacking-->
</resources> </resources>

View File

@@ -151,7 +151,6 @@
<string name="groups_create_group_invitation_button">Enviar Convite</string> <string name="groups_create_group_invitation_button">Enviar Convite</string>
<string name="groups_create_group_hint">Adicionar um nome para seu grupo privado</string> <string name="groups_create_group_hint">Adicionar um nome para seu grupo privado</string>
<string name="groups_invitation_sent">Convite do Grupo enviado </string> <string name="groups_invitation_sent">Convite do Grupo enviado </string>
<string name="groups_compose_message">Escrever Mensagem</string>
<string name="groups_message_sent">Mensagem enviada</string> <string name="groups_message_sent">Mensagem enviada</string>
<string name="groups_member_list">Lista de membros</string> <string name="groups_member_list">Lista de membros</string>
<string name="groups_invite_members">Convidar membros</string> <string name="groups_invite_members">Convidar membros</string>
@@ -201,15 +200,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">Nova postagem em fórum</string>
<string name="forum_new_entry_posted">postada com sucesso</string> <string name="forum_new_entry_posted">postada com sucesso</string>
<string name="forum_new_message_hint">Novo tópico</string> <string name="forum_new_message_hint">Novo tópico</string>
<string name="forum_message_reply_hint">Nova resposta</string> <string name="forum_message_reply_hint">Nova resposta</string>
<string name="btn_reply">Responder</string> <string name="btn_reply">Responder</string>
<plurals name="message_replies">
<item quantity="one">%1$d resposta</item>
<item quantity="other">%1$d respostas</item>
</plurals>
<string name="forum_leave">Sair do fórum</string> <string name="forum_leave">Sair do fórum</string>
<string name="dialog_title_leave_forum">Confirmar saída do fórum</string> <string name="dialog_title_leave_forum">Confirmar saída do fórum</string>
<string name="dialog_message_leave_forum">Você tem certeza que deseja sair deste fórum? Seus contatos com quem você o compartilhou podem deixar de receber notificações dele.</string> <string name="dialog_message_leave_forum">Você tem certeza que deseja sair deste fórum? Seus contatos com quem você o compartilhou podem deixar de receber notificações dele.</string>
@@ -349,4 +343,5 @@
<string name="dev_report_saved">Relatório salvo. Ele será enviado na próxima vez em que você entrar no Briar.</string> <string name="dev_report_saved">Relatório salvo. Ele será enviado na próxima vez em que você entrar no Briar.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Saindo do Briar…</string> <string name="progress_title_logout">Saindo do Briar…</string>
<!--Screen Filters & Tapjacking-->
</resources> </resources>

View File

@@ -151,7 +151,6 @@
<string name="groups_create_group_invitation_button">Dërgoje Ftesën</string> <string name="groups_create_group_invitation_button">Dërgoje Ftesën</string>
<string name="groups_create_group_hint">Shtoni një emër për grupin tuaj privat</string> <string name="groups_create_group_hint">Shtoni një emër për grupin tuaj privat</string>
<string name="groups_invitation_sent">Ftesa e grupit u dërgua</string> <string name="groups_invitation_sent">Ftesa e grupit u dërgua</string>
<string name="groups_compose_message">Hartoni Mesazh</string>
<string name="groups_message_sent">Mesazhi u dërgua</string> <string name="groups_message_sent">Mesazhi u dërgua</string>
<string name="groups_member_list">Listë Anëtarësh</string> <string name="groups_member_list">Listë Anëtarësh</string>
<string name="groups_invite_members">Ftoni Anëtarë</string> <string name="groups_invite_members">Ftoni Anëtarë</string>
@@ -182,6 +181,7 @@
<string name="groups_invitations_response_declined_sent">Hodhët poshtë ftesën e grupit nga %s.</string> <string name="groups_invitations_response_declined_sent">Hodhët poshtë ftesën e grupit nga %s.</string>
<string name="groups_invitations_response_accepted_received">%s e pranoi ftesën e grupit.</string> <string name="groups_invitations_response_accepted_received">%s e pranoi ftesën e grupit.</string>
<string name="groups_invitations_response_declined_received">%s e hodhi poshtë ftesën e grupit.</string> <string name="groups_invitations_response_declined_received">%s e hodhi poshtë ftesën e grupit.</string>
<string name="sharing_status_groups">Vetëm krijuesi mund të ftojë anëtarë të rinj në grup. Më poshtë gjenden tërë anëtarët e tanishëm të grupit.</string>
<!--Private Groups Revealing Contacts--> <!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Shfaqua Kontaktet</string> <string name="groups_reveal_contacts">Shfaqua Kontaktet</string>
<string name="groups_reveal_dialog_message">Mund të zgjidhni tua shfaqni ose jo kontaktet krejt anëtarëve të tanishëm dhe të ardhshëm të këtij grupi.\n\nShfaqja e kontakteve e bën lidhjen tuaj me grupin më të shpejtë dhe më të besueshme, ngaqë mund të komunikoni me kontaktet e shfaqura edhe kur krijuesi i grupit sështë në linjë.</string> <string name="groups_reveal_dialog_message">Mund të zgjidhni tua shfaqni ose jo kontaktet krejt anëtarëve të tanishëm dhe të ardhshëm të këtij grupi.\n\nShfaqja e kontakteve e bën lidhjen tuaj me grupin më të shpejtë dhe më të besueshme, ngaqë mund të komunikoni me kontaktet e shfaqura edhe kur krijuesi i grupit sështë në linjë.</string>
@@ -201,15 +201,10 @@
<item quantity="one">%d postim</item> <item quantity="one">%d postim</item>
<item quantity="other">%d postime</item> <item quantity="other">%d postime</item>
</plurals> </plurals>
<string name="forum_compose_post">Postim i Ri Forumi</string>
<string name="forum_new_entry_posted">U postua zë forumi</string> <string name="forum_new_entry_posted">U postua zë forumi</string>
<string name="forum_new_message_hint">Zë i Ri</string> <string name="forum_new_message_hint">Zë i Ri</string>
<string name="forum_message_reply_hint">Përgjigje e Re</string> <string name="forum_message_reply_hint">Përgjigje e Re</string>
<string name="btn_reply">Përgjigju</string> <string name="btn_reply">Përgjigju</string>
<plurals name="message_replies">
<item quantity="one">%1$d përgjigje</item>
<item quantity="other">%1$d përgjigje</item>
</plurals>
<string name="forum_leave">Braktiseni Forumin</string> <string name="forum_leave">Braktiseni Forumin</string>
<string name="dialog_title_leave_forum">Ripohoni Braktisjen e Forumit</string> <string name="dialog_title_leave_forum">Ripohoni Braktisjen e Forumit</string>
<string name="dialog_message_leave_forum">Jeni i sigurt se doni ta braktisni këtë forum? Kontaktet me të cilët e keni ndarë këtë forum mundet të mbeten jashtë marrjes së përditësimeve nga ky forum.</string> <string name="dialog_message_leave_forum">Jeni i sigurt se doni ta braktisni këtë forum? Kontaktet me të cilët e keni ndarë këtë forum mundet të mbeten jashtë marrjes së përditësimeve nga ky forum.</string>
@@ -236,6 +231,7 @@
<string name="forum_invitation_response_accepted_received">%s pranoi ftesën e forumit.</string> <string name="forum_invitation_response_accepted_received">%s pranoi ftesën e forumit.</string>
<string name="forum_invitation_response_declined_received">%s e hodhi poshtë ftesën e forumit.</string> <string name="forum_invitation_response_declined_received">%s e hodhi poshtë ftesën e forumit.</string>
<string name="sharing_status">Gjendje Ndarjeje Me të Tjerë</string> <string name="sharing_status">Gjendje Ndarjeje Me të Tjerë</string>
<string name="sharing_status_forum">Cilido anëtar i grupit mund ta ndajë me të tjerët. Këtë forum po e ndani me kontaktet vijuese. Mund të ketë edhe anëtarë të tjerët, të cilët smund ti shihni.</string>
<string name="shared_with">E ndarë me %1$d (%2$d në linjë)</string> <string name="shared_with">E ndarë me %1$d (%2$d në linjë)</string>
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d forum ndarë nga kontakte</item> <item quantity="one">%d forum ndarë nga kontakte</item>
@@ -273,6 +269,7 @@
<string name="blogs_sharing_invitations_title">Ftesa Blogu</string> <string name="blogs_sharing_invitations_title">Ftesa Blogu</string>
<string name="blogs_sharing_joined_toast">U pajtuat te Blogu</string> <string name="blogs_sharing_joined_toast">U pajtuat te Blogu</string>
<string name="blogs_sharing_declined_toast">Ftesa e Blogut u Hodh Poshtë</string> <string name="blogs_sharing_declined_toast">Ftesa e Blogut u Hodh Poshtë</string>
<string name="sharing_status_blog">Cilido që pajtohet te një blog mund ta ndajë atë me kontaktet e veta. Këtë blog po e ndani me kontaktet vijuese. Mund të ketë edhe pajtimtarë të tjerë, të cilët smund ti shihni.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importoni Prurje RSS</string> <string name="blogs_rss_feeds_import">Importoni Prurje RSS</string>
<string name="blogs_rss_feeds_import_button">Importo</string> <string name="blogs_rss_feeds_import_button">Importo</string>
@@ -282,6 +279,9 @@
<string name="blogs_rss_feeds_manage_imported">Të importuara:</string> <string name="blogs_rss_feeds_manage_imported">Të importuara:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string> <string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Përditësuar Së Fundi:</string> <string name="blogs_rss_feeds_manage_updated">Përditësuar Së Fundi:</string>
<string name="blogs_rss_remove_feed">Hiqe Prurjen</string>
<string name="blogs_rss_remove_feed_dialog_message">Jeni i sigurt se doni të hiqet kjo prurje dhe krejt postimet e saj?\nÇfarëdo postimi që keni ndarë me të tjerët, nuk do të hiqet nga pajisjet e personave të tjerë.</string>
<string name="blogs_rss_remove_feed_ok">Hiqe Prurjen</string>
<string name="blogs_rss_feeds_manage_delete_error">Su fshi dot prurja!</string> <string name="blogs_rss_feeds_manage_delete_error">Su fshi dot prurja!</string>
<string name="blogs_rss_feeds_manage_empty_state">Skeni importuar ende ndonjë prurje RSS.\n\nPse nuk klikoni mbi shenjën plus në cepin e sipërm djathtas që të shtoni të parën tuaj?</string> <string name="blogs_rss_feeds_manage_empty_state">Skeni importuar ende ndonjë prurje RSS.\n\nPse nuk klikoni mbi shenjën plus në cepin e sipërm djathtas që të shtoni të parën tuaj?</string>
<string name="blogs_rss_feeds_manage_error">Pati një problem me ngarkimin e prurjeve tuaja. Ju lutemi, riprovoni më vonë.</string> <string name="blogs_rss_feeds_manage_error">Pati një problem me ngarkimin e prurjeve tuaja. Ju lutemi, riprovoni më vonë.</string>
@@ -353,4 +353,9 @@
<string name="dev_report_saved">Njoftimi u ruajt. Do të dërgohet herën tjetër që do të hyni në Briar.</string> <string name="dev_report_saved">Njoftimi u ruajt. Do të dërgohet herën tjetër që do të hyni në Briar.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Po dilet nga Briar-i…</string> <string name="progress_title_logout">Po dilet nga Briar-i…</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">U pikas filtër ekrani</string>
<string name="screen_filter_body">Aplikacionet vijuese kanë leje të vizatojnë përmbi aplikacione të tjera:\n\n%1$s \n\nBriar-i nuk do të reagojë ndaj prekjesh, kur mbi të po vizaton një tjetër aplikacion.
Nëse hasni probleme, provoni ti mbyllni këto aplikacione, kur përdorni Briar-in.\n</string>
<string name="checkbox_dont_show_again">Mos më sinjalizo më për këto aplikacione</string>
</resources> </resources>

View File

@@ -5,14 +5,8 @@
<item name="colorPrimary">@color/briar_primary</item> <item name="colorPrimary">@color/briar_primary</item>
<item name="colorPrimaryDark">@color/briar_primary_dark</item> <item name="colorPrimaryDark">@color/briar_primary_dark</item>
<item name="colorAccent">@color/briar_accent</item> <item name="colorAccent">@color/briar_accent</item>
<item name="android:windowBackground">@color/window_background</item>
<item name="android:textColorPrimary">@color/briar_text_primary</item>
<item name="android:textColorPrimaryInverse">@color/briar_text_primary_inverse</item>
<item name="android:textColorSecondary">@color/briar_text_secondary</item>
<item name="android:textColorSecondaryInverse">@color/briar_text_secondary_inverse</item>
<item name="android:textColorTertiary">@color/briar_text_tertiary</item>
<item name="android:textColorTertiaryInverse">@color/briar_text_tertiary_inverse</item>
<item name="android:textColorLink">@color/briar_text_link</item> <item name="android:textColorLink">@color/briar_text_link</item>
<item name="android:windowBackground">@color/window_background</item>
<item name="android:windowAnimationStyle">@style/ActivityAnimation</item> <item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
<item name="android:filterTouchesWhenObscured">true</item> <item name="android:filterTouchesWhenObscured">true</item>
@@ -40,12 +34,6 @@
<item name="colorAccent">@color/briar_accent</item> <item name="colorAccent">@color/briar_accent</item>
<item name="buttonBarPositiveButtonStyle">@style/BriarButtonFlat.Positive</item> <item name="buttonBarPositiveButtonStyle">@style/BriarButtonFlat.Positive</item>
<item name="buttonBarNegativeButtonStyle">@style/BriarButtonFlat.Negative</item> <item name="buttonBarNegativeButtonStyle">@style/BriarButtonFlat.Negative</item>
<item name="android:textColorPrimary">@color/briar_text_primary</item>
<item name="android:textColorPrimaryInverse">@color/briar_text_primary_inverse</item>
<item name="android:textColorSecondary">@color/briar_text_secondary</item>
<item name="android:textColorSecondaryInverse">@color/briar_text_secondary_inverse</item>
<item name="android:textColorTertiary">@color/briar_text_tertiary</item>
<item name="android:textColorTertiaryInverse">@color/briar_text_tertiary_inverse</item>
<item name="android:textColorLink">@color/briar_text_link</item> <item name="android:textColorLink">@color/briar_text_link</item>
<item name="android:windowAnimationStyle">@style/DialogAnimation</item> <item name="android:windowAnimationStyle">@style/DialogAnimation</item>
<item name="android:filterTouchesWhenObscured">true</item> <item name="android:filterTouchesWhenObscured">true</item>

View File

@@ -13,6 +13,8 @@ import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.android.TestBriarApplication; import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.threaded.ThreadItemAdapter; import org.briarproject.briar.android.threaded.ThreadItemAdapter;
import org.briarproject.briar.android.threaded.ThreadItemList;
import org.briarproject.briar.android.threaded.ThreadItemListImpl;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -23,10 +25,7 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
@@ -81,7 +80,7 @@ public class ForumActivityTest {
private TestForumActivity forumActivity; private TestForumActivity forumActivity;
@Captor @Captor
private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumItem>, DbException>> private ArgumentCaptor<UiResultExceptionHandler<ThreadItemList<ForumItem>, DbException>>
rc; rc;
@Before @Before
@@ -93,7 +92,7 @@ public class ForumActivityTest {
.withIntent(intent).create().resume().get(); .withIntent(intent).create().resume().get();
} }
private List<ForumItem> getDummyData() { private ThreadItemList<ForumItem> getDummyData() {
ForumItem[] forumItems = new ForumItem[6]; ForumItem[] forumItems = new ForumItem[6];
for (int i = 0; i < forumItems.length; i++) { for (int i = 0; i < forumItems.length; i++) {
AuthorId authorId = new AuthorId(TestUtils.getRandomId()); AuthorId authorId = new AuthorId(TestUtils.getRandomId());
@@ -103,13 +102,15 @@ public class ForumActivityTest {
AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN); AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN);
forumItems[i].setLevel(LEVELS[i]); forumItems[i].setLevel(LEVELS[i]);
} }
return new ArrayList<>(Arrays.asList(forumItems)); ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
list.addAll(Arrays.asList(forumItems));
return list;
} }
@Test @Test
public void testNestedEntries() { public void testNestedEntries() {
ForumController mc = forumActivity.getController(); ForumController mc = forumActivity.getController();
List<ForumItem> dummyData = getDummyData(); ThreadItemList<ForumItem> dummyData = getDummyData();
verify(mc, times(1)).loadItems(rc.capture()); verify(mc, times(1)).loadItems(rc.capture());
rc.getValue().onResult(dummyData); rc.getValue().onResult(dummyData);
ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter(); ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter();

View File

@@ -34,7 +34,7 @@ public interface BlogManager {
/** /**
* Returns true if a blog can be removed. * Returns true if a blog can be removed.
*/ */
boolean canBeRemoved(GroupId g) throws DbException; boolean canBeRemoved(Blog b) throws DbException;
/** /**
* Removes and deletes a blog. * Removes and deletes a blog.
@@ -60,7 +60,7 @@ public interface BlogManager {
* Adds a comment to an existing blog post or reblogs it. * Adds a comment to an existing blog post or reblogs it.
*/ */
void addLocalComment(LocalAuthor author, GroupId groupId, void addLocalComment(LocalAuthor author, GroupId groupId,
@Nullable String comment, BlogPostHeader wHeader) @Nullable String comment, BlogPostHeader parentHeader)
throws DbException; throws DbException;
/** /**

View File

@@ -25,7 +25,8 @@ public interface BlogPostFactory {
throws FormatException, GeneralSecurityException; throws FormatException, GeneralSecurityException;
Message createBlogComment(GroupId groupId, LocalAuthor author, Message createBlogComment(GroupId groupId, LocalAuthor author,
@Nullable String comment, MessageId originalId, MessageId wrappedId) @Nullable String comment, MessageId parentOriginalId,
MessageId parentCurrentId)
throws FormatException, GeneralSecurityException; throws FormatException, GeneralSecurityException;
/** /**
@@ -44,11 +45,11 @@ public interface BlogPostFactory {
* Wraps a blog comment * Wraps a blog comment
*/ */
Message wrapComment(GroupId groupId, byte[] descriptor, long timestamp, Message wrapComment(GroupId groupId, byte[] descriptor, long timestamp,
BdfList body, MessageId currentId) throws FormatException; BdfList body, MessageId parentCurrentId) throws FormatException;
/** /**
* Re-wraps a previously wrapped comment * Re-wraps a previously wrapped comment
*/ */
Message rewrapWrappedComment(GroupId groupId, BdfList body, Message rewrapWrappedComment(GroupId groupId, BdfList body,
MessageId currentId) throws FormatException; MessageId parentCurrentId) throws FormatException;
} }

View File

@@ -7,6 +7,8 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface MessageTracker { public interface MessageTracker {
@@ -38,6 +40,19 @@ public interface MessageTracker {
void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read) void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read)
throws DbException; throws DbException;
/**
* Loads the stored message id for the respective group id or returns null
* if none is available.
*/
@Nullable
MessageId loadStoredMessageId(GroupId g) throws DbException;
/**
* Stores the message id for the respective group id. Exactly one message id
* can be stored for any group id at any time, older values are overwritten.
*/
void storeMessageId(GroupId g, MessageId m) throws DbException;
/** /**
* Marks a message as read or unread and updates the group count. * Marks a message as read or unread and updates the group count.
*/ */

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.blog;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
@@ -76,7 +75,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
AddContactHook, RemoveContactHook, Client { AddContactHook, RemoveContactHook, Client {
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final ContactManager contactManager;
private final BlogFactory blogFactory; private final BlogFactory blogFactory;
private final BlogPostFactory blogPostFactory; private final BlogPostFactory blogPostFactory;
private final List<RemoveBlogHook> removeHooks; private final List<RemoveBlogHook> removeHooks;
@@ -84,12 +82,10 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Inject @Inject
BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager, BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
ClientHelper clientHelper, MetadataParser metadataParser, ClientHelper clientHelper, MetadataParser metadataParser,
ContactManager contactManager, BlogFactory blogFactory, BlogFactory blogFactory, BlogPostFactory blogPostFactory) {
BlogPostFactory blogPostFactory) {
super(db, clientHelper, metadataParser); super(db, clientHelper, metadataParser);
this.identityManager = identityManager; this.identityManager = identityManager;
this.contactManager = contactManager;
this.blogFactory = blogFactory; this.blogFactory = blogFactory;
this.blogPostFactory = blogPostFactory; this.blogPostFactory = blogPostFactory;
removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>(); removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>();
@@ -120,7 +116,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public void removingContact(Transaction txn, Contact c) throws DbException { public void removingContact(Transaction txn, Contact c) throws DbException {
Blog b = blogFactory.createBlog(c.getAuthor()); Blog b = blogFactory.createBlog(c.getAuthor());
removeBlog(txn, b, true); removeBlog(txn, b);
} }
@Override @Override
@@ -191,10 +187,10 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
@Override @Override
public boolean canBeRemoved(GroupId g) throws DbException { public boolean canBeRemoved(Blog b) throws DbException {
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
boolean canBeRemoved = canBeRemoved(txn, g); boolean canBeRemoved = canBeRemoved(txn, b);
db.commitTransaction(txn); db.commitTransaction(txn);
return canBeRemoved; return canBeRemoved;
} finally { } finally {
@@ -202,23 +198,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} }
} }
private boolean canBeRemoved(Transaction txn, GroupId g) private boolean canBeRemoved(Transaction txn, Blog b)
throws DbException { throws DbException {
boolean canBeRemoved;
Blog b = getBlog(txn, g);
AuthorId authorId = b.getAuthor().getId(); AuthorId authorId = b.getAuthor().getId();
LocalAuthor localAuthor = identityManager.getLocalAuthor(txn); LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
if (localAuthor.getId().equals(authorId)) return false; return !localAuthor.getId().equals(authorId);
canBeRemoved = !contactManager
.contactExists(txn, authorId, localAuthor.getId());
return canBeRemoved;
} }
@Override @Override
public void removeBlog(Blog b) throws DbException { public void removeBlog(Blog b) throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
removeBlog(txn, b, false); removeBlog(txn, b);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -227,12 +218,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public void removeBlog(Transaction txn, Blog b) throws DbException { public void removeBlog(Transaction txn, Blog b) throws DbException {
removeBlog(txn, b, false); if (!canBeRemoved(txn, b))
}
private void removeBlog(Transaction txn, Blog b, boolean forced)
throws DbException {
if (!forced && !canBeRemoved(txn, b.getId()))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
for (RemoveBlogHook hook : removeHooks) for (RemoveBlogHook hook : removeHooks)
hook.removingBlog(txn, b); hook.removingBlog(txn, b);
@@ -246,7 +232,6 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
addLocalPost(txn, p); addLocalPost(txn, p);
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
//noinspection ThrowFromFinallyBlock
db.endTransaction(txn); db.endTransaction(txn);
} }
} }
@@ -269,8 +254,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
MessageId postId = p.getMessage().getId(); MessageId postId = p.getMessage().getId();
BlogPostHeader h = BlogPostHeader h =
getPostHeaderFromMetadata(txn, groupId, postId, meta); getPostHeaderFromMetadata(txn, groupId, postId, meta);
BlogPostAddedEvent event = BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
new BlogPostAddedEvent(groupId, h, true);
txn.attach(event); txn.attach(event);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
@@ -279,42 +263,39 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
@Override @Override
public void addLocalComment(LocalAuthor author, GroupId groupId, public void addLocalComment(LocalAuthor author, GroupId groupId,
@Nullable String comment, BlogPostHeader pOriginalHeader) @Nullable String comment, BlogPostHeader parentHeader)
throws DbException { throws DbException {
MessageType type = pOriginalHeader.getType(); MessageType type = parentHeader.getType();
if (type != POST && type != COMMENT) if (type != POST && type != COMMENT)
throw new IllegalArgumentException("Comment on unknown type!"); throw new IllegalArgumentException("Comment on unknown type!");
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
// Wrap post that we are commenting on // Wrap post that we are commenting on
MessageId parentId = wrapMessage(txn, groupId, pOriginalHeader); MessageId parentOriginalId =
getOriginalMessageId(txn, parentHeader);
// Get ID of new parent's original message. MessageId parentCurrentId =
// Assumes that pOriginalHeader is a POST or COMMENT wrapMessage(txn, groupId, parentHeader, parentOriginalId);
MessageId pOriginalId = pOriginalHeader.getId();
// Create actual comment // Create actual comment
Message message = blogPostFactory Message message = blogPostFactory.createBlogComment(groupId, author,
.createBlogComment(groupId, author, comment, pOriginalId, comment, parentOriginalId, parentCurrentId);
parentId);
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
meta.put(KEY_TYPE, COMMENT.getInt()); meta.put(KEY_TYPE, COMMENT.getInt());
if (comment != null) meta.put(KEY_COMMENT, comment); if (comment != null) meta.put(KEY_COMMENT, comment);
meta.put(KEY_TIMESTAMP, message.getTimestamp()); meta.put(KEY_TIMESTAMP, message.getTimestamp());
meta.put(KEY_ORIGINAL_MSG_ID, message.getId()); meta.put(KEY_ORIGINAL_MSG_ID, message.getId());
meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId); meta.put(KEY_ORIGINAL_PARENT_MSG_ID, parentOriginalId);
meta.put(KEY_PARENT_MSG_ID, parentId); meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
meta.put(KEY_AUTHOR, authorToBdfDictionary(author)); meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
// Send comment // Send comment
clientHelper.addLocalMessage(txn, message, meta, true); clientHelper.addLocalMessage(txn, message, meta, true);
// broadcast event // broadcast event
BlogPostHeader h = BlogPostHeader h = getPostHeaderFromMetadata(txn, groupId,
getPostHeaderFromMetadata(txn, groupId, message.getId(), message.getId(), meta);
meta);
BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true); BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
txn.attach(event); txn.attach(event);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -323,81 +304,94 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
throw new IllegalArgumentException("Invalid key of author", e); throw new IllegalArgumentException("Invalid key of author", e);
} finally { } finally {
//noinspection ThrowFromFinallyBlock
db.endTransaction(txn); db.endTransaction(txn);
} }
} }
private MessageId getOriginalMessageId(Transaction txn, BlogPostHeader h)
throws DbException, FormatException {
MessageType type = h.getType();
if (type == POST || type == COMMENT) return h.getId();
BdfDictionary meta = clientHelper.getMessageMetadataAsDictionary(txn,
h.getId());
return new MessageId(meta.getRaw(KEY_ORIGINAL_MSG_ID));
}
private MessageId wrapMessage(Transaction txn, GroupId groupId, private MessageId wrapMessage(Transaction txn, GroupId groupId,
BlogPostHeader pOriginalHeader) BlogPostHeader header, MessageId originalId)
throws DbException, FormatException { throws DbException, FormatException {
if (groupId.equals(pOriginalHeader.getGroupId())) { if (groupId.equals(header.getGroupId())) {
// We are trying to wrap a post that is already in our group. // We are trying to wrap a post that is already in our group.
// This is unnecessary, so just return the post's MessageId // This is unnecessary, so just return the post's MessageId
return pOriginalHeader.getId(); return header.getId();
} }
// Get body of message to be wrapped // Get body of message to be wrapped
BdfList body = BdfList body = clientHelper.getMessageAsList(txn, header.getId());
clientHelper.getMessageAsList(txn, pOriginalHeader.getId());
if (body == null) throw new DbException(); if (body == null) throw new DbException();
long wTimestamp = pOriginalHeader.getTimestamp(); long timestamp = header.getTimestamp();
Message wMessage; Message wrappedMessage;
BdfDictionary meta = new BdfDictionary(); BdfDictionary meta = new BdfDictionary();
MessageType type = pOriginalHeader.getType(); MessageType type = header.getType();
if (type == POST) { if (type == POST) {
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
byte[] wDescriptor = wGroup.getDescriptor();
// Wrap post // Wrap post
wMessage = blogPostFactory Group group = db.getGroup(txn, header.getGroupId());
.wrapPost(groupId, wDescriptor, wTimestamp, body); byte[] descriptor = group.getDescriptor();
wrappedMessage = blogPostFactory.wrapPost(groupId, descriptor,
timestamp, body);
meta.put(KEY_TYPE, WRAPPED_POST.getInt()); meta.put(KEY_TYPE, WRAPPED_POST.getInt());
meta.put(KEY_RSS_FEED, pOriginalHeader.isRssFeed()); meta.put(KEY_RSS_FEED, header.isRssFeed());
} else if (type == COMMENT) { } else if (type == COMMENT) {
Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId()); // Recursively wrap parent
byte[] wDescriptor = wGroup.getDescriptor(); BlogCommentHeader commentHeader = (BlogCommentHeader) header;
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; BlogPostHeader parentHeader = commentHeader.getParent();
MessageId wrappedId = MessageId parentOriginalId =
wrapMessage(txn, groupId, wComment.getParent()); getOriginalMessageId(txn, parentHeader);
MessageId parentCurrentId =
wrapMessage(txn, groupId, parentHeader, parentOriginalId);
// Wrap comment // Wrap comment
wMessage = blogPostFactory Group group = db.getGroup(txn, header.getGroupId());
.wrapComment(groupId, wDescriptor, wTimestamp, byte[] descriptor = group.getDescriptor();
body, wrappedId); wrappedMessage = blogPostFactory.wrapComment(groupId, descriptor,
timestamp, body, parentCurrentId);
meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
if (wComment.getComment() != null) if (commentHeader.getComment() != null)
meta.put(KEY_COMMENT, wComment.getComment()); meta.put(KEY_COMMENT, commentHeader.getComment());
meta.put(KEY_PARENT_MSG_ID, wrappedId); meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
} else if (type == WRAPPED_POST) { } else if (type == WRAPPED_POST) {
// Re-wrap wrapped post without adding another wrapping layer // Re-wrap wrapped post without adding another wrapping layer
wMessage = blogPostFactory.rewrapWrappedPost(groupId, body); wrappedMessage = 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()); meta.put(KEY_RSS_FEED, header.isRssFeed());
} else if (type == WRAPPED_COMMENT) { } else if (type == WRAPPED_COMMENT) {
BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader; // Recursively wrap parent
MessageId wrappedId = BlogCommentHeader commentHeader = (BlogCommentHeader) header;
wrapMessage(txn, groupId, wComment.getParent()); BlogPostHeader parentHeader = commentHeader.getParent();
MessageId parentOriginalId =
getOriginalMessageId(txn, parentHeader);
MessageId parentCurrentId =
wrapMessage(txn, groupId, parentHeader, parentOriginalId);
// Re-wrap wrapped comment // Re-wrap wrapped comment
wMessage = blogPostFactory wrappedMessage = blogPostFactory.rewrapWrappedComment(groupId, body,
.rewrapWrappedComment(groupId, body, wrappedId); parentCurrentId);
meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt()); meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
if (wComment.getComment() != null) if (commentHeader.getComment() != null)
meta.put(KEY_COMMENT, wComment.getComment()); meta.put(KEY_COMMENT, commentHeader.getComment());
meta.put(KEY_PARENT_MSG_ID, wrappedId); meta.put(KEY_PARENT_MSG_ID, parentCurrentId);
} else { } else {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Unknown Message Type: " + type); "Unknown Message Type: " + type);
} }
meta.put(KEY_ORIGINAL_MSG_ID, pOriginalHeader.getId()); meta.put(KEY_ORIGINAL_MSG_ID, originalId);
meta.put(KEY_AUTHOR, meta.put(KEY_AUTHOR, authorToBdfDictionary(header.getAuthor()));
authorToBdfDictionary(pOriginalHeader.getAuthor())); meta.put(KEY_TIMESTAMP, header.getTimestamp());
meta.put(KEY_TIMESTAMP, pOriginalHeader.getTimestamp()); meta.put(KEY_TIME_RECEIVED, header.getTimeReceived());
meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
// Send wrapped message and store metadata // Send wrapped message and store metadata
clientHelper.addLocalMessage(txn, wMessage, meta, true); clientHelper.addLocalMessage(txn, wrappedMessage, meta, true);
return wMessage.getId(); return wrappedMessage.getId();
} }
@Override @Override

View File

@@ -65,7 +65,8 @@ class BlogPostFactoryImpl implements BlogPostFactory {
@Override @Override
public Message createBlogComment(GroupId groupId, LocalAuthor author, public Message createBlogComment(GroupId groupId, LocalAuthor author,
@Nullable String comment, MessageId pOriginalId, MessageId parentId) @Nullable String comment, MessageId parentOriginalId,
MessageId parentCurrentId)
throws FormatException, GeneralSecurityException { throws FormatException, GeneralSecurityException {
if (comment != null) { if (comment != null) {
@@ -78,22 +79,20 @@ class BlogPostFactoryImpl implements BlogPostFactory {
long timestamp = clock.currentTimeMillis(); long timestamp = clock.currentTimeMillis();
// Generate the signature // Generate the signature
BdfList signed = BdfList signed = BdfList.of(groupId, timestamp, comment,
BdfList.of(groupId, timestamp, comment, pOriginalId, parentId); parentOriginalId, parentCurrentId);
byte[] sig = clientHelper byte[] sig = clientHelper
.sign(SIGNING_LABEL_COMMENT, signed, author.getPrivateKey()); .sign(SIGNING_LABEL_COMMENT, signed, author.getPrivateKey());
// Serialise the signed message // Serialise the signed message
BdfList message = BdfList message = BdfList.of(COMMENT.getInt(), comment,
BdfList.of(COMMENT.getInt(), comment, pOriginalId, parentId, parentOriginalId, parentCurrentId, sig);
sig);
return clientHelper.createMessage(groupId, timestamp, message); return clientHelper.createMessage(groupId, timestamp, message);
} }
@Override @Override
public Message wrapPost(GroupId groupId, byte[] descriptor, public Message wrapPost(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body) long timestamp, BdfList body) throws FormatException {
throws FormatException {
if (getType(body) != POST) if (getType(body) != POST)
throw new IllegalArgumentException("Needs to wrap a POST"); throw new IllegalArgumentException("Needs to wrap a POST");
@@ -101,9 +100,8 @@ class BlogPostFactoryImpl implements BlogPostFactory {
// Serialise the message // Serialise the message
String content = body.getString(1); String content = body.getString(1);
byte[] signature = body.getRaw(2); byte[] signature = body.getRaw(2);
BdfList message = BdfList message = BdfList.of(WRAPPED_POST.getInt(), descriptor,
BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp, timestamp, content, signature);
content, signature);
return clientHelper return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message); .createMessage(groupId, clock.currentTimeMillis(), message);
} }
@@ -120,16 +118,15 @@ class BlogPostFactoryImpl implements BlogPostFactory {
long timestamp = body.getLong(2); long timestamp = body.getLong(2);
String content = body.getString(3); String content = body.getString(3);
byte[] signature = body.getRaw(4); byte[] signature = body.getRaw(4);
BdfList message = BdfList message = BdfList.of(WRAPPED_POST.getInt(), descriptor,
BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp, timestamp, content, signature);
content, signature);
return clientHelper return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message); .createMessage(groupId, clock.currentTimeMillis(), message);
} }
@Override @Override
public Message wrapComment(GroupId groupId, byte[] descriptor, public Message wrapComment(GroupId groupId, byte[] descriptor,
long timestamp, BdfList body, MessageId parentId) long timestamp, BdfList body, MessageId parentCurrentId)
throws FormatException { throws FormatException {
if (getType(body) != COMMENT) if (getType(body) != COMMENT)
@@ -140,16 +137,16 @@ class BlogPostFactoryImpl implements BlogPostFactory {
byte[] pOriginalId = body.getRaw(2); byte[] pOriginalId = body.getRaw(2);
byte[] oldParentId = body.getRaw(3); byte[] oldParentId = body.getRaw(3);
byte[] signature = body.getRaw(4); byte[] signature = body.getRaw(4);
BdfList message = BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, timestamp, comment, pOriginalId, oldParentId, signature,
comment, pOriginalId, oldParentId, signature, parentId); parentCurrentId);
return clientHelper return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message); .createMessage(groupId, clock.currentTimeMillis(), message);
} }
@Override @Override
public Message rewrapWrappedComment(GroupId groupId, BdfList body, public Message rewrapWrappedComment(GroupId groupId, BdfList body,
MessageId parentId) throws FormatException { MessageId parentCurrentId) throws FormatException {
if (getType(body) != WRAPPED_COMMENT) if (getType(body) != WRAPPED_COMMENT)
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -162,9 +159,9 @@ class BlogPostFactoryImpl implements BlogPostFactory {
byte[] pOriginalId = body.getRaw(4); byte[] pOriginalId = body.getRaw(4);
byte[] oldParentId = body.getRaw(5); byte[] oldParentId = body.getRaw(5);
byte[] signature = body.getRaw(6); byte[] signature = body.getRaw(6);
BdfList message = BdfList message = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp, timestamp, comment, pOriginalId, oldParentId, signature,
comment, pOriginalId, oldParentId, signature, parentId); parentCurrentId);
return clientHelper return clientHelper
.createMessage(groupId, clock.currentTimeMillis(), message); .createMessage(groupId, clock.currentTimeMillis(), message);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.client;
public interface MessageTrackerConstants { public interface MessageTrackerConstants {
String GROUP_KEY_STORED_MESSAGE_ID = "storedMessageId";
String GROUP_KEY_MSG_COUNT = "messageCount"; String GROUP_KEY_MSG_COUNT = "messageCount";
String GROUP_KEY_UNREAD_COUNT = "unreadCount"; String GROUP_KEY_UNREAD_COUNT = "unreadCount";
String GROUP_KEY_LATEST_MSG = "latestMessageTime"; String GROUP_KEY_LATEST_MSG = "latestMessageTime";

View File

@@ -13,11 +13,13 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_LATEST_MSG; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_LATEST_MSG;
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_MSG_COUNT; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_MSG_COUNT;
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID;
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_UNREAD_COUNT; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_UNREAD_COUNT;
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
@@ -57,6 +59,30 @@ class MessageTrackerImpl implements MessageTracker {
latestMsgTime)); latestMsgTime));
} }
@Nullable
@Override
public MessageId loadStoredMessageId(GroupId g) throws DbException {
try {
BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(g);
byte[] msgBytes = d.getOptionalRaw(GROUP_KEY_STORED_MESSAGE_ID);
return msgBytes != null ? new MessageId(msgBytes) : null;
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public void storeMessageId(GroupId g, MessageId m) throws DbException {
BdfDictionary d = BdfDictionary.of(
new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, m)
);
try {
clientHelper.mergeGroupMetadata(g, d);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override @Override
public GroupCount getGroupCount(GroupId g) throws DbException { public GroupCount getGroupCount(GroupId g) throws DbException {
GroupCount count; GroupCount count;

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.sharing; package org.briarproject.briar.sharing;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
@@ -11,7 +12,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.ClientId; import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogInvitationResponse; import org.briarproject.briar.api.blog.BlogInvitationResponse;
import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogManager;
@@ -52,18 +52,17 @@ class BlogSharingManagerImpl extends SharingManagerImpl<Blog>
} }
@Override @Override
protected boolean canBeShared(Transaction txn, GroupId shareableId, public void addingContact(Transaction txn, Contact c) throws DbException {
Contact c) throws DbException { super.addingContact(txn, c);
// check if shareableId belongs to our personal blog LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
LocalAuthor author = identityManager.getLocalAuthor(txn); Blog ourBlog = blogManager.getPersonalBlog(localAuthor);
Blog b = blogManager.getPersonalBlog(author); Blog theirBlog = blogManager.getPersonalBlog(c.getAuthor());
if (b.getId().equals(shareableId)) return false; try {
initializeSharedSession(txn, c, ourBlog);
// check if shareableId belongs to c's personal blog initializeSharedSession(txn, c, theirBlog);
b = blogManager.getPersonalBlog(c.getAuthor()); } catch (FormatException e) {
if (b.getId().equals(shareableId)) return false; throw new DbException(e);
}
return super.canBeShared(txn, shareableId, c);
} }
@Override @Override

View File

@@ -48,6 +48,7 @@ import static org.briarproject.briar.sharing.MessageType.DECLINE;
import static org.briarproject.briar.sharing.MessageType.INVITE; import static org.briarproject.briar.sharing.MessageType.INVITE;
import static org.briarproject.briar.sharing.MessageType.LEAVE; import static org.briarproject.briar.sharing.MessageType.LEAVE;
import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID; import static org.briarproject.briar.sharing.SharingConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.briar.sharing.State.SHARING;
@NotNullByDefault @NotNullByDefault
abstract class SharingManagerImpl<S extends Shareable> abstract class SharingManagerImpl<S extends Shareable>
@@ -138,6 +139,16 @@ abstract class SharingManagerImpl<S extends Shareable>
return false; return false;
} }
protected void initializeSharedSession(Transaction txn, Contact c,
S shareable) throws DbException, FormatException {
GroupId contactGroupId = getContactGroup(c).getId();
Session session =
new Session(SHARING, contactGroupId, shareable.getId(), null,
null, 0, 0);
MessageId storageId = createStorageId(txn, contactGroupId);
storeSession(txn, storageId, session);
}
private SessionId getSessionId(GroupId shareableId) { private SessionId getSessionId(GroupId shareableId) {
return new SessionId(shareableId.getBytes()); return new SessionId(shareableId.getBytes());
} }
@@ -414,7 +425,7 @@ abstract class SharingManagerImpl<S extends Shareable>
} }
} }
protected boolean canBeShared(Transaction txn, GroupId g, Contact c) private boolean canBeShared(Transaction txn, GroupId g, Contact c)
throws DbException { throws DbException {
GroupId contactGroupId = getContactGroup(c).getId(); GroupId contactGroupId = getContactGroup(c).getId();
SessionId sessionId = getSessionId(g); SessionId sessionId = getSessionId(g);

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper; import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry; import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
@@ -21,6 +20,7 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.Blog; import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogFactory; import org.briarproject.briar.api.blog.BlogFactory;
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;
@@ -34,24 +34,38 @@ import org.junit.Test;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import static org.briarproject.bramble.api.identity.Author.Status.NONE;
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED; import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getRandomString;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR; import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID; 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_COMMENT;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_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_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;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_COMMENT_LENGTH;
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID; import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
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;
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT;
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_POST;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class BlogManagerImplTest extends BriarTestCase { public class BlogManagerImplTest extends BriarTestCase {
@@ -62,24 +76,41 @@ public class BlogManagerImplTest extends BriarTestCase {
private final IdentityManager identityManager = private final IdentityManager identityManager =
context.mock(IdentityManager.class); context.mock(IdentityManager.class);
private final ClientHelper clientHelper = context.mock(ClientHelper.class); private final ClientHelper clientHelper = context.mock(ClientHelper.class);
private final ContactManager contactManager =
context.mock(ContactManager.class);
private final BlogFactory blogFactory = context.mock(BlogFactory.class); private final BlogFactory blogFactory = context.mock(BlogFactory.class);
private final BlogPostFactory blogPostFactory =
context.mock(BlogPostFactory.class);
private final Blog blog1, blog2; private final LocalAuthor localAuthor1, localAuthor2, rssLocalAuthor;
private final Message message; private final BdfDictionary authorDict1, authorDict2, rssAuthorDict;
private final MessageId messageId; private final Blog blog1, blog2, rssBlog;
private final long timestamp, timeReceived;
private final MessageId messageId, rssMessageId;
private final Message message, rssMessage;
private final String comment;
public BlogManagerImplTest() { public BlogManagerImplTest() {
MetadataParser metadataParser = context.mock(MetadataParser.class); MetadataParser metadataParser = context.mock(MetadataParser.class);
BlogPostFactory blogPostFactory = context.mock(BlogPostFactory.class);
blogManager = new BlogManagerImpl(db, identityManager, clientHelper, blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
metadataParser, contactManager, blogFactory, blogPostFactory); metadataParser, blogFactory, blogPostFactory);
blog1 = createBlog(); localAuthor1 = createLocalAuthor();
blog2 = createBlog(); localAuthor2 = createLocalAuthor();
rssLocalAuthor = createLocalAuthor();
authorDict1 = authorToBdfDictionary(localAuthor1);
authorDict2 = authorToBdfDictionary(localAuthor2);
rssAuthorDict = authorToBdfDictionary(rssLocalAuthor);
blog1 = createBlog(localAuthor1, false);
blog2 = createBlog(localAuthor2, false);
rssBlog = createBlog(rssLocalAuthor, true);
timestamp = System.currentTimeMillis();
timeReceived = timestamp + 1;
messageId = new MessageId(getRandomId()); messageId = new MessageId(getRandomId());
message = new Message(messageId, blog1.getId(), 42, getRandomBytes(42)); rssMessageId = new MessageId(getRandomId());
message = new Message(messageId, blog1.getId(), timestamp,
getRandomBytes(MAX_MESSAGE_LENGTH));
rssMessage = new Message(rssMessageId, rssBlog.getId(), timestamp,
getRandomBytes(MAX_MESSAGE_LENGTH));
comment = getRandomString(MAX_BLOG_COMMENT_LENGTH);
} }
@Test @Test
@@ -126,6 +157,8 @@ public class BlogManagerImplTest extends BriarTestCase {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(blogFactory).createBlog(blog2.getAuthor()); oneOf(blogFactory).createBlog(blog2.getAuthor());
will(returnValue(blog2)); will(returnValue(blog2));
oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor()));
oneOf(db).removeGroup(txn, blog2.getGroup()); oneOf(db).removeGroup(txn, blog2.getGroup());
}}); }});
@@ -136,23 +169,22 @@ public class BlogManagerImplTest extends BriarTestCase {
@Test @Test
public void testIncomingMessage() throws DbException, FormatException { public void testIncomingMessage() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
BdfList list = new BdfList(); BdfList body = BdfList.of("body");
BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
BdfDictionary meta = BdfDictionary.of( BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()), new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, 0), new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, 1), new BdfEntry(KEY_TIME_RECEIVED, timeReceived),
new BdfEntry(KEY_AUTHOR, author), new BdfEntry(KEY_AUTHOR, authorDict1),
new BdfEntry(KEY_READ, false) new BdfEntry(KEY_READ, false),
new BdfEntry(KEY_RSS_FEED, false)
); );
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(identityManager) oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
.getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED)); will(returnValue(VERIFIED));
}}); }});
blogManager.incomingMessage(txn, message, list, meta); blogManager.incomingMessage(txn, message, body, meta);
context.assertIsSatisfied(); context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size()); assertEquals(1, txn.getEvents().size());
@@ -162,24 +194,60 @@ public class BlogManagerImplTest extends BriarTestCase {
assertEquals(blog1.getId(), e.getGroupId()); assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader(); BlogPostHeader h = e.getHeader();
assertEquals(1, h.getTimeReceived()); assertEquals(POST, h.getType());
assertFalse(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timeReceived, h.getTimeReceived());
assertEquals(messageId, h.getId()); assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId()); assertEquals(blog1.getId(), h.getGroupId());
assertNull(h.getParentId());
assertEquals(VERIFIED, h.getAuthorStatus()); assertEquals(VERIFIED, h.getAuthorStatus());
assertEquals(blog1.getAuthor(), h.getAuthor()); assertEquals(localAuthor1, h.getAuthor());
}
@Test
public void testIncomingRssMessage() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
BdfList body = BdfList.of("body");
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived),
new BdfEntry(KEY_AUTHOR, rssAuthorDict),
new BdfEntry(KEY_READ, false),
new BdfEntry(KEY_RSS_FEED, true)
);
blogManager.incomingMessage(txn, rssMessage, body, meta);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(rssBlog.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(POST, h.getType());
assertTrue(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timeReceived, h.getTimeReceived());
assertEquals(rssMessageId, h.getId());
assertEquals(rssBlog.getId(), h.getGroupId());
assertNull(h.getParentId());
assertEquals(NONE, h.getAuthorStatus());
assertEquals(rssLocalAuthor, h.getAuthor());
} }
@Test @Test
public void testRemoveBlog() throws Exception { public void testRemoveBlog() throws Exception {
final Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
checkGetBlogExpectations(txn, false, blog1);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog2.getAuthor())); will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(false));
oneOf(db).removeGroup(txn, blog1.getGroup()); oneOf(db).removeGroup(txn, blog1.getGroup());
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
@@ -192,13 +260,11 @@ public class BlogManagerImplTest extends BriarTestCase {
@Test @Test
public void testAddLocalPost() throws DbException, FormatException { public void testAddLocalPost() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false); final Transaction txn = new Transaction(null, false);
final BlogPost post = final BlogPost post = new BlogPost(message, null, localAuthor1);
new BlogPost(message, null, blog1.getAuthor());
BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
final BdfDictionary meta = BdfDictionary.of( final BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()), new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()), new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_AUTHOR, authorMeta), new BdfEntry(KEY_AUTHOR, authorDict1),
new BdfEntry(KEY_READ, true), new BdfEntry(KEY_READ, true),
new BdfEntry(KEY_RSS_FEED, false) new BdfEntry(KEY_RSS_FEED, false)
); );
@@ -210,11 +276,9 @@ public class BlogManagerImplTest extends BriarTestCase {
will(returnValue(blog1.getGroup())); will(returnValue(blog1.getGroup()));
oneOf(blogFactory).parseBlog(blog1.getGroup()); oneOf(blogFactory).parseBlog(blog1.getGroup());
will(returnValue(blog1)); will(returnValue(blog1));
oneOf(clientHelper) oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
.addLocalMessage(txn, message, meta, true); oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
oneOf(identityManager) will(returnValue(OURSELVES));
.getAuthorStatus(txn, blog1.getAuthor().getId());
will(returnValue(VERIFIED));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
}}); }});
@@ -229,81 +293,546 @@ public class BlogManagerImplTest extends BriarTestCase {
assertEquals(blog1.getId(), e.getGroupId()); assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader(); BlogPostHeader h = e.getHeader();
assertEquals(message.getTimestamp(), h.getTimeReceived()); assertEquals(POST, h.getType());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(messageId, h.getId()); assertEquals(messageId, h.getId());
assertEquals(null, h.getParentId()); assertEquals(blog1.getId(), h.getGroupId());
assertEquals(VERIFIED, h.getAuthorStatus()); assertNull(h.getParentId());
assertEquals(blog1.getAuthor(), h.getAuthor()); assertEquals(OURSELVES, h.getAuthorStatus());
assertEquals(localAuthor1, h.getAuthor());
}
@Test
public void testAddLocalRssPost() throws DbException, FormatException {
final Transaction txn = new Transaction(null, false);
final BlogPost post = new BlogPost(rssMessage, null, rssLocalAuthor);
final BdfDictionary meta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_AUTHOR, rssAuthorDict),
new BdfEntry(KEY_READ, true),
new BdfEntry(KEY_RSS_FEED, true)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
oneOf(db).getGroup(txn, rssBlog.getId());
will(returnValue(rssBlog.getGroup()));
oneOf(blogFactory).parseBlog(rssBlog.getGroup());
will(returnValue(rssBlog));
oneOf(clientHelper).addLocalMessage(txn, rssMessage, meta, true);
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
blogManager.addLocalPost(post);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(rssBlog.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(POST, h.getType());
assertTrue(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(rssMessageId, h.getId());
assertEquals(rssBlog.getId(), h.getGroupId());
assertNull(h.getParentId());
assertEquals(NONE, h.getAuthorStatus());
assertEquals(rssLocalAuthor, h.getAuthor());
}
@Test
public void testAddLocalCommentToLocalPost() throws Exception {
final Transaction txn = new Transaction(null, false);
// The post was originally posted to blog 1, then reblogged to the
// same blog (commenting on own post)
final BdfDictionary postMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, POST.getInt()),
new BdfEntry(KEY_RSS_FEED, false),
new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
new BdfEntry(KEY_AUTHOR, authorDict1),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
);
final MessageId commentId = new MessageId(getRandomId());
final Message commentMsg = new Message(commentId, blog1.getId(),
timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary commentMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, COMMENT.getInt()),
new BdfEntry(KEY_COMMENT, comment),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
new BdfEntry(KEY_PARENT_MSG_ID, messageId),
new BdfEntry(KEY_AUTHOR, authorDict1)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Create the comment
oneOf(blogPostFactory).createBlogComment(blog1.getId(),
localAuthor1, comment, messageId, messageId);
will(returnValue(commentMsg));
// Store the comment
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
true);
// Create the headers for the comment and its parent
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
will(returnValue(OURSELVES));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, messageId);
will(returnValue(postMeta));
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
will(returnValue(OURSELVES));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
BlogPostHeader postHeader = new BlogPostHeader(POST, blog1.getId(),
messageId, null, timestamp, timeReceived, localAuthor1,
OURSELVES, false, true);
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
postHeader);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(COMMENT, h.getType());
assertFalse(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(commentId, h.getId());
assertEquals(blog1.getId(), h.getGroupId());
assertEquals(messageId, h.getParentId());
assertEquals(OURSELVES, h.getAuthorStatus());
assertEquals(localAuthor1, h.getAuthor());
assertTrue(h instanceof BlogCommentHeader);
BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
assertEquals(POST, h1.getType());
assertFalse(h1.isRssFeed());
assertEquals(timestamp, h1.getTimestamp());
assertEquals(timeReceived, h1.getTimeReceived());
assertEquals(messageId, h1.getId());
assertEquals(blog1.getId(), h.getGroupId());
assertNull(h1.getParentId());
assertEquals(OURSELVES, h1.getAuthorStatus());
assertEquals(localAuthor1, h1.getAuthor());
assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
}
@Test
public void testAddLocalCommentToRemotePost() throws Exception {
final Transaction txn = new Transaction(null, false);
// The post was originally posted to blog 1, then reblogged to
// blog 2 with a comment
final BdfList originalPostBody = BdfList.of("originalPostBody");
final MessageId wrappedPostId = new MessageId(getRandomId());
final Message wrappedPostMsg = new Message(wrappedPostId, blog2.getId(),
timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary wrappedPostMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
new BdfEntry(KEY_RSS_FEED, false),
new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
new BdfEntry(KEY_AUTHOR, authorDict1),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
);
final MessageId commentId = new MessageId(getRandomId());
final Message commentMsg = new Message(commentId, blog2.getId(),
timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary commentMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, COMMENT.getInt()),
new BdfEntry(KEY_COMMENT, comment),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, messageId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
new BdfEntry(KEY_AUTHOR, authorDict2)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Wrap the original post for blog 2
oneOf(clientHelper).getMessageAsList(txn, messageId);
will(returnValue(originalPostBody));
oneOf(db).getGroup(txn, blog1.getId());
will(returnValue(blog1.getGroup()));
oneOf(blogPostFactory).wrapPost(blog2.getId(),
blog1.getGroup().getDescriptor(), timestamp,
originalPostBody);
will(returnValue(wrappedPostMsg));
// Store the wrapped post
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
wrappedPostMeta, true);
// Create the comment
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
localAuthor2, comment, messageId, wrappedPostId);
will(returnValue(commentMsg));
// Store the comment
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
true);
// Create the headers for the comment and the wrapped post
oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
will(returnValue(OURSELVES));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
wrappedPostId);
will(returnValue(wrappedPostMeta));
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
will(returnValue(VERIFIED));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
blog1.getId(), messageId, null, timestamp, timeReceived,
localAuthor1, VERIFIED, false, true);
blogManager.addLocalComment(localAuthor2, blog2.getId(), comment,
originalPostHeader);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog2.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(COMMENT, h.getType());
assertFalse(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(commentId, h.getId());
assertEquals(blog2.getId(), h.getGroupId());
assertEquals(wrappedPostId, h.getParentId());
assertEquals(OURSELVES, h.getAuthorStatus());
assertEquals(localAuthor2, h.getAuthor());
assertTrue(h instanceof BlogCommentHeader);
BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
assertEquals(WRAPPED_POST, h1.getType());
assertFalse(h1.isRssFeed());
assertEquals(timestamp, h1.getTimestamp());
assertEquals(timeReceived, h1.getTimeReceived());
assertEquals(wrappedPostId, h1.getId());
assertEquals(blog2.getId(), h1.getGroupId());
assertNull(h1.getParentId());
assertEquals(VERIFIED, h1.getAuthorStatus());
assertEquals(localAuthor1, h1.getAuthor());
assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
}
@Test
public void testAddLocalCommentToRemoteRssPost() throws Exception {
final Transaction txn = new Transaction(null, false);
// The post was originally posted to the RSS blog, then reblogged to
// blog 1 with a comment
final BdfList originalPostBody = BdfList.of("originalPostBody");
final MessageId wrappedPostId = new MessageId(getRandomId());
final Message wrappedPostMsg = new Message(wrappedPostId, blog1.getId(),
timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary wrappedPostMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
new BdfEntry(KEY_RSS_FEED, true),
new BdfEntry(KEY_ORIGINAL_MSG_ID, rssMessageId),
new BdfEntry(KEY_AUTHOR, rssAuthorDict),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
);
final MessageId commentId = new MessageId(getRandomId());
final Message commentMsg = new Message(commentId, blog1.getId(),
timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary commentMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, COMMENT.getInt()),
new BdfEntry(KEY_COMMENT, comment),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_ORIGINAL_MSG_ID, commentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, rssMessageId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedPostId),
new BdfEntry(KEY_AUTHOR, authorDict1)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Wrap the original post for blog 1
oneOf(clientHelper).getMessageAsList(txn, rssMessageId);
will(returnValue(originalPostBody));
oneOf(db).getGroup(txn, rssBlog.getId());
will(returnValue(rssBlog.getGroup()));
oneOf(blogPostFactory).wrapPost(blog1.getId(),
rssBlog.getGroup().getDescriptor(), timestamp,
originalPostBody);
will(returnValue(wrappedPostMsg));
// Store the wrapped post
oneOf(clientHelper).addLocalMessage(txn, wrappedPostMsg,
wrappedPostMeta, true);
// Create the comment
oneOf(blogPostFactory).createBlogComment(blog1.getId(),
localAuthor1, comment, rssMessageId, wrappedPostId);
will(returnValue(commentMsg));
// Store the comment
oneOf(clientHelper).addLocalMessage(txn, commentMsg, commentMeta,
true);
// Create the headers for the comment and the wrapped post
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
will(returnValue(OURSELVES));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
wrappedPostId);
will(returnValue(wrappedPostMeta));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
BlogPostHeader originalPostHeader = new BlogPostHeader(POST,
rssBlog.getId(), rssMessageId, null, timestamp, timeReceived,
rssLocalAuthor, NONE, true, true);
blogManager.addLocalComment(localAuthor1, blog1.getId(), comment,
originalPostHeader);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog1.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(COMMENT, h.getType());
assertFalse(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(commentId, h.getId());
assertEquals(blog1.getId(), h.getGroupId());
assertEquals(wrappedPostId, h.getParentId());
assertEquals(OURSELVES, h.getAuthorStatus());
assertEquals(localAuthor1, h.getAuthor());
assertTrue(h instanceof BlogCommentHeader);
BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
assertEquals(WRAPPED_POST, h1.getType());
assertTrue(h1.isRssFeed());
assertEquals(timestamp, h1.getTimestamp());
assertEquals(timeReceived, h1.getTimeReceived());
assertEquals(wrappedPostId, h1.getId());
assertEquals(blog1.getId(), h1.getGroupId());
assertNull(h1.getParentId());
assertEquals(NONE, h1.getAuthorStatus());
assertEquals(rssLocalAuthor, h1.getAuthor());
assertEquals(h1.getId(), ((BlogCommentHeader) h).getRootPost().getId());
}
@Test
public void testAddLocalCommentToRebloggedRemoteRssPost() throws Exception {
final Transaction txn = new Transaction(null, false);
// The post was originally posted to the RSS blog, then reblogged to
// blog 1 with a comment
final MessageId wrappedPostId = new MessageId(getRandomId());
final BdfList wrappedPostBody = BdfList.of("wrappedPostBody");
final MessageId originalCommentId = new MessageId(getRandomId());
final BdfList originalCommentBody = BdfList.of("originalCommentBody");
// The post and comment were reblogged to blog 2 with another comment
final MessageId rewrappedPostId = new MessageId(getRandomId());
final Message rewrappedPostMsg = new Message(rewrappedPostId,
blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary rewrappedPostMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, WRAPPED_POST.getInt()),
new BdfEntry(KEY_RSS_FEED, true),
new BdfEntry(KEY_ORIGINAL_MSG_ID, messageId),
new BdfEntry(KEY_AUTHOR, rssAuthorDict),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
);
final MessageId wrappedCommentId = new MessageId(getRandomId());
final Message wrappedCommentMsg = new Message(wrappedCommentId,
blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary wrappedCommentMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, WRAPPED_COMMENT.getInt()),
new BdfEntry(KEY_COMMENT, comment),
new BdfEntry(KEY_PARENT_MSG_ID, rewrappedPostId),
new BdfEntry(KEY_ORIGINAL_MSG_ID, originalCommentId),
new BdfEntry(KEY_AUTHOR, authorDict1),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_TIME_RECEIVED, timeReceived)
);
final String localComment = getRandomString(MAX_BLOG_COMMENT_LENGTH);
final MessageId localCommentId = new MessageId(getRandomId());
final Message localCommentMsg = new Message(localCommentId,
blog2.getId(), timestamp, getRandomBytes(MAX_MESSAGE_LENGTH));
final BdfDictionary localCommentMeta = BdfDictionary.of(
new BdfEntry(KEY_TYPE, COMMENT.getInt()),
new BdfEntry(KEY_COMMENT, localComment),
new BdfEntry(KEY_TIMESTAMP, timestamp),
new BdfEntry(KEY_ORIGINAL_MSG_ID, localCommentId),
new BdfEntry(KEY_ORIGINAL_PARENT_MSG_ID, originalCommentId),
new BdfEntry(KEY_PARENT_MSG_ID, wrappedCommentId),
new BdfEntry(KEY_AUTHOR, authorDict2)
);
context.checking(new Expectations() {{
oneOf(db).startTransaction(false);
will(returnValue(txn));
// Rewrap the wrapped post for blog 2
oneOf(clientHelper).getMessageAsList(txn, wrappedPostId);
will(returnValue(wrappedPostBody));
oneOf(blogPostFactory).rewrapWrappedPost(blog2.getId(),
wrappedPostBody);
will(returnValue(rewrappedPostMsg));
// Store the rewrapped post
oneOf(clientHelper).addLocalMessage(txn, rewrappedPostMsg,
rewrappedPostMeta, true);
// Wrap the original comment for blog 2
oneOf(clientHelper).getMessageAsList(txn, originalCommentId);
will(returnValue(originalCommentBody));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
wrappedPostId);
will(returnValue(rewrappedPostMeta));
oneOf(db).getGroup(txn, blog1.getId());
will(returnValue(blog1.getGroup()));
oneOf(blogPostFactory).wrapComment(blog2.getId(),
blog1.getGroup().getDescriptor(), timestamp,
originalCommentBody, rewrappedPostId);
will(returnValue(wrappedCommentMsg));
// Store the wrapped comment
oneOf(clientHelper).addLocalMessage(txn, wrappedCommentMsg,
wrappedCommentMeta, true);
// Create the new comment
oneOf(blogPostFactory).createBlogComment(blog2.getId(),
localAuthor2, localComment, originalCommentId,
wrappedCommentId);
will(returnValue(localCommentMsg));
// Store the new comment
oneOf(clientHelper).addLocalMessage(txn, localCommentMsg,
localCommentMeta, true);
// Create the headers for the new comment, the wrapped comment and
// the rewrapped post
oneOf(identityManager).getAuthorStatus(txn, localAuthor2.getId());
will(returnValue(OURSELVES));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
wrappedCommentId);
will(returnValue(wrappedCommentMeta));
oneOf(identityManager).getAuthorStatus(txn, localAuthor1.getId());
will(returnValue(VERIFIED));
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
rewrappedPostId);
will(returnValue(rewrappedPostMeta));
oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn);
}});
BlogPostHeader wrappedPostHeader = new BlogPostHeader(WRAPPED_POST,
blog1.getId(), wrappedPostId, null, timestamp, timeReceived,
rssLocalAuthor, NONE, true, true);
BlogCommentHeader originalCommentHeader = new BlogCommentHeader(COMMENT,
blog1.getId(), comment, wrappedPostHeader, originalCommentId,
timestamp, timeReceived, localAuthor1, VERIFIED, true);
blogManager.addLocalComment(localAuthor2, blog2.getId(), localComment,
originalCommentHeader);
context.assertIsSatisfied();
assertEquals(1, txn.getEvents().size());
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
assertEquals(blog2.getId(), e.getGroupId());
BlogPostHeader h = e.getHeader();
assertEquals(COMMENT, h.getType());
assertFalse(h.isRssFeed());
assertEquals(timestamp, h.getTimestamp());
assertEquals(timestamp, h.getTimeReceived());
assertEquals(localCommentId, h.getId());
assertEquals(blog2.getId(), h.getGroupId());
assertEquals(wrappedCommentId, h.getParentId());
assertEquals(OURSELVES, h.getAuthorStatus());
assertEquals(localAuthor2, h.getAuthor());
assertTrue(h instanceof BlogCommentHeader);
BlogPostHeader h1 = ((BlogCommentHeader) h).getParent();
assertEquals(WRAPPED_COMMENT, h1.getType());
assertFalse(h1.isRssFeed());
assertEquals(timestamp, h1.getTimestamp());
assertEquals(timeReceived, h1.getTimeReceived());
assertEquals(wrappedCommentId, h1.getId());
assertEquals(blog2.getId(), h1.getGroupId());
assertEquals(rewrappedPostId, h1.getParentId());
assertEquals(VERIFIED, h1.getAuthorStatus());
assertEquals(localAuthor1, h1.getAuthor());
assertTrue(h1 instanceof BlogCommentHeader);
BlogPostHeader h2 = ((BlogCommentHeader) h1).getParent();
assertEquals(WRAPPED_POST, h2.getType());
assertTrue(h2.isRssFeed());
assertEquals(timestamp, h2.getTimestamp());
assertEquals(timeReceived, h2.getTimeReceived());
assertEquals(rewrappedPostId, h2.getId());
assertEquals(blog2.getId(), h2.getGroupId());
assertNull(h2.getParentId());
assertEquals(NONE, h2.getAuthorStatus());
assertEquals(rssLocalAuthor, h2.getAuthor());
assertEquals(h2.getId(), ((BlogCommentHeader) h).getRootPost().getId());
assertEquals(h2.getId(),
((BlogCommentHeader) h1).getRootPost().getId());
} }
@Test @Test
public void testBlogCanBeRemoved() throws Exception { public void testBlogCanBeRemoved() throws Exception {
// check that own personal blogs can not be removed // check that own personal blogs can not be removed
final Transaction txn = new Transaction(null, true); final Transaction txn = new Transaction(null, true);
checkGetBlogExpectations(txn, true, blog1);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn));
oneOf(identityManager).getLocalAuthor(txn); oneOf(identityManager).getLocalAuthor(txn);
will(returnValue(blog1.getAuthor())); will(returnValue(blog1.getAuthor()));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
}}); }});
assertFalse(blogManager.canBeRemoved(blog1.getId())); assertFalse(blogManager.canBeRemoved(blog1));
context.assertIsSatisfied(); context.assertIsSatisfied();
// check that blogs of contacts can not be removed // check that blogs of contacts can be removed
final Transaction txn2 = new Transaction(null, true); final Transaction txn2 = new Transaction(null, true);
checkGetBlogExpectations(txn2, true, blog1);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(true);
will(returnValue(txn2));
oneOf(identityManager).getLocalAuthor(txn2); oneOf(identityManager).getLocalAuthor(txn2);
will(returnValue(blog2.getAuthor())); will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn2, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(true));
oneOf(db).commitTransaction(txn2); oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
}}); }});
assertFalse(blogManager.canBeRemoved(blog1.getId())); assertTrue(blogManager.canBeRemoved(blog1));
context.assertIsSatisfied();
// check that blogs can be removed if they don't belong to a contact
final Transaction txn3 = new Transaction(null, true);
checkGetBlogExpectations(txn3, true, blog1);
context.checking(new Expectations() {{
oneOf(identityManager).getLocalAuthor(txn3);
will(returnValue(blog2.getAuthor()));
oneOf(contactManager).contactExists(txn3, blog1.getAuthor().getId(),
blog2.getAuthor().getId());
will(returnValue(false));
oneOf(db).commitTransaction(txn3);
oneOf(db).endTransaction(txn3);
}});
assertTrue(blogManager.canBeRemoved(blog1.getId()));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
private void checkGetBlogExpectations(final Transaction txn, private LocalAuthor createLocalAuthor() {
final boolean readOnly, final Blog blog) throws Exception { return new LocalAuthor(new AuthorId(getRandomId()),
context.checking(new Expectations() {{ getRandomString(MAX_AUTHOR_NAME_LENGTH),
oneOf(db).startTransaction(readOnly); getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
will(returnValue(txn)); getRandomBytes(123), System.currentTimeMillis());
oneOf(db).getGroup(txn, blog.getId());
will(returnValue(blog.getGroup()));
oneOf(blogFactory).parseBlog(blog.getGroup());
will(returnValue(blog));
}});
} }
private Blog createBlog() { private Blog createBlog(LocalAuthor localAuthor, boolean rssFeed) {
final GroupId groupId = new GroupId(getRandomId()); GroupId groupId = new GroupId(getRandomId());
final Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42)); Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
final AuthorId authorId = new AuthorId(getRandomId()); return new Blog(group, localAuthor, rssFeed);
final byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
final long created = System.currentTimeMillis();
final LocalAuthor localAuthor =
new LocalAuthor(authorId, "Author", publicKey, privateKey,
created);
return new Blog(group, localAuthor, false);
} }
private BdfDictionary authorToBdfDictionary(Author a) { private BdfDictionary authorToBdfDictionary(Author a) {

View File

@@ -21,7 +21,6 @@ import org.junit.rules.ExpectedException;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
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_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -185,19 +184,19 @@ public class BlogManagerIntegrationTest
} }
@Test @Test
public void testCanNotRemoveContactsPersonalBlog() throws Exception { public void testCanRemoveContactsPersonalBlog() throws Exception {
assertFalse(blogManager0.canBeRemoved(blog1.getId())); assertTrue(blogManager0.canBeRemoved(blog1));
assertFalse(blogManager1.canBeRemoved(blog0.getId())); assertTrue(blogManager1.canBeRemoved(blog0));
// the following two calls should throw a DbException now assertEquals(4, blogManager0.getBlogs().size());
thrown.expect(IllegalArgumentException.class); assertEquals(2, blogManager1.getBlogs().size());
blogManager0.removeBlog(blog1); blogManager0.removeBlog(blog1);
blogManager1.removeBlog(blog0); blogManager1.removeBlog(blog0);
// blogs have not been removed // blogs have been removed
assertEquals(2, blogManager0.getBlogs().size()); assertEquals(3, blogManager0.getBlogs().size());
assertEquals(2, blogManager1.getBlogs().size()); assertEquals(1, blogManager1.getBlogs().size());
} }
@Test @Test

View File

@@ -0,0 +1,50 @@
package org.briarproject.briar.client;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.briar.api.client.MessageTracker;
import org.jmock.Expectations;
import org.junit.Assert;
import org.junit.Test;
import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID;
public class MessageTrackerTest extends BrambleMockTestCase {
protected final GroupId groupId = new GroupId(TestUtils.getRandomId());
protected final ClientHelper clientHelper =
context.mock(ClientHelper.class);
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final MessageId messageId = new MessageId(TestUtils.getRandomId());
private final MessageTracker messageTracker =
new MessageTrackerImpl(db, clientHelper);
private final BdfDictionary dictionary = BdfDictionary.of(
new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, messageId)
);
@Test
public void testMessageStore() throws Exception {
context.checking(new Expectations() {{
oneOf(clientHelper).mergeGroupMetadata(groupId, dictionary);
}});
messageTracker.storeMessageId(groupId, messageId);
}
@Test
public void testMessageLoad() throws Exception {
context.checking(new Expectations() {{
oneOf(clientHelper).getGroupMetadataAsDictionary(groupId);
will(returnValue(dictionary));
}});
MessageId loadedId = messageTracker.loadStoredMessageId(groupId);
Assert.assertNotNull(loadedId);
Assert.assertTrue(messageId.equals(loadedId));
}
}

View File

@@ -41,7 +41,7 @@ import static org.junit.Assert.fail;
public class BlogSharingIntegrationTest public class BlogSharingIntegrationTest
extends BriarIntegrationTest<BriarIntegrationTestComponent> { extends BriarIntegrationTest<BriarIntegrationTestComponent> {
private BlogManager blogManager1; private BlogManager blogManager0, blogManager1;
private Blog blog0, blog1, blog2; private Blog blog0, blog1, blog2;
private SharerListener listener0; private SharerListener listener0;
private InviteeListener listener1; private InviteeListener listener1;
@@ -60,7 +60,7 @@ public class BlogSharingIntegrationTest
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
BlogManager blogManager0 = c0.getBlogManager(); blogManager0 = c0.getBlogManager();
blogManager1 = c1.getBlogManager(); blogManager1 = c1.getBlogManager();
blogSharingManager0 = c0.getBlogSharingManager(); blogSharingManager0 = c0.getBlogSharingManager();
blogSharingManager1 = c1.getBlogSharingManager(); blogSharingManager1 = c1.getBlogSharingManager();
@@ -370,7 +370,7 @@ public class BlogSharingIntegrationTest
assertEquals(contact0From1, sharedBy.iterator().next()); assertEquals(contact0From1, sharedBy.iterator().next());
// shared blog can be removed // shared blog can be removed
assertTrue(blogManager1.canBeRemoved(blog2.getId())); assertTrue(blogManager1.canBeRemoved(blog2));
// invitee removes blog again // invitee removes blog again
blogManager1.removeBlog(blog2); blogManager1.removeBlog(blog2);
@@ -386,44 +386,33 @@ public class BlogSharingIntegrationTest
} }
@Test @Test
public void testSharedBlogBecomesPermanent() throws Exception { public void testRemovePreSharedBlog() throws Exception {
// let invitee accept all requests // let invitee accept all requests
listenToEvents(true); listenToEvents(true);
// invitee only sees two blogs // 0 and 1 are sharing blog 1 with each other
assertEquals(2, blogManager1.getBlogs().size()); assertTrue(blogSharingManager0.getSharedWith(blog1.getId())
.contains(contact1From0));
assertTrue(blogSharingManager1.getSharedWith(blog1.getId())
.contains(contact0From1));
// sharer sends invitation for 2's blog to 1 // 0 removes blog 1
blogSharingManager0 assertTrue(blogManager0.getBlogs().contains(blog1));
.sendInvitation(blog2.getId(), contactId1From0, "Hi!", blogManager0.removeBlog(blog1);
clock.currentTimeMillis()); assertFalse(blogManager0.getBlogs().contains(blog1));
// sync first request message // sync leave message to 0
sync0To1(1, true); sync0To1(1, true);
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// make sure blog2 is shared by 0 // 0 and 1 are no longer sharing blog 1 with each other
Collection<Contact> contacts = assertFalse(blogSharingManager0.getSharedWith(blog1.getId())
blogSharingManager1.getSharedWith(blog2.getId()); .contains(contact1From0));
assertEquals(1, contacts.size()); assertFalse(blogSharingManager1.getSharedWith(blog1.getId())
assertTrue(contacts.contains(contact0From1)); .contains(contact0From1));
// sync response back // 1 can again share blog 1 with 0
sync1To0(1, true); assertTrue(
eventWaiter.await(TIMEOUT, 1); blogSharingManager1.canBeShared(blog1.getId(), contact0From1));
assertTrue(listener0.responseReceived);
// blog was added and can be removed
assertEquals(3, blogManager1.getBlogs().size());
assertTrue(blogManager1.canBeRemoved(blog2.getId()));
// 1 and 2 are adding each other
addContacts1And2();
assertEquals(3, blogManager1.getBlogs().size());
// now blog can not be removed anymore
assertFalse(blogManager1.canBeRemoved(blog2.getId()));
} }
@Test @Test