Compare commits

...

50 Commits

Author SHA1 Message Date
akwizgran
89f50bbdaf Bump version numbers for beta release. 2018-03-29 16:38:03 +01:00
akwizgran
3eed7df1a4 Merge branch '1171-wifi-access-point' into 'maintenance-0.16'
Backport: Enable LAN plugin when providing a wifi access point

See merge request akwizgran/briar!755
2018-03-29 15:36:11 +00:00
akwizgran
f7af0dc3b0 Delay handling of AP enabled event. 2018-03-29 16:19:03 +01:00
akwizgran
fbaf446570 AP state change event races with address appearing. 2018-03-29 16:19:03 +01:00
akwizgran
fb6d962131 Enable LAN plugin to use wifi AP interface. 2018-03-29 16:19:03 +01:00
akwizgran
d007de48ac Serialise concurrent calls to updateConnectionStatus(). 2018-03-29 16:19:03 +01:00
akwizgran
95a08eed5c Serialise concurrent calls to bind(). 2018-03-29 16:19:02 +01:00
akwizgran
040894b205 Merge branch '1190-shutdown-from-background' into 'maintenance-0.16'
Backport: Shut down cleanly when phone is shutting down or memory is low

See merge request akwizgran/briar!754
2018-03-29 15:04:21 +00:00
akwizgran
41d3bd4f19 Show notification for low memory shutdown. 2018-03-29 15:50:25 +01:00
akwizgran
347868684c Shut down cleanly when device shuts down. 2018-03-29 15:50:23 +01:00
akwizgran
1038a3532b Shut down cleanly when memory is low. 2018-03-29 15:48:29 +01:00
Torsten Grote
4e6d514a0d Backport translation update, add Romanian 2018-03-29 11:07:31 -03:00
akwizgran
f178ce807f Merge branch '965-empty-state-messages' into 'maintenance-0.16'
Backport: Shorten and clean up various strings, remove empty forum warning bubble

See merge request akwizgran/briar!751
2018-03-29 11:47:53 +00:00
akwizgran
a2c827ef24 Merge branch 'hide-ui-during-shutdown' into 'maintenance-0.16'
Backport: Hide UI during shutdown

See merge request akwizgran/briar!750
2018-03-29 11:39:02 +00:00
akwizgran
9496148182 Merge branch '346-full-screen-qr-code' into 'maintenance-0.16'
Backport: Add fullscreen button to QR code view

See merge request akwizgran/briar!749
2018-03-29 11:29:55 +00:00
akwizgran
bb27ca186a Merge branch '845-wifi-without-internet' into 'maintenance-0.16'
Backport: Use WifiManager to get wifi network information

See merge request akwizgran/briar!748
2018-03-29 11:21:15 +00:00
akwizgran
be38431e03 Merge branch '1184-rejected-execution-exception' into 'maintenance-0.16'
Backport: Discard tasks submitted to ScheduledExecutorService during shutdown

See merge request akwizgran/briar!747
2018-03-29 11:12:23 +00:00
akwizgran
e314b39661 Merge branch '965-forum-empty-state' into 'maintenance-0.16'
Backport: Remove mention of pen icon from forum empty state message

See merge request akwizgran/briar!746
2018-03-29 11:03:28 +00:00
akwizgran
4aa8d0b6c0 Remove empty forum warning bubble. 2018-03-29 12:03:17 +01:00
akwizgran
6220a8c00e Consistent text for blogs and forums. 2018-03-29 12:03:17 +01:00
akwizgran
dcd9b0a637 Shorter empty state messages. 2018-03-29 12:03:16 +01:00
akwizgran
94b17caf0f Consistent explanation of account deletion options. 2018-03-29 12:03:16 +01:00
akwizgran
fce8d9fa9f Finish if back button is pressed in SignOutFragment. 2018-03-29 12:00:41 +01:00
akwizgran
f4c798a2da Use database icon for SignOutFragment. 2018-03-29 12:00:41 +01:00
akwizgran
accef2e51b Close NavDrawerActivity immediately when signing out. 2018-03-29 12:00:41 +01:00
akwizgran
34b4c35f44 Use selectable item background to get touch effect. 2018-03-29 11:55:47 +01:00
akwizgran
9b253fc965 Adjust layout weights when resizing QR code view. 2018-03-29 11:55:46 +01:00
akwizgran
4d97cad842 Add fullscreen button to QR code view. 2018-03-29 11:55:46 +01:00
akwizgran
ba99f58559 Use wifi network's socket factory on API 21+. 2018-03-29 11:53:03 +01:00
akwizgran
edbb0a3c13 Use WifiManager to get wifi network information.
This ensures we bind to the wifi interface even if it doesn't have internet access and there's another interface with internet access (e.g. mobile data).
2018-03-29 11:53:03 +01:00
akwizgran
fdbcc0736c Discard tasks submitted during shutdown. 2018-03-29 11:50:21 +01:00
akwizgran
f4722b2a67 Remove mention of pen icon from forum empty state message. 2018-03-29 11:48:29 +01:00
akwizgran
d316e126a9 Merge branch '1159-android-8-notification-settings' into 'maintenance-0.16'
Backport: Overhaul notifications for Android 8

See merge request akwizgran/briar!744
2018-03-29 10:24:09 +00:00
Torsten Grote
20bd72844c Use a different notification preference summary for Android 8 2018-03-26 13:38:55 -03:00
Torsten Grote
02c88eb907 Show different notification settings for Android O
This also makes the defaults consistent with Android versions below O.
2018-03-26 13:38:54 -03:00
akwizgran
1afc0d4fda Merge branch '545-remove-clientid-from-validator-db-methods' into 'maintenance-0.16'
Backport: Remove client ID from validator's DB methods

See merge request akwizgran/briar!738
2018-03-20 17:33:30 +00:00
akwizgran
5a7f39df4d Backport some inconsequential changes from master.
Should make it easier to backport test changes in future.
2018-03-20 17:24:36 +00:00
akwizgran
e30b190209 Remove client ID from validator's DB methods. 2018-03-20 17:23:26 +00:00
akwizgran
31d35a7dd8 Merge branch '1177-blank-viewfinder' into 'maintenance-0.16'
Backport: Show viewfinder again after connection fails

See merge request akwizgran/briar!736
2018-03-20 16:05:57 +00:00
akwizgran
53f85d4b71 When resetting, restart camera if we've stopped it. 2018-03-20 15:51:06 +00:00
akwizgran
54b0bb6084 Don't create a stack of QR code fragments. 2018-03-20 15:38:34 +00:00
akwizgran
f2cfca1460 Remove performance logging. 2018-03-20 15:38:31 +00:00
akwizgran
0cbdc47649 Merge branch '545-denormalise-statuses' into 'maintenance-0.16'
Backport: Add denormalised columns to statuses table

See merge request akwizgran/briar!730
2018-03-09 15:41:37 +00:00
Torsten Grote
536853343e Merge branch '1169-settings-npe' into 'maintenance-0.16'
Backport: Disable settings until they have been loaded

See merge request akwizgran/briar!731
2018-03-08 15:58:33 +00:00
akwizgran
93de06ed0c Merge branch '1181-blurry-error-icon' into 'maintenance-0.16'
Unblur error icon

See merge request akwizgran/briar!729
2018-03-08 15:57:20 +00:00
Torsten Grote
d7f5da305a Disable settings until they have been loaded
In practise, this is not noticeable in the UI.
Only when the database is congested, it should become visible and
prevent a crash when the sound setting is clicked.
2018-03-08 12:45:46 -03:00
Torsten Grote
7c48bc5a00 Unblur error icon 2018-03-08 11:54:12 -03:00
akwizgran
9493e242cc Add migration to schema version 32. 2018-03-08 14:49:22 +00:00
akwizgran
3e28323ab1 Test that visibility change affects expected contacts. 2018-03-08 12:36:30 +00:00
akwizgran
c7e496230b Add denormalised columns to statuses table. 2018-03-08 12:35:46 +00:00
61 changed files with 2076 additions and 794 deletions

View File

@@ -12,8 +12,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 1619 versionCode 1620
versionName "0.16.19" versionName "0.16.20"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }

View File

@@ -48,7 +48,7 @@ public class AndroidPluginModule {
appContext, locationUtils, reporter, eventBus, appContext, locationUtils, reporter, eventBus,
torSocketFactory, backoffFactory); torSocketFactory, backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext); scheduler, backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
Arrays.asList(bluetooth, tor, lan); Arrays.asList(bluetooth, tor, lan);
@NotNullByDefault @NotNullByDefault

View File

@@ -5,37 +5,84 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin { class AndroidLanTcpPlugin extends LanTcpPlugin {
// See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED";
private static final int WIFI_AP_STATE_ENABLED = 13;
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidLanTcpPlugin.class.getName()); Logger.getLogger(AndroidLanTcpPlugin.class.getName());
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final ConnectivityManager connectivityManager;
@Nullable
private final WifiManager wifiManager;
@Nullable @Nullable
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
private volatile SocketFactory socketFactory;
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff, AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
Context appContext, DuplexPluginCallback callback, int maxLatency, Backoff backoff, Context appContext, DuplexPluginCallback callback,
int maxIdleTime) { int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime); super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
ConnectivityManager connectivityManager = (ConnectivityManager)
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (connectivityManager == null) throw new AssertionError();
this.connectivityManager = connectivityManager;
wifiManager = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
socketFactory = SocketFactory.getDefault();
} }
@Override @Override
@@ -44,7 +91,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
running = true; running = true;
// Register to receive network status events // Register to receive network status events
networkStateReceiver = new NetworkStateReceiver(); networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION); IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
} }
@@ -56,21 +105,92 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
tryToClose(socket); tryToClose(socket);
} }
@Override
protected Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
@Override
protected Collection<InetAddress> getLocalIpAddresses() {
// If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, use that network
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0)
return singletonList(intToInetAddress(info.getIpAddress()));
// If we're running an access point, return its address
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
return singletonList(WIFI_AP_ADDRESS);
// No suitable addresses
return emptyList();
}
private InetAddress intToInetAddress(int ip) {
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
return InetAddress.getByAddress(ipBytes);
} catch (UnknownHostException e) {
// Should only be thrown if address has illegal length
throw new AssertionError(e);
}
}
// On API 21 and later, a socket that is not created with the wifi
// network's socket factory may try to connect via another network
private SocketFactory getSocketFactory() {
if (SDK_INT < 21) return SocketFactory.getDefault();
for (Network net : connectivityManager.getAllNetworks()) {
NetworkInfo info = connectivityManager.getNetworkInfo(net);
if (info != null && info.getType() == TYPE_WIFI)
return net.getSocketFactory();
}
LOG.warning("Could not find suitable socket factory");
return SocketFactory.getDefault();
}
private class NetworkStateReceiver extends BroadcastReceiver { private class NetworkStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context ctx, Intent i) { public void onReceive(Context ctx, Intent i) {
if (!running) return; if (!running) return;
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE); if (isApEnabledEvent(i)) {
ConnectivityManager cm = (ConnectivityManager) o; // The state change may be broadcast before the AP address is
NetworkInfo net = cm.getActiveNetworkInfo(); // visible, so delay handling the event
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) { scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
LOG.info("Connected to Wi-Fi");
if (socket == null || socket.isClosed()) bind();
} else { } else {
LOG.info("Not connected to Wi-Fi"); handleConnectivityChange();
tryToClose(socket);
} }
} }
private void handleConnectivityChange() {
if (!running) return;
Collection<InetAddress> addrs = getLocalIpAddresses();
if (addrs.contains(WIFI_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to
// make outgoing connections on API 21+ if another network
// has internet access
socketFactory = SocketFactory.getDefault();
if (socket == null || socket.isClosed()) bind();
} else if (addrs.isEmpty()) {
LOG.info("Not connected to wifi");
socketFactory = SocketFactory.getDefault();
tryToClose(socket);
} else {
LOG.info("Connected to wifi");
socketFactory = getSocketFactory();
if (socket == null || socket.isClosed()) bind();
}
}
private boolean isApEnabledEvent(Intent i) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(i.getAction()) &&
i.getIntExtra(EXTRA_WIFI_STATE, 0) == WIFI_AP_STATE_ENABLED;
}
} }
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -27,12 +28,15 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
private final Context appContext; private final Context appContext;
public AndroidLanTcpPluginFactory(Executor ioExecutor, public AndroidLanTcpPluginFactory(Executor ioExecutor,
BackoffFactory backoffFactory, Context appContext) { ScheduledExecutorService scheduler, BackoffFactory backoffFactory,
Context appContext) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
this.appContext = appContext; this.appContext = appContext;
} }
@@ -51,7 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext, return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff,
callback, MAX_LATENCY, MAX_IDLE_TIME); appContext, callback, MAX_LATENCY, MAX_IDLE_TIME);
} }
} }

View File

@@ -16,6 +16,7 @@ import android.os.PowerManager;
import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection; import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
@@ -63,8 +64,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@@ -111,7 +110,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TorPlugin.class.getName()); Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor, connectionStatusExecutor;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
@@ -125,7 +124,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile; private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock; private final PowerManager.WakeLock wakeLock;
private final Lock connectionStatusLock;
private final AtomicReference<Future<?>> connectivityCheck = private final AtomicReference<Future<?>> connectivityCheck =
new AtomicReference<>(); new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -167,7 +165,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// This tag will prevent Huawei's powermanager from killing us. // This tag will prevent Huawei's powermanager from killing us.
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService"); wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
connectionStatusLock = new ReentrantLock(); // Don't execute more than one connection status check at a time
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
ioExecutor, 1);
} }
@Override @Override
@@ -697,56 +697,46 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
ioExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
if (!running) return; if (!running) return;
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try { try {
connectionStatusLock.lock(); if (!online) {
updateConnectionStatusLocked(); LOG.info("Disabling network, device is offline");
} finally { enableNetwork(false);
connectionStatusLock.unlock(); } else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }
}); });
} }
// Locking: connectionStatusLock
private void updateConnectionStatusLocked() {
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void scheduleConnectionStatusUpdate() { private void scheduleConnectionStatusUpdate() {
Future<?> newConnectivityCheck = Future<?> newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES); scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);

View File

@@ -259,31 +259,30 @@ public interface DatabaseComponent {
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated.
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c) Collection<MessageId> getMessagesToValidate(Transaction txn)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that are valid but pending delivery due * Returns the IDs of any messages that are pending delivery due to
* to dependencies on other messages for the given client. * dependencies on other messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c) Collection<MessageId> getPendingMessages(Transaction txn)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages from the given client * Returns the IDs of any messages that have shared dependents but have
* that have a shared dependent, but are still not shared themselves. * not yet been shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(Transaction txn, Collection<MessageId> getMessagesToShare(Transaction txn)
ClientId c) throws DbException; throws DbException;
/** /**
* Returns the message with the given ID, in serialised form, or null if * Returns the message with the given ID, in serialised form, or null if

View File

@@ -97,9 +97,12 @@ interface Database<T> {
/** /**
* Stores a message. * Stores a message.
*
* @param sender the contact from whom the message was received, or null
* if the message was created locally.
*/ */
void addMessage(T txn, Message m, State state, boolean shared) void addMessage(T txn, Message m, State state, boolean shared,
throws DbException; @Nullable ContactId sender) throws DbException;
/** /**
* Adds a dependency between two messages in the given group. * Adds a dependency between two messages in the given group.
@@ -112,16 +115,6 @@ interface Database<T> {
*/ */
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
/**
* Initialises the status of the given message with respect to the given
* contact.
*
* @param ack whether the message needs to be acknowledged.
* @param seen whether the contact has seen the message.
*/
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
throws DbException;
/** /**
* Stores a transport. * Stores a transport.
*/ */
@@ -280,7 +273,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<ContactId> getGroupVisibility(T txn, GroupId g) Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
throws DbException; throws DbException;
/** /**
@@ -431,31 +424,27 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated.
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(T txn, ClientId c) Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
throws DbException;
/** /**
* Returns the IDs of any messages that are still pending due to * Returns the IDs of any messages that are pending delivery due to
* dependencies to other messages for the given client. * dependencies on other messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(T txn, ClientId c) Collection<MessageId> getPendingMessages(T txn) throws DbException;
throws DbException;
/** /**
* Returns the IDs of any messages from the given client * Returns the IDs of any messages that have a shared dependent but have
* that have a shared dependent, but are still not shared themselves. * not yet been shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(T txn, ClientId c) Collection<MessageId> getMessagesToShare(T txn) throws DbException;
throws DbException;
/** /**
* Returns the next time (in milliseconds since the Unix epoch) when a * Returns the next time (in milliseconds since the Unix epoch) when a
@@ -584,13 +573,6 @@ interface Database<T> {
*/ */
void removeMessage(T txn, MessageId m) throws DbException; void removeMessage(T txn, MessageId m) throws DbException;
/**
* Removes an offered message that was offered by the given contact, or
* returns false if there is no such message.
*/
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
throws DbException;
/** /**
* Removes the given offered messages that were offered by the given * Removes the given offered messages that were offered by the given
* contact. * contact.
@@ -598,12 +580,6 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c, void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException; Collection<MessageId> requested) throws DbException;
/**
* Removes the status of the given message with respect to the given
* contact.
*/
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
/** /**
* Removes a transport (and all associated state) from the database. * Removes a transport (and all associated state) from the database.
*/ */

View File

@@ -215,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId())) if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) { if (!db.containsMessage(txn, m.getId())) {
addMessage(txn, m, DELIVERED, shared, null); db.addMessage(txn, m, DELIVERED, shared, null);
transaction.attach(new MessageAddedEvent(m, null)); transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
@@ -224,16 +224,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta); db.mergeMessageMetadata(txn, m.getId(), meta);
} }
private void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException {
db.addMessage(txn, m, state, shared);
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || (sender != null && c.equals(sender));
db.addStatus(txn, c, m.getId(), seen, seen);
}
}
@Override @Override
public void addTransport(Transaction transaction, TransportId t, public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException { int maxLatency) throws DbException {
@@ -465,24 +455,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction, public Collection<MessageId> getMessagesToValidate(Transaction transaction)
ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToValidate(txn, c); return db.getMessagesToValidate(txn);
} }
@Override @Override
public Collection<MessageId> getPendingMessages(Transaction transaction, public Collection<MessageId> getPendingMessages(Transaction transaction)
ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getPendingMessages(txn, c); return db.getPendingMessages(txn);
} }
@Override @Override
public Collection<MessageId> getMessagesToShare( public Collection<MessageId> getMessagesToShare(Transaction transaction)
Transaction transaction, ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToShare(txn, c); return db.getMessagesToShare(txn);
} }
@Nullable @Nullable
@@ -583,7 +573,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override @Override
public long getNextSendTime(Transaction transaction, ContactId c) public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getNextSendTime(txn, c); return db.getNextSendTime(txn, c);
} }
@@ -682,7 +672,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId()); db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId()); db.raiseAckFlag(txn, c, m.getId());
} else { } else {
addMessage(txn, m, UNKNOWN, false, c); db.addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c)); transaction.attach(new MessageAddedEvent(m, c));
} }
transaction.attach(new MessageToAckEvent(c)); transaction.attach(new MessageToAckEvent(c));
@@ -750,7 +740,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId(); GroupId id = g.getId();
if (!db.containsGroup(txn, id)) if (!db.containsGroup(txn, id))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Collection<ContactId> affected = db.getGroupVisibility(txn, id); Collection<ContactId> affected =
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id); db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -820,19 +811,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Visibility old = db.getGroupVisibility(txn, c, g); Visibility old = db.getGroupVisibility(txn, c, g);
if (old == v) return; if (old == v) return;
if (old == INVISIBLE) { if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
db.addGroupVisibility(txn, c, g, v == SHARED); else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g)) { else db.setGroupVisibility(txn, c, g, v == SHARED);
boolean seen = db.removeOfferedMessage(txn, c, m);
db.addStatus(txn, c, m, seen, seen);
}
} else if (v == INVISIBLE) {
db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g))
db.removeStatus(txn, c, m);
} else {
db.setGroupVisibility(txn, c, g, v == SHARED);
}
List<ContactId> affected = Collections.singletonList(c); List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }

View File

@@ -34,6 +34,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@@ -72,7 +73,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
static final int CODE_SCHEMA_VERSION = 31; static final int CODE_SCHEMA_VERSION = 32;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -188,6 +189,13 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE TABLE statuses" "CREATE TABLE statuses"
+ " (messageId HASH NOT NULL," + " (messageId HASH NOT NULL,"
+ " contactId INT NOT NULL," + " contactId INT NOT NULL,"
+ " groupId HASH NOT NULL," // Denormalised
+ " timestamp BIGINT NOT NULL," // Denormalised
+ " length INT NOT NULL," // Denormalised
+ " state INT NOT NULL," // Denormalised
+ " groupShared BOOLEAN NOT NULL," // Denormalised
+ " messageShared BOOLEAN NOT NULL," // Denormalised
+ " deleted BOOLEAN NOT NULL," // Denormalised
+ " ack BOOLEAN NOT NULL," + " ack BOOLEAN NOT NULL,"
+ " seen BOOLEAN NOT NULL," + " seen BOOLEAN NOT NULL,"
+ " requested BOOLEAN NOT NULL," + " requested BOOLEAN NOT NULL,"
@@ -199,6 +207,9 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON DELETE CASCADE," + " ON DELETE CASCADE,"
+ " FOREIGN KEY (contactId)" + " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)" + " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE)"; + " ON DELETE CASCADE)";
private static final String CREATE_TRANSPORTS = private static final String CREATE_TRANSPORTS =
@@ -252,6 +263,14 @@ abstract class JdbcDatabase implements Database<Connection> {
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState" "CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
+ " ON messageMetadata (groupId, state)"; + " ON messageMetadata (groupId, state)";
private static final String INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID =
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
+ " ON statuses (contactId, groupId)";
private static final String INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP =
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
+ " ON statuses (contactId, timestamp)";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(JdbcDatabase.class.getName()); Logger.getLogger(JdbcDatabase.class.getName());
@@ -343,7 +362,7 @@ abstract class JdbcDatabase implements Database<Connection> {
// Package access for testing // Package access for testing
List<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {
return Collections.singletonList(new Migration30_31()); return Arrays.asList(new Migration30_31(), new Migration31_32());
} }
private void storeSchemaVersion(Connection txn, int version) private void storeSchemaVersion(Connection txn, int version)
@@ -401,6 +420,8 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID); s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID); s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE); s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
s.close(); s.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(s); tryToClose(s);
@@ -578,7 +599,7 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void addGroupVisibility(Connection txn, ContactId c, GroupId g, public void addGroupVisibility(Connection txn, ContactId c, GroupId g,
boolean shared) throws DbException { boolean groupShared) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO groupVisibilities" String sql = "INSERT INTO groupVisibilities"
@@ -587,16 +608,50 @@ abstract class JdbcDatabase implements Database<Connection> {
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
ps.setBytes(2, g.getBytes()); ps.setBytes(2, g.getBytes());
ps.setBoolean(3, shared); ps.setBoolean(3, groupShared);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
// Create a status row for each message in the group
addStatus(txn, c, g, groupShared);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
} }
} }
private void addStatus(Connection txn, ContactId c, GroupId g,
boolean groupShared) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId, timestamp, state, shared,"
+ " length, raw IS NULL"
+ " FROM messages"
+ " WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
while (rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
long timestamp = rs.getLong(2);
State state = State.fromValue(rs.getInt(3));
boolean messageShared = rs.getBoolean(4);
int length = rs.getInt(5);
boolean deleted = rs.getBoolean(6);
boolean seen = removeOfferedMessage(txn, c, id);
addStatus(txn, id, c, g, timestamp, length, state, groupShared,
messageShared, deleted, seen);
}
rs.close();
ps.close();
} catch (SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
@Override @Override
public void addLocalAuthor(Connection txn, LocalAuthor a) public void addLocalAuthor(Connection txn, LocalAuthor a)
throws DbException { throws DbException {
@@ -622,7 +677,8 @@ abstract class JdbcDatabase implements Database<Connection> {
@Override @Override
public void addMessage(Connection txn, Message m, State state, public void addMessage(Connection txn, Message m, State state,
boolean shared) throws DbException { boolean messageShared, @Nullable ContactId sender)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO messages (messageId, groupId, timestamp," String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
@@ -633,13 +689,24 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setBytes(2, m.getGroupId().getBytes()); ps.setBytes(2, m.getGroupId().getBytes());
ps.setLong(3, m.getTimestamp()); ps.setLong(3, m.getTimestamp());
ps.setInt(4, state.getValue()); ps.setInt(4, state.getValue());
ps.setBoolean(5, shared); ps.setBoolean(5, messageShared);
byte[] raw = m.getRaw(); byte[] raw = m.getRaw();
ps.setInt(6, raw.length); ps.setInt(6, raw.length);
ps.setBytes(7, raw); ps.setBytes(7, raw);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
// Create a status row for each contact that can see the group
Map<ContactId, Boolean> visibility =
getGroupVisibility(txn, m.getGroupId());
for (Entry<ContactId, Boolean> e : visibility.entrySet()) {
ContactId c = e.getKey();
boolean offered = removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || (sender != null && c.equals(sender));
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
m.getLength(), state, e.getValue(), messageShared,
false, seen);
}
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -677,19 +744,28 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g,
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack, long timestamp, int length, State state, boolean groupShared,
boolean seen) throws DbException { boolean messageShared, boolean deleted, boolean seen)
throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "INSERT INTO statuses (messageId, contactId, ack," String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
+ " seen, requested, expiry, txCount)" + " timestamp, length, state, groupShared, messageShared,"
+ " VALUES (?, ?, ?, ?, FALSE, 0, 0)"; + " deleted, ack, seen, requested, expiry, txCount)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0)";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
ps.setBoolean(3, ack); ps.setBytes(3, g.getBytes());
ps.setBoolean(4, seen); ps.setLong(4, timestamp);
ps.setInt(5, length);
ps.setInt(6, state.getValue());
ps.setBoolean(7, groupShared);
ps.setBoolean(8, messageShared);
ps.setBoolean(9, deleted);
ps.setBoolean(10, seen);
ps.setBoolean(11, seen);
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -941,12 +1017,9 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT NULL FROM messages AS m" String sql = "SELECT NULL FROM statuses"
+ " JOIN groupVisibilities AS gv" + " WHERE messageId = ? AND contactId = ?"
+ " ON m.groupId = gv.groupId" + " AND messageShared = TRUE";
+ " WHERE messageId = ?"
+ " AND contactId = ?"
+ " AND m.shared = TRUE";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
@@ -998,6 +1071,13 @@ abstract class JdbcDatabase implements Database<Connection> {
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
if (affected > 1) throw new DbStateException(); if (affected > 1) throw new DbStateException();
ps.close(); ps.close();
// Update denormalised column in statuses
sql = "UPDATE statuses SET deleted = TRUE WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -1220,18 +1300,19 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<ContactId> getGroupVisibility(Connection txn, GroupId g) public Map<ContactId, Boolean> getGroupVisibility(Connection txn, GroupId g)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT contactId FROM groupVisibilities" String sql = "SELECT contactId, shared FROM groupVisibilities"
+ " WHERE groupId = ?"; + " WHERE groupId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<ContactId> visible = new ArrayList<>(); Map<ContactId, Boolean> visible = new HashMap<>();
while (rs.next()) visible.add(new ContactId(rs.getInt(1))); while (rs.next())
visible.put(new ContactId(rs.getInt(1)), rs.getBoolean(2));
rs.close(); rs.close();
ps.close(); ps.close();
return visible; return visible;
@@ -1509,12 +1590,8 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT m.messageId, txCount > 0, seen" String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
+ " FROM messages AS m" + " WHERE groupId = ? AND contactId = ?";
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " WHERE groupId = ?"
+ " AND contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes()); ps.setBytes(1, g.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
@@ -1537,15 +1614,13 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public MessageStatus getMessageStatus(Connection txn, public MessageStatus getMessageStatus(Connection txn, ContactId c,
ContactId c, MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT txCount > 0, seen" String sql = "SELECT txCount > 0, seen FROM statuses"
+ " FROM statuses" + " WHERE messageId = ? AND contactId = ?";
+ " WHERE messageId = ?"
+ " AND contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes()); ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt()); ps.setInt(2, c.getInt());
@@ -1687,14 +1762,10 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT m.messageId FROM messages AS m" String sql = "SELECT messageId FROM statuses"
+ " JOIN groupVisibilities AS gv" + " WHERE contactId = ? AND state = ?"
+ " ON m.groupId = gv.groupId" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " JOIN statuses AS s" + " AND deleted = FALSE"
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE AND requested = FALSE" + " AND seen = FALSE AND requested = FALSE"
+ " AND expiry < ?" + " AND expiry < ?"
+ " ORDER BY timestamp LIMIT ?"; + " ORDER BY timestamp LIMIT ?";
@@ -1748,14 +1819,10 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT length, m.messageId FROM messages AS m" String sql = "SELECT length, messageId FROM statuses"
+ " JOIN groupVisibilities AS gv" + " WHERE contactId = ? AND state = ?"
+ " ON m.groupId = gv.groupId" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " JOIN statuses AS s" + " AND deleted = FALSE"
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE" + " AND seen = FALSE"
+ " AND expiry < ?" + " AND expiry < ?"
+ " ORDER BY timestamp"; + " ORDER BY timestamp";
@@ -1783,28 +1850,26 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<MessageId> getMessagesToValidate(Connection txn, public Collection<MessageId> getMessagesToValidate(Connection txn)
ClientId c) throws DbException { throws DbException {
return getMessagesInState(txn, c, UNKNOWN); return getMessagesInState(txn, UNKNOWN);
} }
@Override @Override
public Collection<MessageId> getPendingMessages(Connection txn, public Collection<MessageId> getPendingMessages(Connection txn)
ClientId c) throws DbException { throws DbException {
return getMessagesInState(txn, c, PENDING); return getMessagesInState(txn, PENDING);
} }
private Collection<MessageId> getMessagesInState(Connection txn, ClientId c, private Collection<MessageId> getMessagesInState(Connection txn,
State state) throws DbException { State state) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT messageId FROM messages AS m" String sql = "SELECT messageId FROM messages"
+ " JOIN groups AS g ON m.groupId = g.groupId" + " WHERE state = ? AND raw IS NOT NULL";
+ " WHERE state = ? AND clientId = ? AND raw IS NOT NULL";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, state.getValue()); ps.setInt(1, state.getValue());
ps.setString(2, c.getString());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
@@ -1819,8 +1884,8 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
@Override @Override
public Collection<MessageId> getMessagesToShare( public Collection<MessageId> getMessagesToShare(Connection txn)
Connection txn, ClientId c) throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
@@ -1829,12 +1894,10 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON m.messageId = d.dependencyId" + " ON m.messageId = d.dependencyId"
+ " JOIN messages AS m1" + " JOIN messages AS m1"
+ " ON d.messageId = m1.messageId" + " ON d.messageId = m1.messageId"
+ " JOIN groups AS g" + " WHERE m.state = ?"
+ " ON m.groupId = g.groupId" + " AND m.shared = FALSE AND m1.shared = TRUE";
+ " WHERE m.shared = FALSE AND m1.shared = TRUE"
+ " AND g.clientId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setString(1, c.getString()); ps.setInt(1, DELIVERED.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
while (rs.next()) ids.add(new MessageId(rs.getBytes(1))); while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
@@ -1854,15 +1917,10 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT expiry FROM messages AS m" String sql = "SELECT expiry FROM statuses"
+ " JOIN groupVisibilities AS gv" + " WHERE contactId = ? AND state = ?"
+ " ON m.groupId = gv.groupId" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " JOIN statuses AS s" + " AND deleted = FALSE AND seen = FALSE"
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE"
+ " ORDER BY expiry LIMIT 1"; + " ORDER BY expiry LIMIT 1";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setInt(1, c.getInt());
@@ -1914,14 +1972,10 @@ abstract class JdbcDatabase implements Database<Connection> {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {
String sql = "SELECT length, m.messageId FROM messages AS m" String sql = "SELECT length, messageId FROM statuses"
+ " JOIN groupVisibilities AS gv" + " WHERE contactId = ? AND state = ?"
+ " ON m.groupId = gv.groupId" + " AND groupShared = TRUE AND messageShared = TRUE"
+ " JOIN statuses AS s" + " AND deleted = FALSE"
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE AND requested = TRUE" + " AND seen = FALSE AND requested = TRUE"
+ " AND expiry < ?" + " AND expiry < ?"
+ " ORDER BY timestamp"; + " ORDER BY timestamp";
@@ -2397,6 +2451,8 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
// Remove status rows for the messages in the group
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m);
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -2436,8 +2492,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override private boolean removeOfferedMessage(Connection txn, ContactId c,
public boolean removeOfferedMessage(Connection txn, ContactId c,
MessageId m) throws DbException { MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
@@ -2481,16 +2536,15 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
@Override private void removeStatus(Connection txn, ContactId c, MessageId m)
public void removeStatus(Connection txn, ContactId c, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {
String sql = "DELETE FROM statuses" String sql = "DELETE FROM statuses"
+ " WHERE contactId = ? AND messageId = ?"; + " WHERE messageId = ? AND contactId = ?";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt()); ps.setBytes(1, m.getBytes());
ps.setBytes(2, m.getBytes()); ps.setInt(2, c.getInt());
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException(); if (affected != 1) throw new DbStateException();
ps.close(); ps.close();
@@ -2586,6 +2640,16 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
// Update denormalised column in statuses
sql = "UPDATE statuses SET groupShared = ?"
+ " WHERE contactId = ? AND groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, shared);
ps.setInt(2, c.getInt());
ps.setBytes(3, g.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -2604,6 +2668,14 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate(); int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException(); if (affected < 0 || affected > 1) throw new DbStateException();
ps.close(); ps.close();
// Update denormalised column in statuses
sql = "UPDATE statuses SET messageShared = TRUE"
+ " WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);
@@ -2630,6 +2702,14 @@ abstract class JdbcDatabase implements Database<Connection> {
affected = ps.executeUpdate(); affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException(); if (affected < 0) throw new DbStateException();
ps.close(); ps.close();
// Update denormalised column in statuses
sql = "UPDATE statuses SET state = ? WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setInt(1, state.getValue());
ps.setBytes(2, m.getBytes());
affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) { } catch (SQLException e) {
tryToClose(ps); tryToClose(ps);
throw new DbException(e); throw new DbException(e);

View File

@@ -0,0 +1,84 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.db.DbException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
class Migration31_32 implements Migration<Connection> {
private static final Logger LOG =
Logger.getLogger(Migration31_32.class.getName());
@Override
public int getStartVersion() {
return 31;
}
@Override
public int getEndVersion() {
return 32;
}
@Override
public void migrate(Connection txn) throws DbException {
Statement s = null;
try {
s = txn.createStatement();
// Add denormalised columns
s.execute("ALTER TABLE statuses ADD COLUMN"
+ " (groupId BINARY(32),"
+ " timestamp BIGINT,"
+ " length INT,"
+ " state INT,"
+ " groupShared BOOLEAN,"
+ " messageShared BOOLEAN,"
+ " deleted BOOLEAN)");
// Populate columns from messages table
s.execute("UPDATE statuses AS s SET (groupId, timestamp, length,"
+ " state, messageShared, deleted) ="
+ " (SELECT groupId, timestamp, length, state, shared,"
+ " raw IS NULL FROM messages AS m"
+ " WHERE s.messageId = m.messageId)");
// Populate column from groupVisibilities table
s.execute("UPDATE statuses AS s SET groupShared ="
+ " (SELECT shared FROM groupVisibilities AS gv"
+ " WHERE s.contactId = gv.contactId"
+ " AND s.groupId = gv.groupId)");
// Add not null constraints now columns have been populated
s.execute("ALTER TABLE statuses ALTER COLUMN groupId SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN timestamp"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN length SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN state SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN groupShared"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN messageShared"
+ " SET NOT NULL");
s.execute("ALTER TABLE statuses ALTER COLUMN deleted SET NOT NULL");
// Add foreign key constraint
s.execute("ALTER TABLE statuses"
+ " ADD CONSTRAINT statusesForeignKeyGroupId"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE");
} catch (SQLException e) {
tryToClose(s);
throw new DbException(e);
}
}
private void tryToClose(@Nullable Statement s) {
try {
if (s != null) s.close();
} catch (SQLException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -241,10 +241,11 @@ class LanTcpPlugin extends TcpPlugin {
} }
return null; return null;
} }
Socket s = new Socket();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
@@ -47,7 +48,7 @@ abstract class TcpPlugin implements DuplexPlugin {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TcpPlugin.class.getName()); Logger.getLogger(TcpPlugin.class.getName());
protected final Executor ioExecutor; protected final Executor ioExecutor, bindExecutor;
protected final Backoff backoff; protected final Backoff backoff;
protected final DuplexPluginCallback callback; protected final DuplexPluginCallback callback;
protected final int maxLatency, maxIdleTime, socketTimeout; protected final int maxLatency, maxIdleTime, socketTimeout;
@@ -90,6 +91,8 @@ abstract class TcpPlugin implements DuplexPlugin {
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; else socketTimeout = maxIdleTime * 2;
// Don't execute more than one bind operation at a time
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
} }
@Override @Override
@@ -110,8 +113,9 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
protected void bind() { protected void bind() {
ioExecutor.execute(() -> { bindExecutor.execute(() -> {
if (!running) return; if (!running) return;
if (socket != null && !socket.isClosed()) return;
ServerSocket ss = null; ServerSocket ss = null;
for (InetSocketAddress addr : getLocalSocketAddresses()) { for (InetSocketAddress addr : getLocalSocketAddresses()) {
try { try {
@@ -243,10 +247,11 @@ abstract class TcpPlugin implements DuplexPlugin {
} }
continue; continue;
} }
Socket s = new Socket();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
s.connect(remote); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -261,6 +266,10 @@ abstract class TcpPlugin implements DuplexPlugin {
return null; return null;
} }
protected Socket createSocket() throws IOException {
return new Socket();
}
@Nullable @Nullable
InetSocketAddress parseSocketAddress(String ipPort) { InetSocketAddress parseSocketAddress(String ipPort) {
if (StringUtils.isNullOrEmpty(ipPort)) return null; if (StringUtils.isNullOrEmpty(ipPort)) return null;

View File

@@ -71,11 +71,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Override @Override
public void startService() { public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
for (ClientId c : validators.keySet()) { validateOutstandingMessagesAsync();
validateOutstandingMessagesAsync(c); deliverOutstandingMessagesAsync();
deliverOutstandingMessagesAsync(c); shareOutstandingMessagesAsync();
shareOutstandingMessagesAsync(c);
}
} }
@Override @Override
@@ -93,17 +91,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook); hooks.put(c, hook);
} }
private void validateOutstandingMessagesAsync(ClientId c) { private void validateOutstandingMessagesAsync() {
dbExecutor.execute(() -> validateOutstandingMessages(c)); dbExecutor.execute(this::validateOutstandingMessages);
} }
@DatabaseExecutor @DatabaseExecutor
private void validateOutstandingMessages(ClientId c) { private void validateOutstandingMessages() {
try { try {
Queue<MessageId> unvalidated = new LinkedList<>(); Queue<MessageId> unvalidated = new LinkedList<>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
unvalidated.addAll(db.getMessagesToValidate(txn, c)); unvalidated.addAll(db.getMessagesToValidate(txn));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -148,17 +146,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
} }
} }
private void deliverOutstandingMessagesAsync(ClientId c) { private void deliverOutstandingMessagesAsync() {
dbExecutor.execute(() -> deliverOutstandingMessages(c)); dbExecutor.execute(this::deliverOutstandingMessages);
} }
@DatabaseExecutor @DatabaseExecutor
private void deliverOutstandingMessages(ClientId c) { private void deliverOutstandingMessages() {
try { try {
Queue<MessageId> pending = new LinkedList<>(); Queue<MessageId> pending = new LinkedList<>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
pending.addAll(db.getPendingMessages(txn, c)); pending.addAll(db.getPendingMessages(txn));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -353,17 +351,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
return pending; return pending;
} }
private void shareOutstandingMessagesAsync(ClientId c) { private void shareOutstandingMessagesAsync() {
dbExecutor.execute(() -> shareOutstandingMessages(c)); dbExecutor.execute(this::shareOutstandingMessages);
} }
@DatabaseExecutor @DatabaseExecutor
private void shareOutstandingMessages(ClientId c) { private void shareOutstandingMessages() {
try { try {
Queue<MessageId> toShare = new LinkedList<>(); Queue<MessageId> toShare = new LinkedList<>();
Transaction txn = db.startTransaction(true); Transaction txn = db.startTransaction(true);
try { try {
toShare.addAll(db.getMessagesToShare(txn, c)); toShare.addAll(db.getMessagesToShare(txn));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);

View File

@@ -4,8 +4,9 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -25,7 +26,10 @@ public class SystemModule {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
public SystemModule() { public SystemModule() {
scheduler = Executors.newSingleThreadScheduledExecutor(); // Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduler = new ScheduledThreadPoolExecutor(1, policy);
} }
@Provides @Provides

View File

@@ -48,6 +48,7 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -56,9 +57,12 @@ import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
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.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
@@ -160,7 +164,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
ContactStatusChangedEvent.class))); ContactStatusChangedEvent.class)));
// getContacts() // getContacts()
oneOf(database).getContacts(txn); oneOf(database).getContacts(txn);
will(returnValue(Collections.singletonList(contact))); will(returnValue(singletonList(contact)));
// addGroup() // addGroup()
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(false)); will(returnValue(false));
@@ -171,12 +175,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
// getGroups() // getGroups()
oneOf(database).getGroups(txn, clientId); oneOf(database).getGroups(txn, clientId);
will(returnValue(Collections.singletonList(group))); will(returnValue(singletonList(group)));
// removeGroup() // removeGroup()
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getGroupVisibility(txn, groupId); oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.emptyList())); will(returnValue(emptyMap()));
oneOf(database).removeGroup(txn, groupId); oneOf(database).removeGroup(txn, groupId);
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class))); oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
@@ -206,11 +210,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
assertEquals(contactId, assertEquals(contactId,
db.addContact(transaction, author, localAuthorId, true, db.addContact(transaction, author, localAuthorId, true,
true)); true));
assertEquals(Collections.singletonList(contact), assertEquals(singletonList(contact),
db.getContacts(transaction)); db.getContacts(transaction));
db.addGroup(transaction, group); // First time - listeners called db.addGroup(transaction, group); // First time - listeners called
db.addGroup(transaction, group); // Second time - not called db.addGroup(transaction, group); // Second time - not called
assertEquals(Collections.singletonList(group), assertEquals(singletonList(group),
db.getGroups(transaction, clientId)); db.getGroups(transaction, clientId));
db.removeGroup(transaction, group); db.removeGroup(transaction, group);
db.removeContact(transaction, contactId); db.removeContact(transaction, contactId);
@@ -255,13 +259,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true); oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// The message was added, so the listeners should be called // The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class))); oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
@@ -397,7 +396,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
Ack a = new Ack(Collections.singletonList(messageId)); Ack a = new Ack(singletonList(messageId));
db.receiveAck(transaction, contactId, a); db.receiveAck(transaction, contactId, a);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -418,7 +417,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
Offer o = new Offer(Collections.singletonList(messageId)); Offer o = new Offer(singletonList(messageId));
db.receiveOffer(transaction, contactId, o); db.receiveOffer(transaction, contactId, o);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -429,7 +428,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false); transaction = db.startTransaction(false);
try { try {
Request r = new Request(Collections.singletonList(messageId)); Request r = new Request(singletonList(messageId));
db.receiveRequest(transaction, contactId, r); db.receiveRequest(transaction, contactId, r);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
@@ -1022,7 +1021,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
Ack a = new Ack(Collections.singletonList(messageId)); Ack a = new Ack(singletonList(messageId));
db.receiveAck(transaction, contactId, a); db.receiveAck(transaction, contactId, a);
db.commitTransaction(transaction); db.commitTransaction(transaction);
} finally { } finally {
@@ -1042,12 +1041,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(VISIBLE)); will(returnValue(VISIBLE));
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).addMessage(txn, message, UNKNOWN, false); oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, true, true);
// Second time // Second time
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
@@ -1197,7 +1191,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
Request r = new Request(Collections.singletonList(messageId)); Request r = new Request(singletonList(messageId));
db.receiveRequest(transaction, contactId, r); db.receiveRequest(transaction, contactId, r);
db.commitTransaction(transaction); db.commitTransaction(transaction);
} finally { } finally {
@@ -1206,7 +1200,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} }
@Test @Test
public void testChangingVisibilityCallsListeners() throws Exception { public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
@@ -1215,16 +1213,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsGroup(txn, groupId); oneOf(database).containsGroup(txn, groupId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId); oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(INVISIBLE)); // Not yet visible will(returnValue(INVISIBLE));
oneOf(database).addGroupVisibility(txn, contactId, groupId, false); oneOf(database).addGroupVisibility(txn, contactId, groupId, false);
oneOf(database).getMessageIds(txn, groupId);
will(returnValue(Collections.singletonList(messageId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any( oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class))); GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
@@ -1236,6 +1231,48 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally { } finally {
db.endTransaction(transaction); db.endTransaction(transaction);
} }
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
}
@Test
public void testChangingVisibilityFromVisibleToInvisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(VISIBLE));
oneOf(database).removeGroupVisibility(txn, contactId, groupId);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
Transaction transaction = db.startTransaction(false);
try {
db.setGroupVisibility(transaction, contactId, groupId, INVISIBLE);
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
}
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
} }
@Test @Test
@@ -1267,8 +1304,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys(); TransportKeys transportKeys = createTransportKeys();
Map<ContactId, TransportKeys> keys = Collections.singletonMap( Map<ContactId, TransportKeys> keys =
contactId, transportKeys); singletonMap(contactId, transportKeys);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// startTransaction() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
@@ -1476,13 +1513,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true); oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).mergeMessageMetadata(txn, messageId, metadata); oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
// addMessageDependencies() // addMessageDependencies()
oneOf(database).containsMessage(txn, messageId); oneOf(database).containsMessage(txn, messageId);
will(returnValue(true)); will(returnValue(true));

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -25,7 +24,6 @@ import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestDatabaseConfig; import org.briarproject.bramble.test.TestDatabaseConfig;
import org.briarproject.bramble.test.TestUtils; import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -45,7 +43,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.db.Metadata.REMOVE; import static org.briarproject.bramble.api.db.Metadata.REMOVE;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -55,6 +52,12 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID; import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING; import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -74,7 +77,6 @@ public class H2DatabaseTest extends BrambleTestCase {
private final ClientId clientId; private final ClientId clientId;
private final Group group; private final Group group;
private final Author author; private final Author author;
private final AuthorId localAuthorId;
private final LocalAuthor localAuthor; private final LocalAuthor localAuthor;
private final MessageId messageId; private final MessageId messageId;
private final long timestamp; private final long timestamp;
@@ -85,19 +87,16 @@ public class H2DatabaseTest extends BrambleTestCase {
private final ContactId contactId; private final ContactId contactId;
public H2DatabaseTest() throws Exception { public H2DatabaseTest() throws Exception {
groupId = new GroupId(TestUtils.getRandomId()); groupId = new GroupId(getRandomId());
clientId = new ClientId(StringUtils.getRandomString(5)); clientId = new ClientId(getRandomString(123));
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH]; byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor); group = new Group(groupId, clientId, descriptor);
AuthorId authorId = new AuthorId(TestUtils.getRandomId()); author = getAuthor();
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]); localAuthor = getLocalAuthor();
localAuthorId = new AuthorId(TestUtils.getRandomId()); messageId = new MessageId(getRandomId());
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
messageId = new MessageId(TestUtils.getRandomId());
size = 1234; size = 1234;
raw = TestUtils.getRandomBytes(size); raw = getRandomBytes(size);
message = new Message(messageId, groupId, timestamp, raw); message = new Message(messageId, groupId, timestamp, raw);
transportId = new TransportId("id"); transportId = new TransportId("id");
contactId = new ContactId(1); contactId = new ContactId(1);
@@ -115,14 +114,14 @@ public class H2DatabaseTest extends BrambleTestCase {
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
assertTrue(db.containsContact(txn, contactId)); assertTrue(db.containsContact(txn, contactId));
assertFalse(db.containsGroup(txn, groupId)); assertFalse(db.containsGroup(txn, groupId));
db.addGroup(txn, group); db.addGroup(txn, group);
assertTrue(db.containsGroup(txn, groupId)); assertTrue(db.containsGroup(txn, groupId));
assertFalse(db.containsMessage(txn, messageId)); assertFalse(db.containsMessage(txn, messageId));
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -160,7 +159,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
// Removing the group should remove the message // Removing the group should remove the message
assertTrue(db.containsMessage(txn, messageId)); assertTrue(db.containsMessage(txn, messageId));
@@ -178,22 +177,15 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
// The message has no status yet, so it should not be sendable // The contact has not seen the message, so it should be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids =
ONE_MEGABYTE); db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100);
assertTrue(ids.isEmpty());
// Adding a status with seen = false should make the message sendable
db.addStatus(txn, contactId, messageId, false, false);
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
assertEquals(Collections.singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100); ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(Collections.singletonList(messageId), ids); assertEquals(Collections.singletonList(messageId), ids);
@@ -216,12 +208,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared but unvalidated message // Add a contact, a shared group and a shared but unvalidated message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, UNKNOWN, true); db.addMessage(txn, message, UNKNOWN, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The message has not been validated, so it should not be sendable // The message has not been validated, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -262,11 +253,10 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, an invisible group and a shared message // Add a contact, an invisible group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The group is invisible, so the message should not be sendable // The group is invisible, so the message should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -314,12 +304,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and an unshared message // Add a contact, a shared group and an unshared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, false); db.addMessage(txn, message, DELIVERED, false, null);
db.addStatus(txn, contactId, messageId, false, false);
// The message is not shared, so it should not be sendable // The message is not shared, so it should not be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -346,12 +335,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The message is sendable, but too large to send // The message is sendable, but too large to send
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -373,20 +361,16 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact and a visible group // Add a contact and a visible group
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
// Add some messages to ack // Add some messages to ack
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(getRandomId());
Message message1 = new Message(messageId1, groupId, timestamp, raw); Message message1 = new Message(messageId1, groupId, timestamp, raw);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, contactId);
db.addStatus(txn, contactId, messageId, false, true); db.addMessage(txn, message1, DELIVERED, true, contactId);
db.raiseAckFlag(txn, contactId, messageId);
db.addMessage(txn, message1, DELIVERED, true);
db.addStatus(txn, contactId, messageId1, false, true);
db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned // Both message IDs should be returned
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234); Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
@@ -399,6 +383,14 @@ public class H2DatabaseTest extends BrambleTestCase {
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn, assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
contactId, 1234)); contactId, 1234));
// Raise the ack flag again
db.raiseAckFlag(txn, contactId, messageId);
db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned
ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(Arrays.asList(messageId, messageId1), ids);
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
} }
@@ -410,12 +402,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// Retrieve the message from the database and mark it as sent // Retrieve the message from the database and mark it as sent
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId, Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
@@ -456,7 +447,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Storing a message should reduce the free space // Storing a message should reduce the free space
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.commitTransaction(txn); db.commitTransaction(txn);
assertTrue(db.getFreeSpace() < free); assertTrue(db.getFreeSpace() < free);
@@ -568,7 +559,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact and a shared group // Add a contact and a shared group
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
@@ -588,7 +579,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact // Add a contact
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
// The group is not in the database // The group is not in the database
@@ -604,15 +595,14 @@ public class H2DatabaseTest extends BrambleTestCase {
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
// Add a contact, a group and a message // Add a contact, an invisible group and a message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The group is not visible // The group is not visible so the message should not be visible
assertFalse(db.containsVisibleMessage(txn, contactId, messageId)); assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -626,37 +616,37 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact and a group // Add a contact and a group
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
// The group should not be visible to the contact // The group should not be visible to the contact
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertEquals(Collections.emptyList(), assertEquals(Collections.emptyMap(),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
// Make the group visible to the contact // Make the group visible to the contact
db.addGroupVisibility(txn, contactId, groupId, false); db.addGroupVisibility(txn, contactId, groupId, false);
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertEquals(Collections.singletonList(contactId), assertEquals(Collections.singletonMap(contactId, false),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
// Share the group with the contact // Share the group with the contact
db.setGroupVisibility(txn, contactId, groupId, true); db.setGroupVisibility(txn, contactId, groupId, true);
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
assertEquals(Collections.singletonList(contactId), assertEquals(Collections.singletonMap(contactId, true),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
// Unshare the group with the contact // Unshare the group with the contact
db.setGroupVisibility(txn, contactId, groupId, false); db.setGroupVisibility(txn, contactId, groupId, false);
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertEquals(Collections.singletonList(contactId), assertEquals(Collections.singletonMap(contactId, false),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
// Make the group invisible again // Make the group invisible again
db.removeGroupVisibility(txn, contactId, groupId); db.removeGroupVisibility(txn, contactId, groupId);
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId)); assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
assertEquals(Collections.emptyList(), assertEquals(Collections.emptyMap(),
db.getGroupVisibility(txn, groupId)); db.getGroupVisibility(txn, groupId));
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -676,7 +666,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add the contact, the transport and the transport keys // Add the contact, the transport and the transport keys
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
db.addTransportKeys(txn, contactId, keys); db.addTransportKeys(txn, contactId, keys);
@@ -738,7 +728,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys)); db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
@@ -774,7 +764,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add the contact, transport and transport keys // Add the contact, transport and transport keys
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addTransport(txn, transportId, 123); db.addTransport(txn, transportId, 123);
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys)); db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
@@ -809,7 +799,7 @@ public class H2DatabaseTest extends BrambleTestCase {
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
// Add a contact associated with the local author // Add a contact associated with the local author
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
// Ensure contact is returned from database by Author ID // Ensure contact is returned from database by Author ID
@@ -834,18 +824,19 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a local author - no contacts should be associated // Add a local author - no contacts should be associated
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
Collection<ContactId> contacts = db.getContacts(txn, localAuthorId); Collection<ContactId> contacts =
db.getContacts(txn, localAuthor.getId());
assertEquals(Collections.emptyList(), contacts); assertEquals(Collections.emptyList(), contacts);
// Add a contact associated with the local author // Add a contact associated with the local author
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
contacts = db.getContacts(txn, localAuthorId); contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(Collections.singletonList(contactId), contacts); assertEquals(Collections.singletonList(contactId), contacts);
// Remove the local author - the contact should be removed // Remove the local author - the contact should be removed
db.removeLocalAuthor(txn, localAuthorId); db.removeLocalAuthor(txn, localAuthor.getId());
contacts = db.getContacts(txn, localAuthorId); contacts = db.getContacts(txn, localAuthor.getId());
assertEquals(Collections.emptyList(), contacts); assertEquals(Collections.emptyList(), contacts);
assertFalse(db.containsContact(txn, contactId)); assertFalse(db.containsContact(txn, contactId));
@@ -860,14 +851,14 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact - initially there should be no offered messages // Add a contact - initially there should be no offered messages
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
assertEquals(0, db.countOfferedMessages(txn, contactId)); assertEquals(0, db.countOfferedMessages(txn, contactId));
// Add some offered messages and count them // Add some offered messages and count them
List<MessageId> ids = new ArrayList<>(); List<MessageId> ids = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
MessageId m = new MessageId(TestUtils.getRandomId()); MessageId m = new MessageId(getRandomId());
db.addOfferedMessage(txn, contactId, m); db.addOfferedMessage(txn, contactId, m);
ids.add(m); ids.add(m);
} }
@@ -876,8 +867,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Remove some of the offered messages and count again // Remove some of the offered messages and count again
List<MessageId> half = ids.subList(0, 5); List<MessageId> half = ids.subList(0, 5);
db.removeOfferedMessages(txn, contactId, half); db.removeOfferedMessages(txn, contactId, half);
assertTrue(db.removeOfferedMessage(txn, contactId, ids.get(5))); assertEquals(5, db.countOfferedMessages(txn, contactId));
assertEquals(4, db.countOfferedMessages(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -928,7 +918,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
// Attach some metadata to the message // Attach some metadata to the message
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
@@ -999,7 +989,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
// Attach some metadata to the message // Attach some metadata to the message
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
@@ -1052,7 +1042,7 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testMetadataQueries() throws Exception { public void testMetadataQueries() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(getRandomId());
Message message1 = new Message(messageId1, groupId, timestamp, raw); Message message1 = new Message(messageId1, groupId, timestamp, raw);
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -1060,8 +1050,8 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and two messages // Add a group and two messages
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message1, DELIVERED, true); db.addMessage(txn, message1, DELIVERED, true, null);
// Attach some metadata to the messages // Attach some metadata to the messages
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
@@ -1156,7 +1146,7 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception { public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(getRandomId());
Message message1 = new Message(messageId1, groupId, timestamp, raw); Message message1 = new Message(messageId1, groupId, timestamp, raw);
Database<Connection> db = open(false); Database<Connection> db = open(false);
@@ -1164,8 +1154,8 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and two messages // Add a group and two messages
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addMessage(txn, message1, DELIVERED, true); db.addMessage(txn, message1, DELIVERED, true, null);
// Attach some metadata to the messages // Attach some metadata to the messages
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
@@ -1227,10 +1217,10 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testMessageDependencies() throws Exception { public void testMessageDependencies() throws Exception {
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(getRandomId());
MessageId messageId2 = new MessageId(TestUtils.getRandomId()); MessageId messageId2 = new MessageId(getRandomId());
MessageId messageId3 = new MessageId(TestUtils.getRandomId()); MessageId messageId3 = new MessageId(getRandomId());
MessageId messageId4 = new MessageId(TestUtils.getRandomId()); MessageId messageId4 = new MessageId(getRandomId());
Message message1 = new Message(messageId1, groupId, timestamp, raw); Message message1 = new Message(messageId1, groupId, timestamp, raw);
Message message2 = new Message(messageId2, groupId, timestamp, raw); Message message2 = new Message(messageId2, groupId, timestamp, raw);
@@ -1239,9 +1229,9 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and some messages // Add a group and some messages
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, PENDING, true); db.addMessage(txn, message, PENDING, true, contactId);
db.addMessage(txn, message1, DELIVERED, true); db.addMessage(txn, message1, DELIVERED, true, contactId);
db.addMessage(txn, message2, INVALID, true); db.addMessage(txn, message2, INVALID, true, contactId);
// Add dependencies // Add dependencies
db.addMessageDependency(txn, groupId, messageId, messageId1); db.addMessageDependency(txn, groupId, messageId, messageId1);
@@ -1308,26 +1298,26 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, PENDING, true); db.addMessage(txn, message, PENDING, true, contactId);
// Add a second group // Add a second group
GroupId groupId1 = new GroupId(TestUtils.getRandomId()); GroupId groupId1 = new GroupId(getRandomId());
Group group1 = new Group(groupId1, clientId, Group group1 = new Group(groupId1, clientId,
TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH)); getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
db.addGroup(txn, group1); db.addGroup(txn, group1);
// Add a message to the second group // Add a message to the second group
MessageId messageId1 = new MessageId(TestUtils.getRandomId()); MessageId messageId1 = new MessageId(getRandomId());
Message message1 = new Message(messageId1, groupId1, timestamp, raw); Message message1 = new Message(messageId1, groupId1, timestamp, raw);
db.addMessage(txn, message1, DELIVERED, true); db.addMessage(txn, message1, DELIVERED, true, contactId);
// Create an ID for a missing message // Create an ID for a missing message
MessageId messageId2 = new MessageId(TestUtils.getRandomId()); MessageId messageId2 = new MessageId(getRandomId());
// Add another message to the first group // Add another message to the first group
MessageId messageId3 = new MessageId(TestUtils.getRandomId()); MessageId messageId3 = new MessageId(getRandomId());
Message message3 = new Message(messageId3, groupId, timestamp, raw); Message message3 = new Message(messageId3, groupId, timestamp, raw);
db.addMessage(txn, message3, DELIVERED, true); db.addMessage(txn, message3, DELIVERED, true, contactId);
// Add dependencies between the messages // Add dependencies between the messages
db.addMessageDependency(txn, groupId, messageId, messageId1); db.addMessageDependency(txn, groupId, messageId, messageId1);
@@ -1360,10 +1350,10 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testGetPendingMessagesForDelivery() throws Exception { public void testGetPendingMessagesForDelivery() throws Exception {
MessageId mId1 = new MessageId(TestUtils.getRandomId()); MessageId mId1 = new MessageId(getRandomId());
MessageId mId2 = new MessageId(TestUtils.getRandomId()); MessageId mId2 = new MessageId(getRandomId());
MessageId mId3 = new MessageId(TestUtils.getRandomId()); MessageId mId3 = new MessageId(getRandomId());
MessageId mId4 = new MessageId(TestUtils.getRandomId()); MessageId mId4 = new MessageId(getRandomId());
Message m1 = new Message(mId1, groupId, timestamp, raw); Message m1 = new Message(mId1, groupId, timestamp, raw);
Message m2 = new Message(mId2, groupId, timestamp, raw); Message m2 = new Message(mId2, groupId, timestamp, raw);
Message m3 = new Message(mId3, groupId, timestamp, raw); Message m3 = new Message(mId3, groupId, timestamp, raw);
@@ -1374,20 +1364,20 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and some messages with different states // Add a group and some messages with different states
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, m1, UNKNOWN, true); db.addMessage(txn, m1, UNKNOWN, true, contactId);
db.addMessage(txn, m2, INVALID, true); db.addMessage(txn, m2, INVALID, true, contactId);
db.addMessage(txn, m3, PENDING, true); db.addMessage(txn, m3, PENDING, true, contactId);
db.addMessage(txn, m4, DELIVERED, true); db.addMessage(txn, m4, DELIVERED, true, contactId);
Collection<MessageId> result; Collection<MessageId> result;
// Retrieve messages to be validated // Retrieve messages to be validated
result = db.getMessagesToValidate(txn, clientId); result = db.getMessagesToValidate(txn);
assertEquals(1, result.size()); assertEquals(1, result.size());
assertTrue(result.contains(mId1)); assertTrue(result.contains(mId1));
// Retrieve pending messages // Retrieve pending messages
result = db.getPendingMessages(txn, clientId); result = db.getPendingMessages(txn);
assertEquals(1, result.size()); assertEquals(1, result.size());
assertTrue(result.contains(mId3)); assertTrue(result.contains(mId3));
@@ -1397,10 +1387,10 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testGetMessagesToShare() throws Exception { public void testGetMessagesToShare() throws Exception {
MessageId mId1 = new MessageId(TestUtils.getRandomId()); MessageId mId1 = new MessageId(getRandomId());
MessageId mId2 = new MessageId(TestUtils.getRandomId()); MessageId mId2 = new MessageId(getRandomId());
MessageId mId3 = new MessageId(TestUtils.getRandomId()); MessageId mId3 = new MessageId(getRandomId());
MessageId mId4 = new MessageId(TestUtils.getRandomId()); MessageId mId4 = new MessageId(getRandomId());
Message m1 = new Message(mId1, groupId, timestamp, raw); Message m1 = new Message(mId1, groupId, timestamp, raw);
Message m2 = new Message(mId2, groupId, timestamp, raw); Message m2 = new Message(mId2, groupId, timestamp, raw);
Message m3 = new Message(mId3, groupId, timestamp, raw); Message m3 = new Message(mId3, groupId, timestamp, raw);
@@ -1411,10 +1401,10 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and some messages // Add a group and some messages
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, m1, DELIVERED, true); db.addMessage(txn, m1, DELIVERED, true, contactId);
db.addMessage(txn, m2, DELIVERED, false); db.addMessage(txn, m2, DELIVERED, false, contactId);
db.addMessage(txn, m3, DELIVERED, false); db.addMessage(txn, m3, DELIVERED, false, contactId);
db.addMessage(txn, m4, DELIVERED, true); db.addMessage(txn, m4, DELIVERED, true, contactId);
// Introduce dependencies between the messages // Introduce dependencies between the messages
db.addMessageDependency(txn, groupId, mId1, mId2); db.addMessageDependency(txn, groupId, mId1, mId2);
@@ -1422,8 +1412,7 @@ public class H2DatabaseTest extends BrambleTestCase {
db.addMessageDependency(txn, groupId, mId4, mId3); db.addMessageDependency(txn, groupId, mId4, mId3);
// Retrieve messages to be shared // Retrieve messages to be shared
Collection<MessageId> result = Collection<MessageId> result = db.getMessagesToShare(txn);
db.getMessagesToShare(txn, clientId);
assertEquals(2, result.size()); assertEquals(2, result.size());
assertTrue(result.contains(mId2)); assertTrue(result.contains(mId2));
assertTrue(result.contains(mId3)); assertTrue(result.contains(mId3));
@@ -1439,12 +1428,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The message should not be sent or seen // The message should not be sent or seen
MessageStatus status = db.getMessageStatus(txn, contactId, messageId); MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
@@ -1508,9 +1496,7 @@ public class H2DatabaseTest extends BrambleTestCase {
@Test @Test
public void testDifferentLocalAuthorsCanHaveTheSameContact() public void testDifferentLocalAuthorsCanHaveTheSameContact()
throws Exception { throws Exception {
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId()); LocalAuthor localAuthor1 = getLocalAuthor();
LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
Database<Connection> db = open(false); Database<Connection> db = open(false);
Connection txn = db.startTransaction(); Connection txn = db.startTransaction();
@@ -1521,15 +1507,15 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add the same contact for each local author // Add the same contact for each local author
ContactId contactId = ContactId contactId =
db.addContact(txn, author, localAuthorId, true, true); db.addContact(txn, author, localAuthor.getId(), true, true);
ContactId contactId1 = ContactId contactId1 =
db.addContact(txn, author, localAuthorId1, true, true); db.addContact(txn, author, localAuthor1.getId(), true, true);
// The contacts should be distinct // The contacts should be distinct
assertNotEquals(contactId, contactId1); assertNotEquals(contactId, contactId1);
assertEquals(2, db.getContacts(txn).size()); assertEquals(2, db.getContacts(txn).size());
assertEquals(1, db.getContacts(txn, localAuthorId).size()); assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
assertEquals(1, db.getContacts(txn, localAuthorId1).size()); assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
db.commitTransaction(txn); db.commitTransaction(txn);
db.close(); db.close();
@@ -1542,12 +1528,11 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact, a shared group and a shared message // Add a contact, a shared group and a shared message
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addMessage(txn, message, DELIVERED, true); db.addMessage(txn, message, DELIVERED, true, null);
db.addStatus(txn, contactId, messageId, false, false);
// The message should be visible to the contact // The message should be visible to the contact
assertTrue(db.containsVisibleMessage(txn, contactId, messageId)); assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
@@ -1588,7 +1573,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a contact // Add a contact
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId, assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
// The contact should be active // The contact should be active
@@ -1621,7 +1606,7 @@ public class H2DatabaseTest extends BrambleTestCase {
// Add a group and a message // Add a group and a message
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false); db.addMessage(txn, message, UNKNOWN, false, contactId);
// Walk the message through the validation and delivery states // Walk the message through the validation and delivery states
assertEquals(UNKNOWN, db.getMessageState(txn, messageId)); assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
@@ -1647,14 +1632,13 @@ public class H2DatabaseTest extends BrambleTestCase {
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(), assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
true, true)); true, true));
db.addGroup(txn, group); db.addGroup(txn, group);
db.addMessage(txn, message, UNKNOWN, false); db.addMessage(txn, message, UNKNOWN, false, null);
// There should be no messages to send // There should be no messages to send
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Share the group with the contact - still no messages to send // Share the group with the contact - still no messages to send
db.addGroupVisibility(txn, contactId, groupId, true); db.addGroupVisibility(txn, contactId, groupId, true);
db.addStatus(txn, contactId, messageId, false, false);
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId)); assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Set the message's state to DELIVERED - still no messages to send // Set the message's state to DELIVERED - still no messages to send
@@ -1665,6 +1649,10 @@ public class H2DatabaseTest extends BrambleTestCase {
db.setMessageShared(txn, messageId); db.setMessageShared(txn, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId)); assertEquals(0, db.getNextSendTime(txn, contactId));
// Mark the message as requested - it should still be sendable
db.raiseRequestedFlag(txn, contactId, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId));
// Update the message's expiry time as though we sent it - now the // Update the message's expiry time as though we sent it - now the
// message should be sendable after one round-trip // message should be sendable after one round-trip
db.updateExpiryTime(txn, contactId, messageId, 1000); db.updateExpiryTime(txn, contactId, messageId, 1000);
@@ -1713,20 +1701,20 @@ public class H2DatabaseTest extends BrambleTestCase {
} }
private TransportKeys createTransportKeys() { private TransportKeys createTransportKeys() {
SecretKey inPrevTagKey = TestUtils.getSecretKey(); SecretKey inPrevTagKey = getSecretKey();
SecretKey inPrevHeaderKey = TestUtils.getSecretKey(); SecretKey inPrevHeaderKey = getSecretKey();
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey, IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
1, 123, new byte[4]); 1, 123, new byte[4]);
SecretKey inCurrTagKey = TestUtils.getSecretKey(); SecretKey inCurrTagKey = getSecretKey();
SecretKey inCurrHeaderKey = TestUtils.getSecretKey(); SecretKey inCurrHeaderKey = getSecretKey();
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey, IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
2, 234, new byte[4]); 2, 234, new byte[4]);
SecretKey inNextTagKey = TestUtils.getSecretKey(); SecretKey inNextTagKey = getSecretKey();
SecretKey inNextHeaderKey = TestUtils.getSecretKey(); SecretKey inNextHeaderKey = getSecretKey();
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey, IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
3, 345, new byte[4]); 3, 345, new byte[4]);
SecretKey outCurrTagKey = TestUtils.getSecretKey(); SecretKey outCurrTagKey = getSecretKey();
SecretKey outCurrHeaderKey = TestUtils.getSecretKey(); SecretKey outCurrHeaderKey = getSecretKey();
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey, OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
2, 456); 2, 456);
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr); return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);

View File

@@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue; import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN; import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getMessage;
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.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -34,7 +35,7 @@ public class Migration30_31Test extends BrambleTestCase {
private static final String CREATE_GROUPS_STUB = private static final String CREATE_GROUPS_STUB =
"CREATE TABLE groups" "CREATE TABLE groups"
+ " (groupID BINARY(32) NOT NULL," + " (groupId BINARY(32) NOT NULL,"
+ " PRIMARY KEY (groupId))"; + " PRIMARY KEY (groupId))";
private static final String CREATE_MESSAGES = private static final String CREATE_MESSAGES =
@@ -66,8 +67,8 @@ public class Migration30_31Test extends BrambleTestCase {
private final String url = "jdbc:h2:" + db.getAbsolutePath(); private final String url = "jdbc:h2:" + db.getAbsolutePath();
private final GroupId groupId = new GroupId(getRandomId()); private final GroupId groupId = new GroupId(getRandomId());
private final GroupId groupId1 = new GroupId(getRandomId()); private final GroupId groupId1 = new GroupId(getRandomId());
private final Message message = TestUtils.getMessage(groupId); private final Message message = getMessage(groupId);
private final Message message1 = TestUtils.getMessage(groupId1); private final Message message1 = getMessage(groupId1);
private final Metadata meta = new Metadata(), meta1 = new Metadata(); private final Metadata meta = new Metadata(), meta1 = new Metadata();
private Connection connection = null; private Connection connection = null;

View File

@@ -0,0 +1,369 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.ValidationManager.State;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import static java.sql.Types.BINARY;
import static junit.framework.Assert.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.junit.Assert.assertEquals;
public class Migration31_32Test extends BrambleTestCase {
private static final String CREATE_GROUPS_STUB =
"CREATE TABLE groups"
+ " (groupId BINARY(32) NOT NULL,"
+ " PRIMARY KEY (groupId))";
private static final String CREATE_CONTACTS_STUB =
"CREATE TABLE contacts"
+ " (contactId INT NOT NULL,"
+ " PRIMARY KEY (contactId))";
private static final String CREATE_GROUP_VISIBILITIES_STUB =
"CREATE TABLE groupVisibilities"
+ " (contactId INT NOT NULL,"
+ " groupId BINARY(32) NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " PRIMARY KEY (contactId, groupId))";
private static final String CREATE_MESSAGES =
"CREATE TABLE messages"
+ " (messageId BINARY(32) NOT NULL,"
+ " groupId BINARY(32) NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " state INT NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " length INT NOT NULL,"
+ " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId),"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_STATUSES_31 =
"CREATE TABLE statuses"
+ " (messageId BINARY(32) NOT NULL,"
+ " contactId INT NOT NULL,"
+ " ack BOOLEAN NOT NULL,"
+ " seen BOOLEAN NOT NULL,"
+ " requested BOOLEAN NOT NULL,"
+ " expiry BIGINT NOT NULL,"
+ " txCount INT NOT NULL,"
+ " PRIMARY KEY (messageId, contactId),"
+ " FOREIGN KEY (messageId)"
+ " REFERENCES messages (messageId)"
+ " ON DELETE CASCADE,"
+ " FOREIGN KEY (contactId)"
+ " REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)";
private final File testDir = TestUtils.getTestDirectory();
private final File db = new File(testDir, "db");
private final String url = "jdbc:h2:" + db.getAbsolutePath();
private final GroupId groupId = new GroupId(getRandomId());
private final GroupId groupId1 = new GroupId(getRandomId());
private final ContactId contactId = new ContactId(123);
private final ContactId contactId1 = new ContactId(234);
private final Message message = getMessage(groupId);
private final Message message1 = getMessage(groupId1);
private final Message message2 = getMessage(groupId1);
private Connection connection = null;
@Before
public void setUp() throws Exception {
assertTrue(testDir.mkdirs());
Class.forName("org.h2.Driver");
connection = DriverManager.getConnection(url);
}
@After
public void tearDown() throws Exception {
if (connection != null) connection.close();
TestUtils.deleteTestDirectory(testDir);
}
@Test
public void testMigration() throws Exception {
try {
Statement s = connection.createStatement();
s.execute(CREATE_GROUPS_STUB);
s.execute(CREATE_CONTACTS_STUB);
s.execute(CREATE_GROUP_VISIBILITIES_STUB);
s.execute(CREATE_MESSAGES);
s.execute(CREATE_STATUSES_31);
s.close();
addGroup(groupId);
addMessage(message, DELIVERED, true, false);
addGroup(groupId1);
addMessage(message1, UNKNOWN, false, false);
addMessage(message2, DELIVERED, true, true);
addContact(contactId);
addGroupVisibility(contactId, groupId, true);
addStatus31(message.getId(), contactId);
addGroupVisibility(contactId, groupId1, false);
addStatus31(message1.getId(), contactId);
addStatus31(message2.getId(), contactId);
addContact(contactId1);
addGroupVisibility(contactId1, groupId1, true);
addStatus31(message1.getId(), contactId1);
addStatus31(message2.getId(), contactId1);
new Migration31_32().migrate(connection);
assertTrue(containsStatus(message.getId(), contactId));
Status32 status = getStatus32(message.getId(), contactId);
assertEquals(groupId, status.groupId);
assertEquals(message.getTimestamp(), status.timestamp);
assertEquals(message.getLength(), status.length);
assertEquals(DELIVERED, status.state);
assertTrue(status.groupShared);
assertTrue(status.messageShared);
assertFalse(status.deleted);
assertTrue(containsStatus(message1.getId(), contactId));
status = getStatus32(message1.getId(), contactId);
assertEquals(groupId1, status.groupId);
assertEquals(message1.getTimestamp(), status.timestamp);
assertEquals(message1.getLength(), status.length);
assertEquals(UNKNOWN, status.state);
assertFalse(status.groupShared);
assertFalse(status.messageShared);
assertFalse(status.deleted);
assertTrue(containsStatus(message2.getId(), contactId));
status = getStatus32(message2.getId(), contactId);
assertEquals(groupId1, status.groupId);
assertEquals(message2.getTimestamp(), status.timestamp);
assertEquals(message2.getLength(), status.length);
assertEquals(DELIVERED, status.state);
assertFalse(status.groupShared);
assertTrue(status.messageShared);
assertTrue(status.deleted);
assertFalse(containsStatus(message.getId(), contactId1));
assertTrue(containsStatus(message1.getId(), contactId1));
status = getStatus32(message1.getId(), contactId1);
assertEquals(groupId1, status.groupId);
assertEquals(message1.getTimestamp(), status.timestamp);
assertEquals(message1.getLength(), status.length);
assertEquals(UNKNOWN, status.state);
assertTrue(status.groupShared);
assertFalse(status.messageShared);
assertFalse(status.deleted);
assertTrue(containsStatus(message2.getId(), contactId1));
status = getStatus32(message2.getId(), contactId1);
assertEquals(groupId1, status.groupId);
assertEquals(message2.getTimestamp(), status.timestamp);
assertEquals(message2.getLength(), status.length);
assertEquals(DELIVERED, status.state);
assertTrue(status.groupShared);
assertTrue(status.messageShared);
assertTrue(status.deleted);
} catch (SQLException e) {
connection.close();
throw e;
}
}
private void addGroup(GroupId g) throws SQLException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO groups (groupId) VALUES (?)";
ps = connection.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
if (ps != null) ps.close();
throw e;
}
}
private void addContact(ContactId c) throws SQLException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO contacts (contactId) VALUES (?)";
ps = connection.prepareStatement(sql);
ps.setInt(1, c.getInt());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
if (ps != null) ps.close();
throw e;
}
}
private void addGroupVisibility(ContactId c, GroupId g, boolean shared)
throws SQLException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO groupVisibilities"
+ " (contactId, groupId, shared) VALUES (?, ?, ?)";
ps = connection.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setBytes(2, g.getBytes());
ps.setBoolean(3, shared);
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
if (ps != null) ps.close();
throw e;
}
}
private void addMessage(Message m, State state, boolean shared,
boolean deleted) throws SQLException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
+ " state, shared, length, raw)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
ps = connection.prepareStatement(sql);
ps.setBytes(1, m.getId().getBytes());
ps.setBytes(2, m.getGroupId().getBytes());
ps.setLong(3, m.getTimestamp());
ps.setInt(4, state.getValue());
ps.setBoolean(5, shared);
byte[] raw = m.getRaw();
ps.setInt(6, raw.length);
if (deleted) ps.setNull(7, BINARY);
else ps.setBytes(7, raw);
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
if (ps != null) ps.close();
throw e;
}
}
private void addStatus31(MessageId m, ContactId c) throws SQLException {
PreparedStatement ps = null;
try {
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
+ " seen, requested, expiry, txCount)"
+ " VALUES (?, ?, FALSE, FALSE, FALSE, 0, 0)";
ps = connection.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected != 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
if (ps != null) ps.close();
throw e;
}
}
private boolean containsStatus(MessageId m, ContactId c)
throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT COUNT (*) FROM statuses"
+ " WHERE messageId = ? AND contactId = ?";
ps = connection.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
int count = rs.getInt(1);
if (count < 0 || count > 1) throw new DbStateException();
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return count > 0;
} catch (SQLException e) {
if (rs != null) rs.close();
if (ps != null) ps.close();
throw e;
}
}
private Status32 getStatus32(MessageId m, ContactId c) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT groupId, timestamp, length, state,"
+ " groupShared, messageShared, deleted"
+ " FROM statuses"
+ " WHERE messageId = ? AND contactId = ?";
ps = connection.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setInt(2, c.getInt());
rs = ps.executeQuery();
if (!rs.next()) throw new DbStateException();
GroupId groupId = new GroupId(rs.getBytes(1));
long timestamp = rs.getLong(2);
int length = rs.getInt(3);
State state = State.fromValue(rs.getInt(4));
boolean groupShared = rs.getBoolean(5);
boolean messageShared = rs.getBoolean(6);
boolean deleted = rs.getBoolean(7);
if (rs.next()) throw new DbStateException();
rs.close();
ps.close();
return new Status32(groupId, timestamp, length, state,
groupShared, messageShared, deleted);
} catch (SQLException e) {
if (rs != null) rs.close();
if (ps != null) ps.close();
throw e;
}
}
private static class Status32 {
private final GroupId groupId;
private final long timestamp;
private final int length;
private final State state;
private final boolean groupShared, messageShared, deleted;
private Status32(GroupId groupId, long timestamp, int length,
State state, boolean groupShared, boolean messageShared,
boolean deleted) {
this.groupId = groupId;
this.timestamp = timestamp;
this.length = length;
this.state = state;
this.groupShared = groupShared;
this.messageShared = messageShared;
this.deleted = deleted;
}
}
}

View File

@@ -100,21 +100,21 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// validateOutstandingMessages() // validateOutstandingMessages()
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
// deliverOutstandingMessages() // deliverOutstandingMessages()
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getPendingMessages(txn1, clientId); oneOf(db).getPendingMessages(txn1);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn1); oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
// shareOutstandingMessages() // shareOutstandingMessages()
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(db).getMessagesToShare(txn2, clientId); oneOf(db).getMessagesToShare(txn2);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn2); oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
@@ -138,7 +138,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to validate // Get messages to validate
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Arrays.asList(messageId, messageId1))); will(returnValue(Arrays.asList(messageId, messageId1)));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
@@ -199,14 +199,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get pending messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn5)); will(returnValue(txn5));
oneOf(db).getPendingMessages(txn5, clientId); oneOf(db).getPendingMessages(txn5);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn5); oneOf(db).commitTransaction(txn5);
oneOf(db).endTransaction(txn5); oneOf(db).endTransaction(txn5);
// Get messages to share // Get messages to share
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn6)); will(returnValue(txn6));
oneOf(db).getMessagesToShare(txn6, clientId); oneOf(db).getMessagesToShare(txn6);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn6); oneOf(db).commitTransaction(txn6);
oneOf(db).endTransaction(txn6); oneOf(db).endTransaction(txn6);
@@ -227,14 +227,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to validate // Get messages to validate
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
// Get pending messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getPendingMessages(txn1, clientId); oneOf(db).getPendingMessages(txn1);
will(returnValue(Collections.singletonList(messageId))); will(returnValue(Collections.singletonList(messageId)));
oneOf(db).commitTransaction(txn1); oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
@@ -292,7 +292,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to share // Get messages to share
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).getMessagesToShare(txn4, clientId); oneOf(db).getMessagesToShare(txn4);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn4); oneOf(db).commitTransaction(txn4);
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
@@ -313,14 +313,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// No messages to validate // No messages to validate
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
// No pending messages to deliver // No pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn1)); will(returnValue(txn1));
oneOf(db).getPendingMessages(txn1, clientId); oneOf(db).getPendingMessages(txn1);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn1); oneOf(db).commitTransaction(txn1);
oneOf(db).endTransaction(txn1); oneOf(db).endTransaction(txn1);
@@ -328,7 +328,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to share // Get messages to share
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn2)); will(returnValue(txn2));
oneOf(db).getMessagesToShare(txn2, clientId); oneOf(db).getMessagesToShare(txn2);
will(returnValue(Collections.singletonList(messageId))); will(returnValue(Collections.singletonList(messageId)));
oneOf(db).commitTransaction(txn2); oneOf(db).commitTransaction(txn2);
oneOf(db).endTransaction(txn2); oneOf(db).endTransaction(txn2);
@@ -416,7 +416,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to validate // Get messages to validate
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Arrays.asList(messageId, messageId1))); will(returnValue(Arrays.asList(messageId, messageId1)));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
@@ -457,14 +457,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get pending messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).getPendingMessages(txn4, clientId); oneOf(db).getPendingMessages(txn4);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn4); oneOf(db).commitTransaction(txn4);
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
// Get messages to share // Get messages to share
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn5)); will(returnValue(txn5));
oneOf(db).getMessagesToShare(txn5, clientId); oneOf(db).getMessagesToShare(txn5);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn5); oneOf(db).commitTransaction(txn5);
oneOf(db).endTransaction(txn5); oneOf(db).endTransaction(txn5);
@@ -487,7 +487,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get messages to validate // Get messages to validate
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn)); will(returnValue(txn));
oneOf(db).getMessagesToValidate(txn, clientId); oneOf(db).getMessagesToValidate(txn);
will(returnValue(Arrays.asList(messageId, messageId1))); will(returnValue(Arrays.asList(messageId, messageId1)));
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
@@ -533,14 +533,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
// Get pending messages to deliver // Get pending messages to deliver
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn4)); will(returnValue(txn4));
oneOf(db).getPendingMessages(txn4, clientId); oneOf(db).getPendingMessages(txn4);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn4); oneOf(db).commitTransaction(txn4);
oneOf(db).endTransaction(txn4); oneOf(db).endTransaction(txn4);
// Get messages to share // Get messages to share
oneOf(db).startTransaction(true); oneOf(db).startTransaction(true);
will(returnValue(txn5)); will(returnValue(txn5));
oneOf(db).getMessagesToShare(txn5, clientId); oneOf(db).getMessagesToShare(txn5);
will(returnValue(Collections.emptyList())); will(returnValue(Collections.emptyList()));
oneOf(db).commitTransaction(txn5); oneOf(db).commitTransaction(txn5);
oneOf(db).endTransaction(txn5); oneOf(db).endTransaction(txn5);

View File

@@ -189,8 +189,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 1619 versionCode 1620
versionName "0.16.19" versionName "0.16.20"
applicationId "org.briarproject.briar.beta" applicationId "org.briarproject.briar.beta"
resValue "string", "app_package", "org.briarproject.briar.beta" resValue "string", "app_package", "org.briarproject.briar.beta"
resValue "string", "app_name", "Briar Beta" resValue "string", "app_name", "Briar Beta"

View File

@@ -374,7 +374,12 @@
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.panic.ExitActivity" android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay">
</activity>
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.NoDisplay">
</activity> </activity>

View File

@@ -62,6 +62,7 @@ import javax.inject.Inject;
import static android.app.Notification.DEFAULT_LIGHTS; import static android.app.Notification.DEFAULT_LIGHTS;
import static android.app.Notification.DEFAULT_SOUND; import static android.app.Notification.DEFAULT_SOUND;
import static android.app.Notification.DEFAULT_VIBRATE; import static android.app.Notification.DEFAULT_VIBRATE;
import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
@@ -90,12 +91,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private static final int BLOG_POST_NOTIFICATION_ID = 6; private static final int BLOG_POST_NOTIFICATION_ID = 6;
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7; private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7;
// Channel IDs
private static final String CONTACT_CHANNEL_ID = "contacts";
private static final String GROUP_CHANNEL_ID = "groups";
private static final String FORUM_CHANNEL_ID = "forums";
private static final String BLOG_CHANNEL_ID = "blogs";
private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2); private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
private static final Logger LOG = private static final Logger LOG =
@@ -175,6 +170,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationChannel nc = NotificationChannel nc =
new NotificationChannel(channelId, appContext.getString(name), new NotificationChannel(channelId, appContext.getString(name),
IMPORTANCE_DEFAULT); IMPORTANCE_DEFAULT);
nc.setLockscreenVisibility(VISIBILITY_SECRET);
nc.enableVibration(true);
nc.enableLights(true); nc.enableLights(true);
nc.setLightColor( nc.setLightColor(
ContextCompat.getColor(appContext, R.color.briar_green_light)); ContextCompat.getColor(appContext, R.color.briar_green_light));

View File

@@ -4,8 +4,11 @@ import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
@@ -17,19 +20,26 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logout.HideUiActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
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.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE; import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
import static android.support.v4.app.NotificationCompat.PRIORITY_MIN; import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
@@ -61,6 +71,9 @@ public class BriarService extends Service {
private final AtomicBoolean created = new AtomicBoolean(false); private final AtomicBoolean created = new AtomicBoolean(false);
private final Binder binder = new BriarBinder(); private final Binder binder = new BriarBinder();
@Nullable
private BroadcastReceiver receiver = null;
@Inject @Inject
protected DatabaseConfig databaseConfig; protected DatabaseConfig databaseConfig;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@@ -143,6 +156,19 @@ public class BriarService extends Service {
} }
} }
}.start(); }.start();
// Register for device shutdown broadcasts
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
LOG.info("Device is shutting down");
shutdownFromBackground();
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SHUTDOWN);
filter.addAction("android.intent.action.QUICKBOOT_POWEROFF");
filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF");
registerReceiver(receiver, filter);
} }
private void showStartupFailureNotification(StartResult result) { private void showStartupFailureNotification(StartResult result) {
@@ -187,6 +213,7 @@ public class BriarService extends Service {
super.onDestroy(); super.onDestroy();
LOG.info("Destroyed"); LOG.info("Destroyed");
stopForeground(true); stopForeground(true);
if (receiver != null) unregisterReceiver(receiver);
// Stop the services in a background thread // Stop the services in a background thread
new Thread() { new Thread() {
@Override @Override
@@ -200,7 +227,48 @@ public class BriarService extends Service {
public void onLowMemory() { public void onLowMemory() {
super.onLowMemory(); super.onLowMemory();
LOG.warning("Memory is low"); LOG.warning("Memory is low");
// FIXME: Work out what to do about it shutdownFromBackground();
showLowMemoryShutdownNotification();
}
private void shutdownFromBackground() {
// Stop the service
stopSelf();
// Hide the UI
Intent i = new Intent(this, HideUiActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK
| FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
// Wait for shutdown to complete, then exit
new Thread(() -> {
try {
if (started) lifecycleManager.waitForShutdown();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for shutdown");
}
LOG.info("Exiting");
System.exit(0);
}).start();
}
private void showLowMemoryShutdownNotification() {
androidExecutor.runOnUiThread(() -> {
NotificationCompat.Builder b = new NotificationCompat.Builder(
BriarService.this, FAILURE_CHANNEL_ID);
b.setSmallIcon(android.R.drawable.stat_notify_error);
b.setContentTitle(getText(
R.string.low_memory_shutdown_notification_title));
b.setContentText(getText(
R.string.low_memory_shutdown_notification_text));
Intent i = new Intent(this, SplashScreenActivity.class);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
b.setAutoCancel(true);
Object o = getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
});
} }
/** /**

View File

@@ -16,7 +16,7 @@ import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.handler.UiResultHandler; import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.login.PasswordActivity; import org.briarproject.briar.android.login.PasswordActivity;
import org.briarproject.briar.android.panic.ExitActivity; import org.briarproject.briar.android.logout.ExitActivity;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@@ -52,19 +52,15 @@ class ForumListAdapter
// Post Count // Post Count
int postCount = item.getPostCount(); int postCount = item.getPostCount();
if (postCount > 0) { if (postCount > 0) {
ui.avatar.setProblem(false);
ui.postCount.setText(ctx.getResources() ui.postCount.setText(ctx.getResources()
.getQuantityString(R.plurals.posts, postCount, .getQuantityString(R.plurals.posts, postCount,
postCount)); postCount));
ui.postCount.setTextColor( ui.postCount.setTextColor(
ContextCompat ContextCompat.getColor(ctx, R.color.briar_text_secondary));
.getColor(ctx, R.color.briar_text_secondary));
} else { } else {
ui.avatar.setProblem(true);
ui.postCount.setText(ctx.getString(R.string.no_posts)); ui.postCount.setText(ctx.getString(R.string.no_posts));
ui.postCount.setTextColor( ui.postCount.setTextColor(
ContextCompat ContextCompat.getColor(ctx, R.color.briar_text_tertiary));
.getColor(ctx, R.color.briar_text_tertiary));
} }
// Date // Date

View File

@@ -9,6 +9,7 @@ import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog.Builder; import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
@@ -206,11 +207,14 @@ public class KeyAgreementActivity extends BriarActivity implements
private void showQrCodeFragment() { private void showQrCodeFragment() {
// FIXME #824 // FIXME #824
BaseFragment f = ShowQrCodeFragment.newInstance(); FragmentManager fm = getSupportFragmentManager();
getSupportFragmentManager().beginTransaction() if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) {
.replace(R.id.fragmentContainer, f, f.getUniqueTag()) BaseFragment f = ShowQrCodeFragment.newInstance();
.addToBackStack(f.getUniqueTag()) fm.beginTransaction()
.commit(); .replace(R.id.fragmentContainer, f, f.getUniqueTag())
.addToBackStack(f.getUniqueTag())
.commit();
}
} }
private boolean checkPermissions() { private boolean checkPermissions() {

View File

@@ -65,7 +65,6 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) { public void onPreviewFrame(byte[] data, Camera camera) {
if (camera == this.camera) { if (camera == this.camera) {
LOG.info("Got preview frame");
try { try {
Size size = camera.getParameters().getPreviewSize(); Size size = camera.getParameters().getPreviewSize();
// The preview should be in NV21 format: width * height bytes of // The preview should be in NV21 format: width * height bytes of
@@ -103,19 +102,12 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
long now = System.currentTimeMillis();
BinaryBitmap bitmap = binarize(data, width, height, orientation); BinaryBitmap bitmap = binarize(data, width, height, orientation);
Result result = null; Result result;
try { try {
result = reader.decode(bitmap); result = reader.decode(bitmap);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Decoding barcode took " + duration + " ms");
} catch (ReaderException e) { } catch (ReaderException e) {
// No barcode found // No barcode found
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("No barcode found after " + duration + " ms");
return null; return null;
} catch (RuntimeException e) { } catch (RuntimeException e) {
LOG.warning("Invalid preview frame"); LOG.warning("Invalid preview frame");

View File

@@ -15,6 +15,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AlphaAnimation; import android.view.animation.AlphaAnimation;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -50,6 +52,8 @@ import javax.inject.Inject;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -59,7 +63,8 @@ import static java.util.logging.Level.WARNING;
public class ShowQrCodeFragment extends BaseEventFragment public class ShowQrCodeFragment extends BaseEventFragment
implements QrCodeDecoder.ResultCallback { implements QrCodeDecoder.ResultCallback {
private static final String TAG = ShowQrCodeFragment.class.getName(); static final String TAG = ShowQrCodeFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG); private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
@@ -80,6 +85,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
private ImageView qrCode; private ImageView qrCode;
private TextView mainProgressTitle; private TextView mainProgressTitle;
private ViewGroup mainProgressContainer; private ViewGroup mainProgressContainer;
private boolean fullscreen = false;
private boolean gotRemotePayload; private boolean gotRemotePayload;
private volatile boolean gotLocalPayload; private volatile boolean gotLocalPayload;
@@ -124,6 +130,34 @@ public class ShowQrCodeFragment extends BaseEventFragment
qrCode = view.findViewById(R.id.qr_code); qrCode = view.findViewById(R.id.qr_code);
mainProgressTitle = view.findViewById(R.id.title_progress_bar); mainProgressTitle = view.findViewById(R.id.title_progress_bar);
mainProgressContainer = view.findViewById(R.id.container_progress); mainProgressContainer = view.findViewById(R.id.container_progress);
ImageView fullscreenButton = view.findViewById(R.id.fullscreen_button);
fullscreenButton.setOnClickListener(v -> {
View qrCodeContainer = view.findViewById(R.id.qr_code_container);
LinearLayout cameraOverlay = view.findViewById(R.id.camera_overlay);
LayoutParams statusParams, qrCodeParams;
if (fullscreen) {
// Shrink the QR code container to fill half its parent
if (cameraOverlay.getOrientation() == HORIZONTAL) {
statusParams = new LayoutParams(0, MATCH_PARENT, 1f);
qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f);
} else {
statusParams = new LayoutParams(MATCH_PARENT, 0, 1f);
qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f);
}
fullscreenButton.setImageResource(
R.drawable.ic_fullscreen_black_48dp);
} else {
// Grow the QR code container to fill its parent
statusParams = new LayoutParams(0, 0, 0f);
qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f);
fullscreenButton.setImageResource(
R.drawable.ic_fullscreen_exit_black_48dp);
}
statusView.setLayoutParams(statusParams);
qrCodeContainer.setLayoutParams(qrCodeParams);
cameraOverlay.invalidate();
fullscreen = !fullscreen;
});
} }
@Override @Override
@@ -204,6 +238,15 @@ public class ShowQrCodeFragment extends BaseEventFragment
@UiThread @UiThread
private void reset() { private void reset() {
// If we've stopped the camera view, restart it
if (gotRemotePayload) {
try {
cameraView.start(getScreenRotationDegrees());
} catch (CameraException e) {
logCameraExceptionAndFinish(e);
return;
}
}
statusView.setVisibility(INVISIBLE); statusView.setVisibility(INVISIBLE);
cameraView.setVisibility(VISIBLE); cameraView.setVisibility(VISIBLE);
gotRemotePayload = false; gotRemotePayload = false;
@@ -218,12 +261,17 @@ public class ShowQrCodeFragment extends BaseEventFragment
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + encoded.length + " bytes"); LOG.info("Remote payload is " + encoded.length + " bytes");
Payload remotePayload = payloadParser.parse(encoded); Payload remotePayload = payloadParser.parse(encoded);
gotRemotePayload = true;
cameraView.stop();
cameraView.setVisibility(INVISIBLE); cameraView.setVisibility(INVISIBLE);
statusView.setVisibility(VISIBLE); statusView.setVisibility(VISIBLE);
status.setText(R.string.connecting_to_device); status.setText(R.string.connecting_to_device);
task.connectAndRunProtocol(remotePayload); task.connectAndRunProtocol(remotePayload);
} catch (CameraException e) {
logCameraExceptionAndFinish(e);
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
// TODO show failure if (LOG.isLoggable(WARNING)) LOG.log(WARNING, "QR Code Invalid", e);
reset();
Toast.makeText(getActivity(), R.string.qr_code_invalid, Toast.makeText(getActivity(), R.string.qr_code_invalid,
LENGTH_LONG).show(); LENGTH_LONG).show();
} }
@@ -261,6 +309,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
new AsyncTask<Void, Void, Bitmap>() { new AsyncTask<Void, Void, Bitmap>() {
@Override @Override
@Nullable
protected Bitmap doInBackground(Void... params) { protected Bitmap doInBackground(Void... params) {
byte[] encoded = payloadEncoder.encode(payload); byte[] encoded = payloadEncoder.encode(payload);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
@@ -325,13 +374,8 @@ public class ShowQrCodeFragment extends BaseEventFragment
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
LOG.info("Got result from decoder"); LOG.info("Got result from decoder");
// Ignore results until the KeyAgreementTask is ready // Ignore results until the KeyAgreementTask is ready
if (!gotLocalPayload) { if (!gotLocalPayload) return;
return; if (!gotRemotePayload) qrCodeScanned(result.getText());
}
if (!gotRemotePayload) {
gotRemotePayload = true;
qrCodeScanned(result.getText());
}
}); });
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.panic; package org.briarproject.briar.android.logout;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;

View File

@@ -0,0 +1,20 @@
package org.briarproject.briar.android.logout;
import android.os.Bundle;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
public class HideUiActivity extends BaseActivity {
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
finish();
}
@Override
public void injectActivity(ActivityComponent component) {
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.fragment; package org.briarproject.briar.android.logout;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -7,15 +7,17 @@ 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 org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public class SignOutFragment extends BaseFragment { public class SignOutFragment extends BaseFragment {
private static final String TAG = SignOutFragment.class.getName(); public static final String TAG = SignOutFragment.class.getName();
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(@Nonnull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_sign_out, container, false); return inflater.inflate(R.layout.fragment_sign_out, container, false);
@@ -30,5 +32,4 @@ public class SignOutFragment extends BaseFragment {
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
// no need to inject // no need to inject
} }
} }

View File

@@ -7,6 +7,7 @@ import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener; import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
@@ -22,6 +23,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
@@ -35,7 +37,7 @@ import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.forum.ForumListFragment; import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.fragment.SignOutFragment; import org.briarproject.briar.android.logout.SignOutFragment;
import org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning; import org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning;
import org.briarproject.briar.android.privategroup.list.GroupListFragment; import org.briarproject.briar.android.privategroup.list.GroupListFragment;
import org.briarproject.briar.android.settings.SettingsActivity; import org.briarproject.briar.android.settings.SettingsActivity;
@@ -51,6 +53,7 @@ import static android.support.v4.view.GravityCompat.START;
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
@@ -73,6 +76,8 @@ public class NavDrawerActivity extends BriarActivity implements
@Inject @Inject
NavDrawerController controller; NavDrawerController controller;
@Inject
LifecycleManager lifecycleManager;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private NavigationView navigation; private NavigationView navigation;
@@ -128,7 +133,9 @@ public class NavDrawerActivity extends BriarActivity implements
initializeTransports(getLayoutInflater()); initializeTransports(getLayoutInflater());
transportsView.setAdapter(transportsAdapter); transportsView.setAdapter(transportsAdapter);
if (state == null) { if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
showSignOutFragment();
} else if (state == null) {
startFragment(ContactListFragment.newInstance(), startFragment(ContactListFragment.newInstance(),
R.id.nav_btn_contacts); R.id.nav_btn_contacts);
} }
@@ -212,19 +219,23 @@ public class NavDrawerActivity extends BriarActivity implements
public void onBackPressed() { public void onBackPressed() {
if (drawerLayout.isDrawerOpen(START)) { if (drawerLayout.isDrawerOpen(START)) {
drawerLayout.closeDrawer(START); drawerLayout.closeDrawer(START);
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 && } else {
getSupportFragmentManager() FragmentManager fm = getSupportFragmentManager();
.findFragmentByTag(ContactListFragment.TAG) == null) { if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
finish();
} else if (fm.getBackStackEntryCount() == 0
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
/* /*
* This makes sure that the first fragment (ContactListFragment) the * This makes sure that the first fragment (ContactListFragment) the
* user sees is the same as the last fragment the user sees before * user sees is the same as the last fragment the user sees before
* exiting. This models the typical Google navigation behaviour such * exiting. This models the typical Google navigation behaviour such
* as in Gmail/Inbox. * as in Gmail/Inbox.
*/ */
startFragment(ContactListFragment.newInstance(), startFragment(ContactListFragment.newInstance(),
R.id.nav_btn_contacts); R.id.nav_btn_contacts);
} else { } else {
super.onBackPressed(); super.onBackPressed();
}
} }
} }
@@ -240,10 +251,15 @@ public class NavDrawerActivity extends BriarActivity implements
drawerToggle.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig);
} }
private void signOut() { private void showSignOutFragment() {
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED); drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
startFragment(new SignOutFragment()); startFragment(new SignOutFragment());
}
private void signOut() {
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
signOut(false); signOut(false);
finish();
} }
private void startFragment(BaseFragment fragment, int itemId) { private void startFragment(BaseFragment fragment, int itemId) {

View File

@@ -74,9 +74,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
if (group.isEmpty()) { if (group.isEmpty()) {
postCount.setVisibility(GONE); postCount.setVisibility(GONE);
date.setVisibility(GONE); date.setVisibility(GONE);
avatar.setProblem(true); status.setText(ctx.getString(R.string.groups_group_is_empty));
status
.setText(ctx.getString(R.string.groups_group_is_empty));
status.setVisibility(VISIBLE); status.setVisibility(VISIBLE);
} else { } else {
// Message Count // Message Count
@@ -91,7 +89,6 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
long lastUpdate = group.getTimestamp(); long lastUpdate = group.getTimestamp();
date.setText(UiUtils.formatDate(ctx, lastUpdate)); date.setText(UiUtils.formatDate(ctx, lastUpdate));
date.setVisibility(VISIBLE); date.setVisibility(VISIBLE);
avatar.setProblem(false);
status.setVisibility(GONE); status.setVisibility(GONE);
} }
remove.setVisibility(GONE); remove.setVisibility(GONE);

View File

@@ -1,12 +1,13 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.Ringtone; import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.StringRes;
import android.support.v7.preference.CheckBoxPreference; import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.ListPreference; import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference; import android.support.v7.preference.Preference;
@@ -44,6 +45,10 @@ import static android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT;
import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE; import static android.media.RingtoneManager.EXTRA_RINGTONE_TITLE;
import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE; import static android.media.RingtoneManager.EXTRA_RINGTONE_TYPE;
import static android.media.RingtoneManager.TYPE_NOTIFICATION; import static android.media.RingtoneManager.TYPE_NOTIFICATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS;
import static android.provider.Settings.EXTRA_APP_PACKAGE;
import static android.provider.Settings.EXTRA_CHANNEL_ID;
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -53,6 +58,10 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.GROUP_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG; import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_BLOG;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM; import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_FORUM;
import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP; import static org.briarproject.briar.api.android.AndroidNotificationManager.PREF_NOTIFY_GROUP;
@@ -128,35 +137,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
"pref_key_notify_lock_screen"); "pref_key_notify_lock_screen");
notifySound = findPreference("pref_key_notify_sound"); notifySound = findPreference("pref_key_notify_sound");
setSettingsEnabled(false);
enableBluetooth.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this);
notifyPrivateMessages.setOnPreferenceChangeListener(this); if (SDK_INT >= 21) {
notifyGroupMessages.setOnPreferenceChangeListener(this);
notifyForumPosts.setOnPreferenceChangeListener(this);
notifyBlogPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this);
if (Build.VERSION.SDK_INT >= 21) {
notifyLockscreen.setVisible(true); notifyLockscreen.setVisible(true);
notifyLockscreen.setOnPreferenceChangeListener(this); notifyLockscreen.setOnPreferenceChangeListener(this);
} }
notifySound.setOnPreferenceClickListener(preference -> {
String title = getString(R.string.choose_ringtone_title);
Intent i = new Intent(ACTION_RINGTONE_PICKER);
i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
i.putExtra(EXTRA_RINGTONE_TITLE, title);
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI, DEFAULT_NOTIFICATION_URI);
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
Uri uri;
String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
if (StringUtils.isNullOrEmpty(ringtoneUri))
uri = DEFAULT_NOTIFICATION_URI;
else uri = Uri.parse(ringtoneUri);
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
}
startActivityForResult(i, REQUEST_RINGTONE);
return true;
});
findPreference("pref_key_send_feedback").setOnPreferenceClickListener( findPreference("pref_key_send_feedback").setOnPreferenceClickListener(
preference -> { preference -> {
@@ -218,39 +206,105 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setValue(Boolean.toString(btSetting)); enableBluetooth.setValue(Boolean.toString(btSetting));
torNetwork.setValue(Integer.toString(torSetting)); torNetwork.setValue(Integer.toString(torSetting));
notifyPrivateMessages.setChecked(settings.getBoolean( if (SDK_INT < 26) {
PREF_NOTIFY_PRIVATE, true)); notifyPrivateMessages.setChecked(settings.getBoolean(
PREF_NOTIFY_PRIVATE, true));
notifyGroupMessages.setChecked(settings.getBoolean( notifyGroupMessages.setChecked(settings.getBoolean(
PREF_NOTIFY_GROUP, true)); PREF_NOTIFY_GROUP, true));
notifyForumPosts.setChecked(settings.getBoolean(
notifyForumPosts.setChecked(settings.getBoolean( PREF_NOTIFY_FORUM, true));
PREF_NOTIFY_FORUM, true)); notifyBlogPosts.setChecked(settings.getBoolean(
PREF_NOTIFY_BLOG, true));
notifyBlogPosts.setChecked(settings.getBoolean( notifyVibration.setChecked(settings.getBoolean(
PREF_NOTIFY_BLOG, true)); PREF_NOTIFY_VIBRATION, true));
notifyPrivateMessages.setOnPreferenceChangeListener(this);
notifyVibration.setChecked(settings.getBoolean( notifyGroupMessages.setOnPreferenceChangeListener(this);
PREF_NOTIFY_VIBRATION, true)); notifyForumPosts.setOnPreferenceChangeListener(this);
notifyBlogPosts.setOnPreferenceChangeListener(this);
notifyLockscreen.setChecked(settings.getBoolean( notifyVibration.setOnPreferenceChangeListener(this);
PREF_NOTIFY_LOCK_SCREEN, false)); notifyLockscreen.setChecked(settings.getBoolean(
PREF_NOTIFY_LOCK_SCREEN, false));
String text; notifySound.setOnPreferenceClickListener(
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) { pref -> onNotificationSoundClicked());
String ringtoneName = settings.get(PREF_NOTIFY_RINGTONE_NAME); String text;
if (StringUtils.isNullOrEmpty(ringtoneName)) { if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
text = getString(R.string.notify_sound_setting_default); String ringtoneName =
settings.get(PREF_NOTIFY_RINGTONE_NAME);
if (StringUtils.isNullOrEmpty(ringtoneName)) {
text = getString(R.string.notify_sound_setting_default);
} else {
text = ringtoneName;
}
} else { } else {
text = ringtoneName; text = getString(R.string.notify_sound_setting_disabled);
} }
notifySound.setSummary(text);
} else { } else {
text = getString(R.string.notify_sound_setting_disabled); setupNotificationPreference(notifyPrivateMessages,
CONTACT_CHANNEL_ID,
R.string.notify_private_messages_setting_summary_26);
setupNotificationPreference(notifyGroupMessages,
GROUP_CHANNEL_ID,
R.string.notify_group_messages_setting_summary_26);
setupNotificationPreference(notifyForumPosts, FORUM_CHANNEL_ID,
R.string.notify_forum_posts_setting_summary_26);
setupNotificationPreference(notifyBlogPosts, BLOG_CHANNEL_ID,
R.string.notify_blog_posts_setting_summary_26);
notifyVibration.setVisible(false);
notifyLockscreen.setVisible(false);
notifySound.setVisible(false);
} }
notifySound.setSummary(text); setSettingsEnabled(true);
}); });
} }
private void setSettingsEnabled(boolean enabled) {
enableBluetooth.setEnabled(enabled);
torNetwork.setEnabled(enabled);
notifyPrivateMessages.setEnabled(enabled);
notifyGroupMessages.setEnabled(enabled);
notifyForumPosts.setEnabled(enabled);
notifyBlogPosts.setEnabled(enabled);
notifyVibration.setEnabled(enabled);
notifyLockscreen.setEnabled(enabled);
notifySound.setEnabled(enabled);
}
@TargetApi(26)
private void setupNotificationPreference(CheckBoxPreference pref,
String channelId, @StringRes int summary) {
pref.setWidgetLayoutResource(0);
pref.setSummary(summary);
pref.setOnPreferenceClickListener(clickedPref -> {
Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS)
.putExtra(EXTRA_APP_PACKAGE, getContext().getPackageName())
.putExtra(EXTRA_CHANNEL_ID, channelId);
startActivity(intent);
return true;
});
}
private boolean onNotificationSoundClicked() {
String title = getString(R.string.choose_ringtone_title);
Intent i = new Intent(ACTION_RINGTONE_PICKER);
i.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION);
i.putExtra(EXTRA_RINGTONE_TITLE, title);
i.putExtra(EXTRA_RINGTONE_DEFAULT_URI,
DEFAULT_NOTIFICATION_URI);
i.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true);
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
Uri uri;
String ringtoneUri =
settings.get(PREF_NOTIFY_RINGTONE_URI);
if (StringUtils.isNullOrEmpty(ringtoneUri))
uri = DEFAULT_NOTIFICATION_URI;
else uri = Uri.parse(ringtoneUri);
i.putExtra(EXTRA_RINGTONE_EXISTING_URI, uri);
}
startActivityForResult(i, REQUEST_RINGTONE);
return true;
}
private void triggerFeedback() { private void triggerFeedback() {
androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter() androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter()
.handleException(new UserFeedback(), false)); .handleException(new UserFeedback(), false));

View File

@@ -5,7 +5,6 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -26,7 +25,6 @@ public class TextAvatarView extends FrameLayout {
private final AppCompatTextView character; private final AppCompatTextView character;
private final CircleImageView background; private final CircleImageView background;
private final TextView badge; private final TextView badge;
private int unreadCount;
public TextAvatarView(Context context, @Nullable AttributeSet attrs) { public TextAvatarView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs); super(context, attrs);
@@ -49,30 +47,14 @@ public class TextAvatarView extends FrameLayout {
} }
public void setUnreadCount(int count) { public void setUnreadCount(int count) {
unreadCount = count;
if (count > 0) { if (count > 0) {
badge.setBackgroundResource(R.drawable.bubble);
badge.setText(String.valueOf(count)); badge.setText(String.valueOf(count));
badge.setTextColor(ContextCompat.getColor(getContext(),
R.color.briar_text_primary_inverse));
badge.setVisibility(VISIBLE); badge.setVisibility(VISIBLE);
} else { } else {
badge.setVisibility(INVISIBLE); badge.setVisibility(INVISIBLE);
} }
} }
public void setProblem(boolean problem) {
if (problem) {
badge.setBackgroundResource(R.drawable.bubble_problem);
badge.setText("!");
badge.setTextColor(ContextCompat
.getColor(getContext(), R.color.briar_primary));
badge.setVisibility(VISIBLE);
} else {
setUnreadCount(unreadCount);
}
}
public void setBackgroundBytes(byte[] bytes) { public void setBackgroundBytes(byte[] bytes) {
int r = getByte(bytes, 0) * 3 / 4 + 96; int r = getByte(bytes, 0) * 3 / 4 + 96;
int g = getByte(bytes, 1) * 3 / 4 + 96; int g = getByte(bytes, 1) * 3 / 4 + 96;

View File

@@ -21,6 +21,12 @@ public interface AndroidNotificationManager {
String PREF_NOTIFY_VIBRATION = "notifyVibration"; String PREF_NOTIFY_VIBRATION = "notifyVibration";
String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen"; String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen";
// Channel IDs
String CONTACT_CHANNEL_ID = "contacts";
String GROUP_CHANNEL_ID = "groups";
String FORUM_CHANNEL_ID = "forums";
String BLOG_CHANNEL_ID = "blogs";
// Content URIs for pending intents // Content URIs for pending intents
String CONTACT_URI = "content://org.briarproject.briar/contact"; String CONTACT_URI = "content://org.briarproject.briar/contact";
String GROUP_URI = "content://org.briarproject.briar/group"; String GROUP_URI = "content://org.briarproject.briar/group";

View File

@@ -1,5 +1,9 @@
<vector android:alpha="0.54" android:height="24dp" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="128dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="128dp"
<path android:fillColor="#FF000000" android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/> android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/>
</vector> </vector>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="@dimen/unread_bubble_size"/>
<padding
android:left="@dimen/unread_bubble_padding_horizontal"
android:right="@dimen/unread_bubble_padding_horizontal"/>
<solid
android:color="@color/briar_gold"/>
<stroke
android:color="@color/briar_primary"
android:width="@dimen/avatar_border_width"/>
</shape>

View File

@@ -0,0 +1,4 @@
<vector android:height="48dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
</vector>

View File

@@ -0,0 +1,4 @@
<vector android:height="48dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"/>
</vector>

View File

@@ -15,39 +15,35 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="horizontal"
android:weightSum="2"> android:baselineAligned="false">
<FrameLayout <LinearLayout
android:id="@+id/status_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"> android:layout_weight="1"
android:background="@android:color/background_light"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/margin_medium"
android:visibility="invisible">
<LinearLayout <ProgressBar
android:id="@+id/status_container" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:background="@android:color/background_light"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:paddingTop="@dimen/margin_large"
android:padding="@dimen/margin_medium" tools:text="Connection failed"/>
android:visibility="invisible"> </LinearLayout>
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="@dimen/margin_large"
tools:text="Connection failed"/>
</LinearLayout>
</FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/qr_code_container"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
@@ -59,12 +55,31 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"/> android:layout_gravity="center"/>
<ImageView <RelativeLayout
android:id="@+id/qr_code"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:scaleType="fitCenter"
android:layout_gravity="center"/> <ImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:contentDescription="@string/qr_code"
android:scaleType="fitCenter"/>
<ImageView
android:id="@+id/fullscreen_button"
android:background="?selectableItemBackground"
android:src="@drawable/ic_fullscreen_black_48dp"
android:alpha="0.54"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:contentDescription="@string/show_qr_code_fullscreen"/>
</RelativeLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
@@ -92,5 +107,4 @@
android:paddingTop="@dimen/margin_large" android:paddingTop="@dimen/margin_large"
tools:text="@string/waiting_for_contact_to_scan"/> tools:text="@string/waiting_for_contact_to_scan"/>
</RelativeLayout> </RelativeLayout>
</FrameLayout> </FrameLayout>

View File

@@ -15,39 +15,35 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:weightSum="2"> android:baselineAligned="false">
<FrameLayout <LinearLayout
android:id="@+id/status_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"> android:layout_weight="1"
android:background="@android:color/background_light"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/margin_medium"
android:visibility="invisible">
<LinearLayout <ProgressBar
android:id="@+id/status_container" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:background="@android:color/background_light"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:paddingTop="@dimen/margin_large"
android:padding="@dimen/margin_medium" tools:text="Connection failed"/>
android:visibility="invisible"> </LinearLayout>
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/connect_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="@dimen/margin_large"
tools:text="Connection failed"/>
</LinearLayout>
</FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/qr_code_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
@@ -59,12 +55,31 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"/> android:layout_gravity="center"/>
<ImageView <RelativeLayout
android:id="@+id/qr_code"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:scaleType="fitCenter"
android:layout_gravity="center"/> <ImageView
android:id="@+id/qr_code"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:contentDescription="@string/qr_code"
android:scaleType="fitCenter"/>
<ImageView
android:id="@+id/fullscreen_button"
android:background="?selectableItemBackground"
android:src="@drawable/ic_fullscreen_black_48dp"
android:alpha="0.54"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:contentDescription="@string/show_qr_code_fullscreen"/>
</RelativeLayout>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
@@ -92,5 +107,4 @@
android:paddingTop="@dimen/margin_large" android:paddingTop="@dimen/margin_large"
tools:text="@string/waiting_for_contact_to_scan"/> tools:text="@string/waiting_for_contact_to_scan"/>
</RelativeLayout> </RelativeLayout>
</FrameLayout> </FrameLayout>

View File

@@ -1,23 +1,46 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="128dp"
android:layout_height="128dp"
android:scaleType="center"
android:src="@drawable/startup_lock"
android:tint="@color/briar_primary"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintVertical_chainStyle="packed"
tools:ignore="ContentDescription"/>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_centerInParent="true"/> app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="@+id/imageView"
app:layout_constraintStart_toStartOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView"/>
<TextView <TextView
android:id="@+id/title_progress_bar" android:id="@+id/textView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/progressBar" android:layout_margin="8dp"
android:layout_centerHorizontal="true" android:text="@string/progress_title_logout"
android:paddingTop="@dimen/margin_large" android:textSize="@dimen/text_size_large"
android:text="@string/progress_title_logout"/> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView"/>
</RelativeLayout> </android.support.constraint.ConstraintLayout>

View File

@@ -234,8 +234,8 @@
<string name="blogs_blog_post_scroll_to">Отвори</string> <string name="blogs_blog_post_scroll_to">Отвори</string>
<string name="blogs_feed_empty_state">Това е глобалната блог емисия.\n\nНикой още не е публикувал нищо.\n\nБъдете първия и натиснете писалката, за да напишете първата блог публикация.</string> <string name="blogs_feed_empty_state">Това е глобалната блог емисия.\n\nНикой още не е публикувал нищо.\n\nБъдете първия и натиснете писалката, за да напишете първата блог публикация.</string>
<string name="blogs_remove_blog">Премахване на блог</string> <string name="blogs_remove_blog">Премахване на блог</string>
<string name="blogs_remove_blog_ok">Премахване</string>
<string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че искате да премахнете този блог и всички публикации?\nБлогът няма да бъдат премахнат от устройствата на други хора.</string> <string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че искате да премахнете този блог и всички публикации?\nБлогът няма да бъдат премахнат от устройствата на други хора.</string>
<string name="blogs_remove_blog_ok">Премахни блог</string>
<string name="blogs_blog_removed">Блогът е премахнат</string> <string name="blogs_blog_removed">Блогът е премахнат</string>
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string> <string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
<string name="blogs_reblog_button">Реблог</string> <string name="blogs_reblog_button">Реблог</string>
@@ -264,8 +264,8 @@
<string name="blogs_rss_feeds_manage_author">Автор:</string> <string name="blogs_rss_feeds_manage_author">Автор:</string>
<string name="blogs_rss_feeds_manage_updated">Последно актуализиране:</string> <string name="blogs_rss_feeds_manage_updated">Последно актуализиране:</string>
<string name="blogs_rss_remove_feed">Премахване на емисия</string> <string name="blogs_rss_remove_feed">Премахване на емисия</string>
<string name="blogs_rss_remove_feed_ok">Премахване</string>
<string name="blogs_rss_remove_feed_dialog_message">Сигурни ли сте, че искате да премахнете тази емисия и всички нейни публикации?\nВсички споделени от вас публикации няма да бъдат премахнати от устройствата на други хора.</string> <string name="blogs_rss_remove_feed_dialog_message">Сигурни ли сте, че искате да премахнете тази емисия и всички нейни публикации?\nВсички споделени от вас публикации няма да бъдат премахнати от устройствата на други хора.</string>
<string name="blogs_rss_remove_feed_ok">Емисията е премахната</string>
<string name="blogs_rss_feeds_manage_delete_error">Емисията не можа да бъде изтрита!</string> <string name="blogs_rss_feeds_manage_delete_error">Емисията не можа да бъде изтрита!</string>
<string name="blogs_rss_feeds_manage_empty_state">Нямате добавени RSS емисии.\n\nНатиснете плюса в горния десен ъгъл на екрана, за да добавите нова емисия.</string> <string name="blogs_rss_feeds_manage_empty_state">Нямате добавени RSS емисии.\n\nНатиснете плюса в горния десен ъгъл на екрана, за да добавите нова емисия.</string>
<string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string> <string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string>

View File

@@ -134,8 +134,10 @@
<!--Forum Sharing--> <!--Forum Sharing-->
<string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string> <string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_remove_blog_ok">Dilemel</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_remove_feed_ok">Dilemel</string>
<!--Settings Network--> <!--Settings Network-->
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="lock_setting_title">Digevreañ</string> <string name="lock_setting_title">Digevreañ</string>

View File

@@ -43,6 +43,8 @@
</plurals> </plurals>
<string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string> <string name="expiry_update">S\'ha ampliat la data de venciment de la prova. El vostre compte caducarà ara en %d dies.</string>
<string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string> <string name="expiry_date_reached">Aquest programa ha caducat.\nGràcies per haver-lo provat!</string>
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Obre el calaix de navegació</string> <string name="nav_drawer_open_description">Obre el calaix de navegació</string>
<string name="nav_drawer_close_description">Tanca el calaix de navegació</string> <string name="nav_drawer_close_description">Tanca el calaix de navegació</string>
@@ -207,7 +209,7 @@
<string name="choose_forum_hint">Trieu un nom per al fòrum</string> <string name="choose_forum_hint">Trieu un nom per al fòrum</string>
<string name="create_forum_button">Crea el fòrum</string> <string name="create_forum_button">Crea el fòrum</string>
<string name="forum_created_toast">S\'ha creat el fòrum</string> <string name="forum_created_toast">S\'ha creat el fòrum</string>
<string name="no_forum_posts">Aquest fòrum està buit.\n\nUtilitzeu la icona de la ploma a la part superior per redactar la primera publicació.\n\nEsteu aquí sol? Compartiu aquest fòrum amb els vostres contactes!</string> <string name="no_forum_posts">No hi ha publicacions per mostrar</string>
<string name="no_posts">No hi ha publicacions</string> <string name="no_posts">No hi ha publicacions</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d publicacio</item> <item quantity="one">%d publicacio</item>
@@ -251,7 +253,7 @@
</plurals> </plurals>
<string name="nobody">Ningú</string> <string name="nobody">Ningú</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Aquest blog està buit.\n\nPotser l\'autor encara no hi ha escrit res o que la persona que us ha compartit aquest blog hagi de connectar-se, llavors es podran sincronitzar les publicacions.</string> <string name="blogs_other_blog_empty_state">No hi ha publicacions per mostrar</string>
<string name="read_more">llegir més</string> <string name="read_more">llegir més</string>
<string name="blogs_write_blog_post">Escriviu una publicació de blog</string> <string name="blogs_write_blog_post">Escriviu una publicació de blog</string>
<string name="blogs_write_blog_post_body_hint">Escriviu la vostra publicació al blog aquí</string> <string name="blogs_write_blog_post_body_hint">Escriviu la vostra publicació al blog aquí</string>

View File

@@ -31,7 +31,9 @@
<string name="dialog_title_lost_password">Pasahitz galdua</string> <string name="dialog_title_lost_password">Pasahitz galdua</string>
<string name="dialog_message_lost_password">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian, beraz ezin dugu zure pasahitza berrezarri. Zure kontua ezabatu eta berriro hasi nahi duzu?\n\nKontuz: Zure identitateak, kontaktuak eta mezuak betirako galduko dira.</string> <string name="dialog_message_lost_password">Zure Briar kontua zure gailuan zifratuta gordetzen da, ez hodeian, beraz ezin dugu zure pasahitza berrezarri. Zure kontua ezabatu eta berriro hasi nahi duzu?\n\nKontuz: Zure identitateak, kontaktuak eta mezuak betirako galduko dira.</string>
<string name="startup_failed_notification_title">Ezin izan da Briar abiatu</string> <string name="startup_failed_notification_title">Ezin izan da Briar abiatu</string>
<string name="startup_failed_notification_text">Sakatu informazio gehiagorako</string>
<string name="startup_failed_activity_title">Briar abio-hutsegitea</string> <string name="startup_failed_activity_title">Briar abio-hutsegitea</string>
<string name="startup_failed_data_too_new_error">Aplikazioaren bertsio hau zaharregia da. Eguneratu azken bertsiora eta saiatu berriro.</string>
<string name="startup_failed_service_error">Briar aplikazioak ezin izan du ezinbesteko plugin bat abiatu. Briar berrinstalatzeak arazoa konpondu ohi du. Hala ere, jakin zure kontua eta datuak galduko dituzula Briar aplikazioak ez baititu zerbitzari zentralak erabiltzen zure datuak gordetzeko.</string> <string name="startup_failed_service_error">Briar aplikazioak ezin izan du ezinbesteko plugin bat abiatu. Briar berrinstalatzeak arazoa konpondu ohi du. Hala ere, jakin zure kontua eta datuak galduko dituzula Briar aplikazioak ez baititu zerbitzari zentralak erabiltzen zure datuak gordetzeko.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="one">Hau Briar-en probetarako bertsio bat da. Zure kontua egun %d barru iraungituko da eta ezin da berriztu.</item> <item quantity="one">Hau Briar-en probetarako bertsio bat da. Zure kontua egun %d barru iraungituko da eta ezin da berriztu.</item>
@@ -39,6 +41,8 @@
</plurals> </plurals>
<string name="expiry_update">Probetarako iraungitze data luzatu da. Zure kontua %d egun barru iraungituko da.</string> <string name="expiry_update">Probetarako iraungitze data luzatu da. Zure kontua %d egun barru iraungituko da.</string>
<string name="expiry_date_reached">Programa hau iraungitu da.\nEskerrik asko probatzeagatik!</string> <string name="expiry_date_reached">Programa hau iraungitu da.\nEskerrik asko probatzeagatik!</string>
<string name="startup_open_database">Datu-basea deszifratzen...</string>
<string name="startup_migrate_database">Datu-basea eguneratzen...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Ireki nabigazio tiradera</string> <string name="nav_drawer_open_description">Ireki nabigazio tiradera</string>
<string name="nav_drawer_close_description">Itxi nabigazio tiradera</string> <string name="nav_drawer_close_description">Itxi nabigazio tiradera</string>
@@ -203,7 +207,7 @@
<string name="choose_forum_hint">Hautatu zure foroaren izena</string> <string name="choose_forum_hint">Hautatu zure foroaren izena</string>
<string name="create_forum_button">Sortu foroa</string> <string name="create_forum_button">Sortu foroa</string>
<string name="forum_created_toast">Foroa sortuta</string> <string name="forum_created_toast">Foroa sortuta</string>
<string name="no_forum_posts">Foro hau hutsik dago.\n\nErabili goiko arkatzaren ikonoa lehen mezua idazteko.\n\nBakarrik sentitzen zara hemen? Partekatu foro hau zure kontaktuekin!</string> <string name="no_forum_posts">Ez dago mezurik erakusteko</string>
<string name="no_posts">Sarrerarik ez</string> <string name="no_posts">Sarrerarik ez</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">Bidalketa %d</item> <item quantity="one">Bidalketa %d</item>
@@ -247,7 +251,7 @@
</plurals> </plurals>
<string name="nobody">Inor ez</string> <string name="nobody">Inor ez</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Blog hau orain hutsik dago.\n\nEgileak ez du ezer idatzi edo blog hau zurekin partekatu duen kontaktua deskonektatuta dago, eta ezin da orain sinkronizatu.</string> <string name="blogs_other_blog_empty_state">Ez dago mezurik erakusteko</string>
<string name="read_more">irakurri gehiago</string> <string name="read_more">irakurri gehiago</string>
<string name="blogs_write_blog_post">Idatzi blog sarrera</string> <string name="blogs_write_blog_post">Idatzi blog sarrera</string>
<string name="blogs_write_blog_post_body_hint">Idatzi zure blog sarrera hemen</string> <string name="blogs_write_blog_post_body_hint">Idatzi zure blog sarrera hemen</string>

View File

@@ -40,6 +40,8 @@
</plurals> </plurals>
<string name="expiry_update">Testiaikaa on pidennetty. Tilisi tulee nyt vanhentumaan %d päivän kuluttua.</string> <string name="expiry_update">Testiaikaa on pidennetty. Tilisi tulee nyt vanhentumaan %d päivän kuluttua.</string>
<string name="expiry_date_reached">Tämä sovellus on vanhentunut.\nKiitos testaamisesta!</string> <string name="expiry_date_reached">Tämä sovellus on vanhentunut.\nKiitos testaamisesta!</string>
<string name="startup_open_database">Puretaan tietokannan salaus...</string>
<string name="startup_migrate_database">Päivitetään tietokanta...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Avaa navigointilaatikko</string> <string name="nav_drawer_open_description">Avaa navigointilaatikko</string>
<string name="nav_drawer_close_description">Sulje navigointilaatikko</string> <string name="nav_drawer_close_description">Sulje navigointilaatikko</string>
@@ -94,11 +96,11 @@
<string name="show_onboarding">Näytä apudialogi</string> <string name="show_onboarding">Näytä apudialogi</string>
<string name="fix">Korjaa</string> <string name="fix">Korjaa</string>
<string name="help">Ohje</string> <string name="help">Ohje</string>
<string name="sorry">Anteeksi</string> <string name="sorry">Pahoittelemme</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Näyttää siltä, että olet uusi täällä, eikä sinulla vielä ole yhteyshenkilöitä.\n\nNapauta yllä olevaa + -kuvaketta ja seuraa ohjeita lisätäksesi kavereita luetteloon.\n\nMuista: Voit ainoastaan lisätä uusia yhteyshenkilöitä tapaamalla heidät kasvokkain. Tämä estää sen, että joku voisi esittää olevansa sinä tai lukea viestejäsi tulevaisuudessa.</string> <string name="no_contacts">Näyttää siltä, että olet uusi täällä, eikä sinulla vielä ole yhteyshenkilöitä.\n\nNapauta yllä olevaa + -kuvaketta ja seuraa ohjeita lisätäksesi kavereita luetteloon.\n\nMuista: Voit ainoastaan lisätä uusia yhteyshenkilöitä tapaamalla heidät kasvokkain. Tämä estää sen, että joku voisi esittää olevansa sinä tai lukea viestejäsi tulevaisuudessa.</string>
<string name="date_no_private_messages">Ei viestejä.</string> <string name="date_no_private_messages">Ei viestejä.</string>
<string name="no_private_messages">Tämä on keskustelunäkymä.\n\nKeskustelu näyttää puuttuvan.\n\nNapauta syöttökenttää sivun pohjalla aloittaaksesi keskustelun.</string> <string name="no_private_messages">Ei viestejä</string>
<string name="message_hint">Kirjoita viesti</string> <string name="message_hint">Kirjoita viesti</string>
<string name="delete_contact">Poista yhteystieto</string> <string name="delete_contact">Poista yhteystieto</string>
<string name="dialog_title_delete_contact">Vahvista yhteystiedon poistaminen</string> <string name="dialog_title_delete_contact">Vahvista yhteystiedon poistaminen</string>
@@ -203,7 +205,7 @@
<string name="choose_forum_hint">Valitse nimi foorumillesi</string> <string name="choose_forum_hint">Valitse nimi foorumillesi</string>
<string name="create_forum_button">Luo foorumi</string> <string name="create_forum_button">Luo foorumi</string>
<string name="forum_created_toast">Foorumi luotu</string> <string name="forum_created_toast">Foorumi luotu</string>
<string name="no_forum_posts">Tämä foorumi on tyhjä.\n\nKäytä yllä olevaa kynää kirjoittaaksesi ensimmäisen viestin.\n\nTuntuuko yksinäiseltä? Kerro tästä foorumista muille!</string> <string name="no_forum_posts">Ei kirjoituksia</string>
<string name="no_posts">Ei viestejä</string> <string name="no_posts">Ei viestejä</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d viesti</item> <item quantity="one">%d viesti</item>
@@ -231,7 +233,7 @@
<string name="forum_invitations_title">Kutsut liittyä foorumeihin</string> <string name="forum_invitations_title">Kutsut liittyä foorumeihin</string>
<string name="forum_invitation_exists">Olet jo hyväksynyt kutsun liittyä tähän foorumiin. Useamman kutsun hyväksyminen kasvattaa ja vahvistaa foorumin kommunikaatiota.</string> <string name="forum_invitation_exists">Olet jo hyväksynyt kutsun liittyä tähän foorumiin. Useamman kutsun hyväksyminen kasvattaa ja vahvistaa foorumin kommunikaatiota.</string>
<string name="forum_joined_toast">Liittyi foorumiin</string> <string name="forum_joined_toast">Liittyi foorumiin</string>
<string name="forum_declined_toast">Kutsu liittyä foorumiin on hylätty</string> <string name="forum_declined_toast">Kutsu hylätty</string>
<string name="shared_by_format">Jakanut %s</string> <string name="shared_by_format">Jakanut %s</string>
<string name="forum_invitation_already_sharing">On jo jakamassa</string> <string name="forum_invitation_already_sharing">On jo jakamassa</string>
<string name="forum_invitation_response_accepted_sent">Olet hyväksynyt käyttäjän %s lähettämän foorumikutsun.</string> <string name="forum_invitation_response_accepted_sent">Olet hyväksynyt käyttäjän %s lähettämän foorumikutsun.</string>
@@ -247,10 +249,10 @@
</plurals> </plurals>
<string name="nobody">Ei kukaan</string> <string name="nobody">Ei kukaan</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Blogi on tällä hetkellä tyhjä.\n\nJoko blogin julkaisija ei ole kirjoittanut mitään vielä, tai tämän blogin sinulle jakaneen henkilön on liityttävä takaisin verkkoon, jotta blogikirjoitukset voitaisiin synkronisoida.</string> <string name="blogs_other_blog_empty_state">Ei kirjoituksia</string>
<string name="read_more">lue lisää</string> <string name="read_more">lue lisää</string>
<string name="blogs_write_blog_post">Julkaise blogikirjoitus</string> <string name="blogs_write_blog_post">Julkaise blogikirjoitus</string>
<string name="blogs_write_blog_post_body_hint">Kirjoita blogikirjoitus tähän</string> <string name="blogs_write_blog_post_body_hint">Kirjoita blogikirjoitus</string>
<string name="blogs_publish_blog_post">Julkaise</string> <string name="blogs_publish_blog_post">Julkaise</string>
<string name="blogs_blog_post_created">Blogikirjoitus julkaistu</string> <string name="blogs_blog_post_created">Blogikirjoitus julkaistu</string>
<string name="blogs_blog_post_received">Uusi blogikirjoitus vastaanotettu</string> <string name="blogs_blog_post_received">Uusi blogikirjoitus vastaanotettu</string>
@@ -275,7 +277,7 @@
<string name="blogs_sharing_invitation_sent">Olet jakanut \"%1$s\" -nimisen blogin käyttäjälle %2$s.</string> <string name="blogs_sharing_invitation_sent">Olet jakanut \"%1$s\" -nimisen blogin käyttäjälle %2$s.</string>
<string name="blogs_sharing_invitations_title">Blogi kutsut</string> <string name="blogs_sharing_invitations_title">Blogi kutsut</string>
<string name="blogs_sharing_joined_toast">Blogi tilattu</string> <string name="blogs_sharing_joined_toast">Blogi tilattu</string>
<string name="blogs_sharing_declined_toast">Kutsu blogiin on hylätty</string> <string name="blogs_sharing_declined_toast">Kutsu hylätty</string>
<string name="sharing_status_blog">Kuka tahansa joka tilaa blogin voi jakaa sen tuntemilleen käyttäjille. Olet jakamassa tämän blogin seuraaville käyttäjille. Voi myös olla muita blogin tilaajia, joita sinä et näe.</string> <string name="sharing_status_blog">Kuka tahansa joka tilaa blogin voi jakaa sen tuntemilleen käyttäjille. Olet jakamassa tämän blogin seuraaville käyttäjille. Voi myös olla muita blogin tilaajia, joita sinä et näe.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Tuo RSS syöte</string> <string name="blogs_rss_feeds_import">Tuo RSS syöte</string>
@@ -376,4 +378,6 @@
<string name="permission_camera_request_body">Skannatakseen QR koodin, Briar tarvitsee luvan käyttää kameraa.</string> <string name="permission_camera_request_body">Skannatakseen QR koodin, Briar tarvitsee luvan käyttää kameraa.</string>
<string name="permission_camera_denied_body">Olet kieltänyt käyttämästä kameraa, mutta yhteyshenkilöiden lisääminen vaatii kameran käyttöä.\n\nOle hyvä ja harkitse kameraluvan myöntämistä.</string> <string name="permission_camera_denied_body">Olet kieltänyt käyttämästä kameraa, mutta yhteyshenkilöiden lisääminen vaatii kameran käyttöä.\n\nOle hyvä ja harkitse kameraluvan myöntämistä.</string>
<string name="permission_camera_denied_toast">Kameralupaa ei myönnetty</string> <string name="permission_camera_denied_toast">Kameralupaa ei myönnetty</string>
<string name="qr_code">QR-koodi</string>
<string name="show_qr_code_fullscreen">Näytä QR-koodi koko näytöllä</string>
</resources> </resources>

View File

@@ -33,16 +33,18 @@
<string name="startup_failed_notification_title">Impossible de démarrer Briar</string> <string name="startup_failed_notification_title">Impossible de démarrer Briar</string>
<string name="startup_failed_notification_text">Toucher pour plus dinformations.</string> <string name="startup_failed_notification_text">Toucher pour plus dinformations.</string>
<string name="startup_failed_activity_title">Échec 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 quelque raison, votre base de données Briar est corrompue sans espoir de réparation. Votre compte, vos données et tous vos contacts sont perdus. Malheureusement, vous devez réinstaller Briar et créer un nouveau compte en choisissant « Jai oublié mon mot de passe » dans linvite de mot de passe.</string> <string name="startup_failed_db_error">Pour quelque raison, votre base de données Briar est corrompue sans espoir de réparation. Votre compte, vos données et tous vos contacts sont perdus. Malheureusement, vous devez réinstaller Briar et créer un nouveau compte en choisissant « Jai oublié mon mot de passe » dans linvite de mot de passe.</string>
<string name="startup_failed_data_too_old_error">Votre compte a été créé avec une ancienne version de cette appli et ne peut pas être ouvert avec cette version. Vous devez soit installer lancienne version soit supprimer votre compte en choisissant « Jai oublié mon mot de passe » dans linvite de mot de passe.</string> <string name="startup_failed_data_too_old_error">Votre compte a été créé avec une ancienne version de cette appli et ne peut pas être ouvert avec cette version. Vous devez soit installer lancienne version, soit créer un nouveau compte en choisissant « Jai oublié mon mot de passe » dans linvite de mot de passe.</string>
<string name="startup_failed_data_too_new_error">Cette version de lappli est trop ancienne. Veuillez la mettre à niveau vers la dernière version et ressayer.</string> <string name="startup_failed_data_too_new_error">Cette version de lappli est trop ancienne. Veuillez la mettre à niveau vers la dernière version et ressayer.</string>
<string name="startup_failed_service_error">Briar na pas pu démarrer un greffon exigé. Réinstaller Briar résout généralement ce problème. Veuillez cependant noter que vous perdrez votre compte et toutes données relatives puisque Briar nutilise pas de serveurs centralisés sur lesquels enregistrer vos données.</string> <string name="startup_failed_service_error">Briar na pas pu démarrer un greffon exigé. Réinstaller Briar résout généralement ce problème. Veuillez cependant noter que vous perdrez votre compte et toutes données relatives puisque Briar nutilise pas de serveurs centralisés sur lesquels enregistrer vos données.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="one">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item> <item quantity="one">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
<item quantity="other">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jours et ne peut pas être renouvelé.</item> <item quantity="other">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
</plurals> </plurals>
<string name="expiry_update">La date de fin de test a été repoussée. Votre compte arrivera maintenant à expiration dans %d jours.</string> <string name="expiry_update">La date de fin de test a été repoussée. Votre compte arrivera maintenant à expiration dans %d jours.</string>
<string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de lavoir testé!</string> <string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de lavoir testé!</string>
<string name="startup_open_database">Déchiffrement de la base de données…</string>
<string name="startup_migrate_database">Mise à niveau de la base de données…</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Ouvrir le tiroir de navigation</string> <string name="nav_drawer_open_description">Ouvrir le tiroir de navigation</string>
<string name="nav_drawer_close_description">Fermer le tiroir de navigation</string> <string name="nav_drawer_close_description">Fermer le tiroir de navigation</string>
@@ -99,9 +101,9 @@
<string name="help">Aide</string> <string name="help">Aide</string>
<string name="sorry">Désolé</string> <string name="sorry">Désolé</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Il semble que soyez nouveau ici, sans encore aucun contact.\n\nTouchez licône + en haut et suivez les instructions pour ajouter des amis à votre liste.\n\nVeuillez ne pas oublier que vous pouvez seulement ajouter des contacts en les rencontrant directement, afin déviter que quelquun se fasse passer pour vous et puisse lire vos messages à lavenir.</string> <string name="no_contacts">Aucun contact à afficher\n\nTouchez licône + pour ajouter un contact</string>
<string name="date_no_private_messages">Aucun message.</string> <string name="date_no_private_messages">Aucun message.</string>
<string name="no_private_messages">Ceci est la vue des conversations.\n\nIl semble ne pas y en avoir.\n\nIl suffit de toucher le champ de saisie ci-bas pour en démarrer une.</string> <string name="no_private_messages">Aucun message à afficher</string>
<string name="message_hint">Rédiger le message</string> <string name="message_hint">Rédiger le message</string>
<string name="delete_contact">Supprimer le contact</string> <string name="delete_contact">Supprimer le contact</string>
<string name="dialog_title_delete_contact">Confirmer la suppression du contact</string> <string name="dialog_title_delete_contact">Confirmer la suppression du contact</string>
@@ -149,7 +151,7 @@
<item quantity="other">%d nouveaux contacts ont été ajoutés.</item> <item quantity="other">%d nouveaux contacts ont été ajoutés.</item>
</plurals> </plurals>
<!--Private Groups--> <!--Private Groups-->
<string name="groups_list_empty">Vous ne participez à aucun groupe.\n\nTouchez licône + ci-haut pour en créer un ou demandez à vos contacts de vous inviter dans lun des leurs.</string> <string name="groups_list_empty">Aucun groupe à afficher\n\nTouchez licône + pour créer un groupe ou demandez à vos contacts de partager des groupes avec vous</string>
<string name="groups_created_by">Créé par %s</string> <string name="groups_created_by">Créé par %s</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d message</item> <item quantity="one">%d message</item>
@@ -202,12 +204,12 @@
<string name="groups_reveal_visible_revealed_by_contact">Votre lien avec le contact est visible par le groupe (dévoilé par %s)</string> <string name="groups_reveal_visible_revealed_by_contact">Votre lien avec le contact est visible par le groupe (dévoilé par %s)</string>
<string name="groups_reveal_invisible">Votre lien avec le contact nest pas visible par le groupe</string> <string name="groups_reveal_invisible">Votre lien avec le contact nest pas visible par le groupe</string>
<!--Forums--> <!--Forums-->
<string name="no_forums">Vous navez pas encore de forums.\n\nPourquoi ne pas en créer un en touchant licône + ci-haut?\n\nVous pouvez aussi demander à vos contacts den partager avec vous.</string> <string name="no_forums">Aucun forum à afficher\n\nTouchez licône + pour créer un forum ou demandez à vos contacts de partager des forums avec vous</string>
<string name="create_forum_title">Créer un forum</string> <string name="create_forum_title">Créer un forum</string>
<string name="choose_forum_hint">Choisir un nom pour votre forum </string> <string name="choose_forum_hint">Choisir un nom pour votre forum </string>
<string name="create_forum_button">Créer un forum</string> <string name="create_forum_button">Créer un forum</string>
<string name="forum_created_toast">Le forum a été créé</string> <string name="forum_created_toast">Le forum a été créé</string>
<string name="no_forum_posts">Ce forum est vide.\n\nUtilisez licône de crayon ci-haut pour rédiger le premier article.\n\nVous sentez-vous seul ici? Partagez ce forum avec vos contacts!</string> <string name="no_forum_posts">Aucun article à afficher</string>
<string name="no_posts">Aucun article</string> <string name="no_posts">Aucun article</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d article</item> <item quantity="one">%d article</item>
@@ -219,23 +221,23 @@
<string name="btn_reply">Répondre</string> <string name="btn_reply">Répondre</string>
<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">Voulez-vous vraiment quitter ce forum? Les contacts avec qui vous lavez partagé pourraient ne plus en recevoir les mises à jour.</string> <string name="dialog_message_leave_forum">Voulez-vous vraiment quitter ce forum?\n\nLes contacts avec qui vous lavez partagé pourraient ne plus en recevoir les mises à jour.</string>
<string name="dialog_button_leave">Quitter</string> <string name="dialog_button_leave">Quitter</string>
<string name="forum_left_toast">A quitté le forum</string> <string name="forum_left_toast">À quitté le forum</string>
<!--Forum Sharing--> <!--Forum Sharing-->
<string name="forum_share_button">Partager le forum</string> <string name="forum_share_button">Partager le forum</string>
<string name="contacts_selected">Des contacts ont été sélectionnés</string> <string name="contacts_selected">Des contacts ont été sélectionnés</string>
<string name="activity_share_toolbar_header">Choisir des contacts</string> <string name="activity_share_toolbar_header">Choisir des contacts</string>
<string name="no_contacts_selector">Il semble que soyez nouveau ici, sans encore aucun contact.\n\nRevenez ici après avoir ajouté votre premier contact.</string> <string name="no_contacts_selector">Aucun contact à afficher.\n\nVeuillez revenir ici après avoir ajouté un contact</string>
<string name="forum_shared_snackbar">Le forum a été partagé avec les contacts choisis</string> <string name="forum_shared_snackbar">Le forum a été partagé avec les contacts choisis</string>
<string name="forum_share_message">Ajouter un message (facultatif)</string> <string name="forum_share_message">Ajouter un message (facultatif)</string>
<string name="forum_share_error">Une erreur est survenue lors du partage de ce forum.</string> <string name="forum_share_error">Une erreur est survenue lors du partage de ce forum.</string>
<string name="forum_invitation_received">%1$s a partagé le forum « %2$s » avec vous.</string> <string name="forum_invitation_received">%1$s a partagé le forum « %2$s » avec vous.</string>
<string name="forum_invitation_sent">Vous avez partagé le forum « %1$s » avec %2$s.</string> <string name="forum_invitation_sent">Vous avez partagé le forum « %1$s » avec %2$s.</string>
<string name="forum_invitations_title">Invitations au forum</string> <string name="forum_invitations_title">Invitations au forum</string>
<string name="forum_invitation_exists">Vous avez déjà accepté une invitation à ce forum. En acceptant dautres invitations, vous augmenterez et renforcerez la communication de ce forum.</string> <string name="forum_invitation_exists">Vous avez déjà accepté une invitation à ce forum.\n\n En acceptant dautres invitations, vous rendrez la communication vers ce forum plus rapide et plus fiable.</string>
<string name="forum_joined_toast">Vous vous êtes joint au forum</string> <string name="forum_joined_toast">Vous vous êtes joint au forum</string>
<string name="forum_declined_toast">Linvitation au forum a été refusée</string> <string name="forum_declined_toast">Linvitation a été refusée</string>
<string name="shared_by_format">Partagé par %s</string> <string name="shared_by_format">Partagé par %s</string>
<string name="forum_invitation_already_sharing">Le forum est déjà partagé</string> <string name="forum_invitation_already_sharing">Le forum est déjà partagé</string>
<string name="forum_invitation_response_accepted_sent">Vous avez accepté linvitation de %s au forum.</string> <string name="forum_invitation_response_accepted_sent">Vous avez accepté linvitation de %s au forum.</string>
@@ -251,18 +253,18 @@
</plurals> </plurals>
<string name="nobody">Personne</string> <string name="nobody">Personne</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Ce blogue est actuellement vide.\n\nSoit lauteur na encore rien écrit, soit la personne qui la partagé avec vous doit se connecter pour que les articles soient synchronisés.</string> <string name="blogs_other_blog_empty_state">Aucun billet à afficher</string>
<string name="read_more">en lire davantage</string> <string name="read_more">en lire davantage</string>
<string name="blogs_write_blog_post">Écrire un article de blogue</string> <string name="blogs_write_blog_post">Écrire un billet de blogue</string>
<string name="blogs_write_blog_post_body_hint">Saisir votre message de blogue ici</string> <string name="blogs_write_blog_post_body_hint">Tapez votre billet de blogue</string>
<string name="blogs_publish_blog_post">Publier</string> <string name="blogs_publish_blog_post">Publier</string>
<string name="blogs_blog_post_created">Larticle de blogue a été créé</string> <string name="blogs_blog_post_created">Le billet de blogue a été créé</string>
<string name="blogs_blog_post_received">Un nouvel article de blogue a été reçu</string> <string name="blogs_blog_post_received">Un nouvel billet de blogue a été reçu</string>
<string name="blogs_blog_post_scroll_to">Atteindre</string> <string name="blogs_blog_post_scroll_to">Atteindre</string>
<string name="blogs_feed_empty_state">Ceci est le fil global des blogues.\n\nIl semble que personne nait encore rien écrit.\n\nSoyez le premier et touchez licône de crayon pour rédiger un nouvel article de blogue.</string> <string name="blogs_feed_empty_state">Aucun billet à afficher.\n\n\Les billets de vos contacts et les blogues auxquels vous vous abonnez apparaîtront ici.\n\nTouchez licône de crayon pour rédiger un billet</string>
<string name="blogs_remove_blog">Supprimer le blogue</string> <string name="blogs_remove_blog">Supprimer le blogue</string>
<string name="blogs_remove_blog_dialog_message">Voulez-vous vraiment supprimer ce blogue et tous ses messages?\nNotez que cela ne le supprimera pas des appareils dautrui.</string> <string name="blogs_remove_blog_dialog_message">Voulez-vous vraiment supprimer ce blogue?\nLes billets seront supprimés de votre appareil mais pas des appareils dautrui.\n\nLes contacts avec qui vous avez partagé ce blogue pourraient ne plus en recevoir les mises à jour.</string>
<string name="blogs_remove_blog_ok">Supprimer le blogue</string> <string name="blogs_remove_blog_ok">Supprimer</string>
<string name="blogs_blog_removed">Le blogue a été supprimé</string> <string name="blogs_blog_removed">Le blogue a été supprimé</string>
<string name="blogs_reblog_comment_hint">Ajouter un commentaire (facultatif)</string> <string name="blogs_reblog_comment_hint">Ajouter un commentaire (facultatif)</string>
<string name="blogs_reblog_button">Rebloguer</string> <string name="blogs_reblog_button">Rebloguer</string>
@@ -279,7 +281,7 @@
<string name="blogs_sharing_invitation_sent">Vous avez partagé le blogue « %1$s » avec %2$s.</string> <string name="blogs_sharing_invitation_sent">Vous avez partagé le blogue « %1$s » avec %2$s.</string>
<string name="blogs_sharing_invitations_title">Invitations au blogue</string> <string name="blogs_sharing_invitations_title">Invitations au blogue</string>
<string name="blogs_sharing_joined_toast">Est abonné au blogue</string> <string name="blogs_sharing_joined_toast">Est abonné au blogue</string>
<string name="blogs_sharing_declined_toast">Linvitation au blogue a été refusée</string> <string name="blogs_sharing_declined_toast">Linvitation a été refusée</string>
<string name="sharing_status_blog">Quiconque est abonné à un blogue peut le partager avec ses contacts. Vous partagez ce blogue avec les contacts suivants. Il peut aussi y avoir dautres abonnés que vous ne pouvez pas voir.</string> <string name="sharing_status_blog">Quiconque est abonné à un blogue peut le partager avec ses contacts. Vous partagez ce blogue avec les contacts suivants. Il peut aussi y avoir dautres abonnés que vous ne pouvez pas voir.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importer un fil RSS</string> <string name="blogs_rss_feeds_import">Importer un fil RSS</string>
@@ -291,10 +293,10 @@
<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 fil</string> <string name="blogs_rss_remove_feed">Supprimer le fil</string>
<string name="blogs_rss_remove_feed_dialog_message">Voulez-vous vraiment supprimer ce fil et tous ses messages ?\nLes articles que vous avez partagés ne seront pas supprimés des appareils dautrui.</string> <string name="blogs_rss_remove_feed_dialog_message">Voulez-vous vraiment supprimer ce fil?\nLes billets seront supprimés de votre appareil mais pas des appareils dautrui.\n\nLes contacts avec qui vous avez partagé ce fil pourraient ne plus en recevoir les mises à jour.</string>
<string name="blogs_rss_remove_feed_ok">Supprimer le fil</string> <string name="blogs_rss_remove_feed_ok">Supprimer</string>
<string name="blogs_rss_feeds_manage_delete_error">Impossible de supprimer le fil!</string> <string name="blogs_rss_feeds_manage_delete_error">Impossible de supprimer le fil!</string>
<string name="blogs_rss_feeds_manage_empty_state">Vous navez encore importé aucun fil RSS.\n\nPourquoi ne pas cliquer sur licône + en haut à droite pour ajouter votre premier fil?</string> <string name="blogs_rss_feeds_manage_empty_state">Aucun fil RSS à afficher\n\nTouchez licône + pour importer un fil</string>
<string name="blogs_rss_feeds_manage_error">Un problème est survenu lors du chargement de vos fils. Veuillez ressayer ultérieurement.</string> <string name="blogs_rss_feeds_manage_error">Un problème est survenu lors du chargement de vos fils. Veuillez ressayer ultérieurement.</string>
<!--Settings Network--> <!--Settings Network-->
<string name="network_settings_title">Réseaux</string> <string name="network_settings_title">Réseaux</string>
@@ -331,12 +333,16 @@
<string name="notification_settings_title">Notifications</string> <string name="notification_settings_title">Notifications</string>
<string name="notify_private_messages_setting_title">Messages privés</string> <string name="notify_private_messages_setting_title">Messages privés</string>
<string name="notify_private_messages_setting_summary">Afficher des notifications pour les messages privés</string> <string name="notify_private_messages_setting_summary">Afficher des notifications pour les messages privés</string>
<string name="notify_private_messages_setting_summary_26">Configurer les alertes pour les messages privés</string>
<string name="notify_group_messages_setting_title">Messages de groupe</string> <string name="notify_group_messages_setting_title">Messages de groupe</string>
<string name="notify_group_messages_setting_summary">Afficher des alertes pour les messages de groupe</string> <string name="notify_group_messages_setting_summary">Afficher des alertes pour les messages de groupe</string>
<string name="notify_group_messages_setting_summary_26">Configurer les alertes pour les messages de groupe</string>
<string name="notify_forum_posts_setting_title">Articles de forum</string> <string name="notify_forum_posts_setting_title">Articles de forum</string>
<string name="notify_forum_posts_setting_summary">Afficher des alertes pour les articles de forum</string> <string name="notify_forum_posts_setting_summary">Afficher des alertes pour les articles de forum</string>
<string name="notify_blog_posts_setting_title">Articles de blogue</string> <string name="notify_forum_posts_setting_summary_26">Configurer les alertes pour les articles de forum</string>
<string name="notify_blog_posts_setting_summary">Afficher des alertes pour pour les articles de blogue</string> <string name="notify_blog_posts_setting_title">Billets de blogue</string>
<string name="notify_blog_posts_setting_summary">Afficher des alertes pour les billets de blogue</string>
<string name="notify_blog_posts_setting_summary_26">Configurer les alertes pour les billets de forum</string>
<string name="notify_vibration_setting">Vibrer</string> <string name="notify_vibration_setting">Vibrer</string>
<string name="notify_lock_screen_setting_title">Écran de verrouillage</string> <string name="notify_lock_screen_setting_title">Écran de verrouillage</string>
<string name="notify_lock_screen_setting_summary">Afficher les notifications sur lécran de verrouillage</string> <string name="notify_lock_screen_setting_summary">Afficher les notifications sur lécran de verrouillage</string>
@@ -380,4 +386,6 @@
<string name="permission_camera_request_body">Pour lire le code QR, Briar doit accéder à la caméra.</string> <string name="permission_camera_request_body">Pour lire le code QR, Briar doit accéder à la caméra.</string>
<string name="permission_camera_denied_body">Vous avez refusé laccès à la caméra, mais lajout de contacts exige lutilisation de celle-ci.\n\nVeuillez envisager dy accorder laccès.</string> <string name="permission_camera_denied_body">Vous avez refusé laccès à la caméra, mais lajout de contacts exige lutilisation de celle-ci.\n\nVeuillez envisager dy accorder laccès.</string>
<string name="permission_camera_denied_toast">Laccès à la caméra na pas été accordé</string> <string name="permission_camera_denied_toast">Laccès à la caméra na pas été accordé</string>
<string name="qr_code">Code QR</string>
<string name="show_qr_code_fullscreen">Afficher le code QR en plein écran</string>
</resources> </resources>

View File

@@ -107,11 +107,13 @@
<!--Blogs--> <!--Blogs-->
<string name="read_more">ler mais</string> <string name="read_more">ler mais</string>
<string name="blogs_publish_blog_post">Publicar</string> <string name="blogs_publish_blog_post">Publicar</string>
<string name="blogs_remove_blog_ok">Eliminar</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">Importar</string> <string name="blogs_rss_feeds_import_button">Importar</string>
<string name="blogs_rss_feeds_manage_author">Autor/a:</string> <string name="blogs_rss_feeds_manage_author">Autor/a:</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_ok">Eliminar</string>
<!--Settings Network--> <!--Settings Network-->
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="change_password">Cambiar contrasinal</string> <string name="change_password">Cambiar contrasinal</string>

View File

@@ -241,11 +241,13 @@
<string name="forum_declined_toast">הזמנה לפורום נדחתה</string> <string name="forum_declined_toast">הזמנה לפורום נדחתה</string>
<string name="shared_by_format">שותף על ידי %s</string> <string name="shared_by_format">שותף על ידי %s</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_remove_blog_ok">להסיר</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">ייבא</string> <string name="blogs_rss_feeds_import_button">ייבא</string>
<string name="blogs_rss_feeds_manage_author">מחבר:</string> <string name="blogs_rss_feeds_manage_author">מחבר:</string>
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string> <string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>
<string name="blogs_rss_remove_feed_ok">להסיר</string>
<!--Settings Network--> <!--Settings Network-->
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="security_settings_title">אבטחה</string> <string name="security_settings_title">אבטחה</string>

View File

@@ -43,6 +43,8 @@
</plurals> </plurals>
<string name="expiry_update">La scadenza della versione di prova è stata prorogata. Il tuo account ora scadrà fra %d giorni.</string> <string name="expiry_update">La scadenza della versione di prova è stata prorogata. Il tuo account ora scadrà fra %d giorni.</string>
<string name="expiry_date_reached">Questo software è scaduto.\nGrazie per il test!</string> <string name="expiry_date_reached">Questo software è scaduto.\nGrazie per il test!</string>
<string name="startup_open_database">Decrittazione database...</string>
<string name="startup_migrate_database">Aggiornamento database...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Apri la barra di navigazione</string> <string name="nav_drawer_open_description">Apri la barra di navigazione</string>
<string name="nav_drawer_close_description">Chiudi la barra di navigazione</string> <string name="nav_drawer_close_description">Chiudi la barra di navigazione</string>
@@ -207,7 +209,7 @@
<string name="choose_forum_hint">Scegli un nome per il tuo forum</string> <string name="choose_forum_hint">Scegli un nome per il tuo forum</string>
<string name="create_forum_button">Crea Forum</string> <string name="create_forum_button">Crea Forum</string>
<string name="forum_created_toast">Forum creato</string> <string name="forum_created_toast">Forum creato</string>
<string name="no_forum_posts">Questo forum è vuoto.\n\nUsa l\'icona penna in alto per comporre il primo post.\n\nTi senti solo qui? Condividi questo forum con i tuoi contatti!</string> <string name="no_forum_posts">Nessun post da mostrare</string>
<string name="no_posts">Nessun post.</string> <string name="no_posts">Nessun post.</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d post</item> <item quantity="one">%d post</item>
@@ -251,7 +253,7 @@
</plurals> </plurals>
<string name="nobody">Nessuno</string> <string name="nobody">Nessuno</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Questo blog è attualmente vuoto.\n\nO l\'autore non ha ancora scritto nulla, oppure la persona che ha condiviso questo blog con te deve tornare in line, in modo che i post possano sincronizzarsi.</string> <string name="blogs_other_blog_empty_state">Nessun post da mostrare</string>
<string name="read_more">leggi ancora</string> <string name="read_more">leggi ancora</string>
<string name="blogs_write_blog_post">Scrivere un post sul blog</string> <string name="blogs_write_blog_post">Scrivere un post sul blog</string>
<string name="blogs_write_blog_post_body_hint">Scrivi qui il tuo post del blog</string> <string name="blogs_write_blog_post_body_hint">Scrivi qui il tuo post del blog</string>

View File

@@ -97,11 +97,13 @@
<!--Forum Sharing--> <!--Forum Sharing-->
<!--Blogs--> <!--Blogs-->
<string name="blogs_publish_blog_post">公開</string> <string name="blogs_publish_blog_post">公開</string>
<string name="blogs_remove_blog_ok">解除</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">インポート</string> <string name="blogs_rss_feeds_import_button">インポート</string>
<string name="blogs_rss_feeds_manage_author">著者:</string> <string name="blogs_rss_feeds_manage_author">著者:</string>
<string name="blogs_rss_feeds_manage_updated">最終更新:</string> <string name="blogs_rss_feeds_manage_updated">最終更新:</string>
<string name="blogs_rss_remove_feed_ok">解除</string>
<!--Settings Network--> <!--Settings Network-->
<string name="tor_network_setting_never">二度としない</string> <string name="tor_network_setting_never">二度としない</string>
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
@@ -113,6 +115,7 @@
<!--Settings Notifications--> <!--Settings Notifications-->
<string name="notification_settings_title">通知</string> <string name="notification_settings_title">通知</string>
<string name="notify_private_messages_setting_title">プライベート・メッセージ</string> <string name="notify_private_messages_setting_title">プライベート・メッセージ</string>
<string name="notify_lock_screen_setting_title">ロック画面</string>
<string name="notify_sound_setting_disabled">なし</string> <string name="notify_sound_setting_disabled">なし</string>
<!--Settings Feedback--> <!--Settings Feedback-->
<!--Link Warning--> <!--Link Warning-->

View File

@@ -31,7 +31,9 @@
<string name="dialog_title_lost_password">Wachtwoord vergeten</string> <string name="dialog_title_lost_password">Wachtwoord vergeten</string>
<string name="dialog_message_lost_password">Je Briar-account is versleuteld opgeslagen op je apparaat, niet in de cloud, dus kunnen we je wachtwoord niet resetten. Wil je je account verwijderen en opnieuw beginnen?\n\nLet op: Je identiteiten, contacten en berichten zullen permanent verloren gaan.</string> <string name="dialog_message_lost_password">Je Briar-account is versleuteld opgeslagen op je apparaat, niet in de cloud, dus kunnen we je wachtwoord niet resetten. Wil je je account verwijderen en opnieuw beginnen?\n\nLet op: Je identiteiten, contacten en berichten zullen permanent verloren gaan.</string>
<string name="startup_failed_notification_title">Briar kon niet opstarten</string> <string name="startup_failed_notification_title">Briar kon niet opstarten</string>
<string name="startup_failed_notification_text">Tap voor meer informatie.</string>
<string name="startup_failed_activity_title">Opstarten Briar mislukt</string> <string name="startup_failed_activity_title">Opstarten Briar mislukt</string>
<string name="startup_failed_data_too_new_error">Deze versie van de is app is te oud. Upgrade a.u.b. naar de laatste versie en probeer het nog een keer.</string>
<string name="startup_failed_service_error">Briar kon de vereiste plug-in niet starten. Herinstalleren van Briar lost dit probleem meestal op. Let op dat je al je je account en alle gegevens die daaraan vast zitten zal verliezen omdat Briar geen centrale servers gebruikt om gegevens op te slaan.</string> <string name="startup_failed_service_error">Briar kon de vereiste plug-in niet starten. Herinstalleren van Briar lost dit probleem meestal op. Let op dat je al je je account en alle gegevens die daaraan vast zitten zal verliezen omdat Briar geen centrale servers gebruikt om gegevens op te slaan.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="one">Dit is een testversie van Briar. Je account verloopt binnen %d dag en kan niet worden vernieuwd.</item> <item quantity="one">Dit is een testversie van Briar. Je account verloopt binnen %d dag en kan niet worden vernieuwd.</item>
@@ -39,6 +41,8 @@
</plurals> </plurals>
<string name="expiry_update">De verloopdatum voor de test is vooruitgeschoven. Je account verloopt nu binnen %d dagen.</string> <string name="expiry_update">De verloopdatum voor de test is vooruitgeschoven. Je account verloopt nu binnen %d dagen.</string>
<string name="expiry_date_reached">Deze software is verlopen.\nBedankt vor het testen!</string> <string name="expiry_date_reached">Deze software is verlopen.\nBedankt vor het testen!</string>
<string name="startup_open_database">Database aan het ontsleutelen…</string>
<string name="startup_migrate_database">Database aan het upgraden…</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Open de navigatielade</string> <string name="nav_drawer_open_description">Open de navigatielade</string>
<string name="nav_drawer_close_description">Sluit de navigatielade</string> <string name="nav_drawer_close_description">Sluit de navigatielade</string>
@@ -93,6 +97,7 @@
<string name="show_onboarding">Toon helpdialoog</string> <string name="show_onboarding">Toon helpdialoog</string>
<string name="fix">Fiks</string> <string name="fix">Fiks</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="sorry">Excuses</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Zo te zien ben je nieuw hier en heb je nog geen contacten.\n\nTap op het +-icoon bovenaan en volg de instructies om een aantal vrienden aan je list toe te voegen.\n\nHerinner a.u.b.: Je kan alleen in levenden lijve nieuwe contacten toevoevoegen om te voorkomen dat anderen zich als jou voor kunnen doen of in de toekomst je berichten kunnen lezen.</string> <string name="no_contacts">Zo te zien ben je nieuw hier en heb je nog geen contacten.\n\nTap op het +-icoon bovenaan en volg de instructies om een aantal vrienden aan je list toe te voegen.\n\nHerinner a.u.b.: Je kan alleen in levenden lijve nieuwe contacten toevoevoegen om te voorkomen dat anderen zich als jou voor kunnen doen of in de toekomst je berichten kunnen lezen.</string>
<string name="date_no_private_messages">Geen berichten.</string> <string name="date_no_private_messages">Geen berichten.</string>
@@ -114,6 +119,7 @@
<string name="contact_already_exists">Contact %s bestaat al</string> <string name="contact_already_exists">Contact %s bestaat al</string>
<string name="contact_exchange_failed">Uitwisselen contact is mislukt</string> <string name="contact_exchange_failed">Uitwisselen contact is mislukt</string>
<string name="qr_code_invalid">De QR-code is ongeldig</string> <string name="qr_code_invalid">De QR-code is ongeldig</string>
<string name="qr_code_unsupported">De QR-code die je probeert te scannen is van een oude ver van %s die niet meer wordt ondersteund.\n\nControleer a.u.b. dat jullie beide de laatste versie gebruiken en probeer het nog een keer.</string>
<string name="camera_error">Camerafout</string> <string name="camera_error">Camerafout</string>
<string name="connecting_to_device">Aan het verbinden met apparaat\u2026</string> <string name="connecting_to_device">Aan het verbinden met apparaat\u2026</string>
<string name="authenticating_with_device">Aan het authentificeren met apparaat\u2026</string> <string name="authenticating_with_device">Aan het authentificeren met apparaat\u2026</string>
@@ -367,6 +373,8 @@
<string name="progress_title_logout">Uitloggen van Briar…</string> <string name="progress_title_logout">Uitloggen van Briar…</string>
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Schermoverlay detecteerd</string> <string name="screen_filter_title">Schermoverlay detecteerd</string>
<string name="screen_filter_body">Een andere app ligt zich bovenop Briar. Om je veiligheid te beschermen zal Briar niet reageren op aanrakingen als er een andere app bovenop ligt.\n\nDe volgende apps proberen waarschijnlijk dit te doen:\n\n%1$s</string>
<string name="screen_filter_allow">Sta toe dat deze apps bovenop te liggen</string>
<!--Permission Requests--> <!--Permission Requests-->
<string name="permission_camera_title">Cameratoestemming</string> <string name="permission_camera_title">Cameratoestemming</string>
<string name="permission_camera_request_body">Om de QR-code te scannen moet Briar toegang hebben tot de camera.</string> <string name="permission_camera_request_body">Om de QR-code te scannen moet Briar toegang hebben tot de camera.</string>

View File

@@ -33,8 +33,8 @@
<string name="startup_failed_notification_title">Briar não pode iniciar</string> <string name="startup_failed_notification_title">Briar não pode iniciar</string>
<string name="startup_failed_notification_text">Pressione para mais informações.</string> <string name="startup_failed_notification_text">Pressione para mais informações.</string>
<string name="startup_failed_activity_title">Inicialização do Briar falhou</string> <string name="startup_failed_activity_title">Inicialização do Briar falhou</string>
<string name="startup_failed_db_error">Por algum motivo, o banco de dados do seu Briar está corrompido e não é possível repará-lo. Sua conta, seus dados e seus contatos foram perdidos. Infelizmente, você precisará reinstalar o Briar e criar uma nova conta escolhendo a opção \'Esqueci minha senha\'.</string> <string name="startup_failed_db_error">Por algum motivo, o banco de dados do seu Briar está corrompido e não é possível repará-lo. Sua conta, seus dados e seus contatos foram perdidos. Infelizmente, você precisará reinstalar o Briar ou criar uma nova conta escolhendo a opção \'Esqueci minha senha\'.</string>
<string name="startup_failed_data_too_old_error">Sua conta foi criada numa versão mais antiga do aplicativo e não pode ser aberta nesta versão. Você pode reinstalar a versão antiga ou deleter sua conta antiga escolhendo a opção \"Esqueci minha senha\".</string> <string name="startup_failed_data_too_old_error">Sua conta foi criada numa versão mais antiga do aplicativo e não pode ser aberta nesta versão. Você pode reinstalar a versão antiga ou criar uma conta nova escolhendo a opção \"Esqueci minha senha\".</string>
<string name="startup_failed_data_too_new_error">A versão deste aplicativo é muito antiga. Por favor, atualize para a versão mais nova e tente novamente.</string> <string name="startup_failed_data_too_new_error">A versão deste aplicativo é muito antiga. Por favor, atualize para a versão mais nova e tente novamente.</string>
<string name="startup_failed_service_error">O Briar não pode iniciar devido a um plugin. Reinstalar o Briar geralmente resolve esse problema. Porém, note que ao fazer isso você perderá sua conta e todos os dados associados a ela, já que o Briar não usa um servidor central para armazenar seus dados.</string> <string name="startup_failed_service_error">O Briar não pode iniciar devido a um plugin. Reinstalar o Briar geralmente resolve esse problema. Porém, note que ao fazer isso você perderá sua conta e todos os dados associados a ela, já que o Briar não usa um servidor central para armazenar seus dados.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
@@ -43,6 +43,8 @@
</plurals> </plurals>
<string name="expiry_update">O prazo de validade da versão de teste foi extendido. Agora sua conta irá expirar em %d dias.</string> <string name="expiry_update">O prazo de validade da versão de teste foi extendido. Agora sua conta irá expirar em %d dias.</string>
<string name="expiry_date_reached">Este software expirou.\nObrigado por testar!</string> <string name="expiry_date_reached">Este software expirou.\nObrigado por testar!</string>
<string name="startup_open_database">Descriptografando Banco de Dados...</string>
<string name="startup_migrate_database">Atualizando Banco de Dados...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Abrir aba de navegação</string> <string name="nav_drawer_open_description">Abrir aba de navegação</string>
<string name="nav_drawer_close_description">Fechar aba de navegação</string> <string name="nav_drawer_close_description">Fechar aba de navegação</string>
@@ -358,7 +360,7 @@
<string name="briar_crashed">Briar encerrou de maneira inesperada</string> <string name="briar_crashed">Briar encerrou de maneira inesperada</string>
<string name="not_your_fault">Isso não é sua culpa.</string> <string name="not_your_fault">Isso não é sua culpa.</string>
<string name="please_send_report">Nós ajude a construir um Briar melhor enviando um relatório de falhas.</string> <string name="please_send_report">Nós ajude a construir um Briar melhor enviando um relatório de falhas.</string>
<string name="report_is_encrypted">Nós prometemos que o relatório é encriptado e enviado de forma segura.</string> <string name="report_is_encrypted">Nós prometemos que o relatório é criptografado e enviado de forma segura.</string>
<string name="feedback_title">Comentário</string> <string name="feedback_title">Comentário</string>
<string name="describe_crash">Descreva o que aconteceu (opcional)</string> <string name="describe_crash">Descreva o que aconteceu (opcional)</string>
<string name="enter_feedback">Digite seu comentário</string> <string name="enter_feedback">Digite seu comentário</string>
@@ -378,6 +380,7 @@
<!--Permission Requests--> <!--Permission Requests-->
<string name="permission_camera_title">Permissão da câmera</string> <string name="permission_camera_title">Permissão da câmera</string>
<string name="permission_camera_request_body">O Briar precisa acessar a câmera para pode escanear o QR code.</string> <string name="permission_camera_request_body">O Briar precisa acessar a câmera para pode escanear o QR code.</string>
<string name="permission_camera_denied_body">Você negou acesso à câmera, mas adicionar contatos requer que você use a câmera.\n\nPor favor, considere a concessão de acesso a ela.</string> <string name="permission_camera_denied_body">Você negou acesso à câmera, mas para adicionar contatos você precisa da câmera.\n\nPor favor, pense em liberar o acesso a ela.</string>
<string name="permission_camera_denied_toast">A permissão da câmera não foi concedida</string> <string name="permission_camera_denied_toast">A permissão da câmera não foi concedida</string>
<string name="qr_code">Código QR</string>
</resources> </resources>

View File

@@ -0,0 +1,359 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<!--Setup-->
<string name="setup_title">Bine ai venit la Briar</string>
<string name="setup_name_explanation">Numele dumneavoastră va fi afișat lângă orice conținut trimiteți. Nu îl veți putea schimba după crearea contului.</string>
<string name="setup_next">Următorul</string>
<string name="setup_password_intro">Alegeți o parolă</string>
<string name="setup_doze_title">Conexiuni în fundal</string>
<string name="setup_doze_intro">Pentru a primi mesaje, Briar are nevoie să stea conectat în fundal.</string>
<string name="setup_doze_button">Permite conexiuni</string>
<string name="choose_nickname">Alegeți-vă numele de utilizator</string>
<string name="choose_password">Alegeți-vă parola</string>
<string name="confirm_password">Confirmați parola</string>
<string name="name_too_long">Numele este prea lung</string>
<string name="password_too_weak">Parola este prea slabă</string>
<string name="passwords_do_not_match">Parolele nu se potrivesc</string>
<string name="create_account_button">Creează un cont</string>
<string name="more_info">Informații suplimentare</string>
<string name="don_t_ask_again">Nu mai întreba din nou</string>
<string name="setup_huawei_text">Vă rugăm să apăsați butonul de mai jos și să vă asigurați că Briar este marcat ca protejat în fereastra de \"Aplicații protejate\".</string>
<string name="setup_huawei_button">Protejează Briar</string>
<string name="setup_huawei_help">Dacă Briar nu este adăugat în lista de aplicații protejate, nu v-a fi capabil să ruleze în fundal.</string>
<string name="warning_dozed">%s nu poate rula în fundal</string>
<!--Login-->
<string name="enter_password">Introduceți parola:</string>
<string name="try_again">Parolă greșită, reîncercați</string>
<string name="sign_in_button">Autentificare</string>
<string name="forgotten_password">Am uitat parola</string>
<string name="dialog_title_lost_password">Parolă uitată</string>
<string name="startup_failed_notification_title">Briar nu a putut pornii</string>
<string name="startup_failed_notification_text">Atingeți pentru informații suplimentare</string>
<string name="startup_failed_activity_title">Eroare de pornire Briar</string>
<string name="startup_failed_data_too_new_error">Această versiune a aplicației este prea veche. Vă rugăm să actualizați la cea mai nouă versiune și să încercați din nou.</string>
<plurals name="expiry_warning">
<item quantity="one">Aceasta este o versiune de test pentru Briar. Contul dumneavoastră va expira în %d zi și nu se poate reînnoi</item>
<item quantity="few">Aceasta este o versiune de test pentru Briar. Contul dumneavoastră va expira în %d zile și nu se poate reînnoi.</item>
<item quantity="other">Aceasta este o versiune de test pentru Briar. Contul dumneavoastră va expira în %d de zile și nu se poate reînnoi.</item>
</plurals>
<string name="expiry_update">Data de expirare a versiunii de test a fost extinsă. Contul dumneavoastră va expira acum în %d zile.</string>
<string name="expiry_date_reached">Acest program a expirat.\nVă mulțumim că l-ați testat!</string>
<string name="startup_open_database">Decriptare bază de date...</string>
<string name="startup_migrate_database">Actualizare bază de date...</string>
<!--Navigation Drawer-->
<string name="nav_drawer_open_description">Deschide bara de navigare</string>
<string name="nav_drawer_close_description">Închide bara de navigare</string>
<string name="contact_list_button">Contacte</string>
<string name="groups_button">Grupuri private</string>
<string name="forums_button">Forumuri</string>
<string name="blogs_button">Blog-uri</string>
<string name="settings_button">Setări</string>
<string name="sign_out_button">Ieșire</string>
<!--Transports-->
<string name="transport_tor">Internet</string>
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--Notifications-->
<string name="ongoing_notification_title">Autentificat în Briar</string>
<string name="ongoing_notification_text">Atingeți pentru a deschide Briar</string>
<plurals name="private_message_notification_text">
<item quantity="one">Mesaj privat nou.</item>
<item quantity="few">%d mesaje private noi.</item>
<item quantity="other">%d de mesaje private noi.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Mesaj nou de grup.</item>
<item quantity="few">%d mesaje de grup noi.</item>
<item quantity="other">%d de mesaje de grup noi.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Un nou mesaj pe forum.</item>
<item quantity="few">%d mesaje noi pe forum.</item>
<item quantity="other">%d de mesaje noi pe forum.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Un nou mesaj pe blog.</item>
<item quantity="few">%d mesaje noi pe blog.</item>
<item quantity="other">%d de mesaje noi pe blog.</item>
</plurals>
<!--Misc-->
<string name="now">acum</string>
<string name="show">Arată</string>
<string name="hide">Ascunde</string>
<string name="ok">Bine</string>
<string name="cancel">Anulează</string>
<string name="got_it">Am înțeles</string>
<string name="delete">Șterge</string>
<string name="accept">Acceptă</string>
<string name="decline">Refuză</string>
<string name="options">Opțiuni</string>
<string name="online">Conectat</string>
<string name="offline">Deconectat</string>
<string name="send">Trimite</string>
<string name="allow">Permite</string>
<string name="open">Deschide</string>
<string name="no_data">Fără date</string>
<string name="ellipsis"></string>
<string name="text_too_long">Textul introdus este prea lung</string>
<string name="show_onboarding">Arata fereastra de ajutor</string>
<string name="fix">Rezolvă</string>
<string name="help">Ajutor</string>
<string name="sorry">Ne pare rău</string>
<!--Contacts and Private Conversations-->
<string name="date_no_private_messages">Fără mesaje.</string>
<string name="message_hint">Scrieți mesajul</string>
<string name="delete_contact">Șterge contactul</string>
<string name="dialog_title_delete_contact">Confirmare ștergere contact</string>
<string name="dialog_message_delete_contact">Sigur doriți să ștergeți acest contact și toate mesajele schimbate?</string>
<string name="contact_deleted_toast">Contact șters</string>
<!--Adding Contacts-->
<string name="add_contact_title">Adaugă un contact</string>
<string name="continue_button">Continuă</string>
<string name="connection_failed">Conexiune eșuată</string>
<string name="try_again_button">Încearcă din nou</string>
<string name="waiting_for_contact_to_scan">Se așteaptă scanarea și conectarea contactului\u2026</string>
<string name="exchanging_contact_details">Se face schimbul de date de contact\u2026</string>
<string name="contact_added_toast">Contact adăugat: %s</string>
<string name="contact_already_exists">Contactul %s există deja</string>
<string name="contact_exchange_failed">Schimbul de date de contactului a eșuat</string>
<string name="qr_code_invalid">Codul QR este invalid!</string>
<string name="camera_error">Eroare la camera foto</string>
<string name="connecting_to_device">Conectare la dispozitiv\u2026</string>
<string name="authenticating_with_device">Autentificare cu dispozitivul\u2026</string>
<string name="connection_aborted_local">Conexiune întreruptă! Aceasta poate însemna că cineva încearcă să interfereze conexiunea dumneavoastră</string>
<string name="connection_aborted_remote">Conexiune întreruptă de către contactul dumneavoastră! Aceasta poate însemna că cineva încearcă să interfereze conexiunea dumneavoastră</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Recomandați-vă contactele</string>
<string name="introduction_onboarding_text">Puteți să vă recomandați contactele unele altora, încât sa nu fie nevoie ca să se vadă față în față pentru a se putea conecta la Briar.</string>
<string name="introduction_activity_title">Alege un contact</string>
<string name="introduction_message_title">Recomandă contacte</string>
<string name="introduction_message_hint">Adaugă un mesaj (opțional)</string>
<string name="introduction_button">Fă o recomandare</string>
<string name="introduction_sent">Recomandarea dumneavoastră a fost trimisă.</string>
<string name="introduction_error">A apărut o eroare în procesul de recomandare.</string>
<string name="introduction_response_error">Eroare atunci când s-a răspuns la recomandare</string>
<string name="introduction_request_sent">Ați cerut recomandarea %1$s către %2$s.</string>
<string name="introduction_request_received">%1$s a cerut să vă recomande către %2$s. Doriți să adăugați pe %2$s la lista dumneavoastră de contacte?</string>
<string name="introduction_request_exists_received">%1$s a cerut să vă recomande către %2$s, dar %2$s este deja în lista dumneavoastră de contacte. Cum %1$s s-ar putea să nu știe asta, puteți totuși răspunde:</string>
<string name="introduction_request_answered_received">%1$s vă recomandă pe %2$s.</string>
<string name="introduction_response_accepted_sent">Ați acceptat recomandarea pentru %1$s.</string>
<string name="introduction_response_declined_sent">Ați refuzat recomandarea pentru %1$s.</string>
<string name="introduction_response_accepted_received">%1$s a acceptat recomandarea pentru %2$s.</string>
<string name="introduction_response_declined_received">%1$s a refuzat recomandarea pentru %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s spune că %2$s a refuzat recomandarea.</string>
<plurals name="introduction_notification_text">
<item quantity="one">Un nou contact adăugat.</item>
<item quantity="few">%d contacte noi adăugate.</item>
<item quantity="other">%d de contacte noi adăugate.</item>
</plurals>
<!--Private Groups-->
<string name="groups_created_by">Creat de %s</string>
<plurals name="messages">
<item quantity="one">%d mesaj</item>
<item quantity="few">%d mesaje</item>
<item quantity="other">%d de mesaje</item>
</plurals>
<string name="groups_group_is_empty">Acest grup este gol</string>
<string name="groups_group_is_dissolved">Acest grup a fost dizolvat</string>
<string name="groups_remove">Șterge</string>
<string name="groups_create_group_title">Creează grup privat</string>
<string name="groups_create_group_button">Creează grup</string>
<string name="groups_create_group_invitation_button">Trimite invitație</string>
<string name="groups_create_group_hint">Alegeți un nume pentru grupul dumneavoastră privat</string>
<string name="groups_invitation_sent">Invitația in grup a fost trimisă</string>
<string name="groups_message_sent">Mesaj trimis</string>
<string name="groups_member_list">Lista de membrii</string>
<string name="groups_invite_members">Invită membrii</string>
<string name="groups_member_created_you">Ați creat grupul</string>
<string name="groups_member_created">%s a creat grupul</string>
<string name="groups_member_joined_you">V-ați alăturat grupului</string>
<string name="groups_member_joined">%s s-a alăturat grupului</string>
<string name="groups_leave">Părăsește grupul</string>
<string name="groups_leave_dialog_title">Confirmare părăsire grup</string>
<string name="groups_leave_dialog_message">Sigur doriți să părăsiți acest grup?</string>
<string name="groups_dissolve">Dizolvă grupul</string>
<string name="groups_dissolve_dialog_title">Confirmă dizolvarea grupului</string>
<string name="groups_dissolve_button">Dizolvă</string>
<string name="groups_dissolved_dialog_title">Grupul a fost dizolvat</string>
<!--Private Group Invitations-->
<string name="groups_invitations_title">Invitații în grup</string>
<string name="groups_invitations_invitation_sent">Ați invitat pe %1$s să se alăture grupului \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s v-a invitat să vă alăturați grupului \"%2$s\".</string>
<string name="groups_invitations_joined">V-ați alăturat grupului</string>
<string name="groups_invitations_declined">Invitația în grup a fost refuzată</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d invitație în grup disponibilă</item>
<item quantity="few">%d invitații în grup disponibile</item>
<item quantity="other">%d de invitații în grup disponibile</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Ați acceptat invitația în grup de la %s.</string>
<string name="groups_invitations_response_declined_sent">Ați refuzat invitația în grup pentru %s.</string>
<string name="groups_invitations_response_accepted_received">%s a acceptat invitația în grup.</string>
<string name="groups_invitations_response_declined_received">%s a refuzat invitația în grup.</string>
<string name="sharing_status_groups">Doar persoana care a creat grupul poate invita noi membrii. Mai jos vedeți membrii actuali ai grupului.</string>
<!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Arată contactele</string>
<string name="groups_reveal_visible">Lista de contacte este vizibilă grupului</string>
<string name="groups_reveal_visible_revealed_by_us">Lista de contacte este vizibilă grupului (dezvăluită de dumneavoastră)</string>
<string name="groups_reveal_visible_revealed_by_contact">Lista de contacte este vizibilă grupului (dezvăluită de %s)</string>
<string name="groups_reveal_invisible">Lista de contacte nu este vizibilă grupului</string>
<!--Forums-->
<string name="create_forum_title">Creează forum</string>
<string name="choose_forum_hint">Alegeți un nume pentru forumul dumneavoastră</string>
<string name="create_forum_button">Creează forum</string>
<string name="forum_created_toast">Forum creat</string>
<string name="no_posts">Nici un mesaj</string>
<plurals name="posts">
<item quantity="one">%d mesaj</item>
<item quantity="few">%d mesaje</item>
<item quantity="other">%d de mesaje</item>
</plurals>
<string name="forum_new_entry_posted">Mesajul a fost introdus pe forum</string>
<string name="forum_new_message_hint">Mesaj nou</string>
<string name="forum_message_reply_hint">Răspuns nou</string>
<string name="btn_reply">Răspunde</string>
<string name="forum_leave">Părăsește forum</string>
<string name="dialog_title_leave_forum">Confirmare părăsire forum</string>
<string name="dialog_button_leave">Părăsește</string>
<!--Forum Sharing-->
<string name="forum_share_button">Partajează forum</string>
<string name="contacts_selected">Contacte selectate</string>
<string name="activity_share_toolbar_header">Alegeți contactele</string>
<string name="forum_shared_snackbar">Forum partajat cu contactele alese</string>
<string name="forum_share_message">Adaugă un mesaj (opțional)</string>
<string name="forum_share_error">A apărut o eroare la partajarea acestui forum.</string>
<string name="forum_invitation_received">%1$s a partajat forumul \"%2$s\" cu dumneavoastră.</string>
<string name="forum_invitation_sent">Ați partajat forumul \"%1$s\" cu %2$s.</string>
<string name="forum_invitations_title">Invitații la forum</string>
<string name="shared_by_format">Partajat de %s</string>
<string name="forum_invitation_already_sharing">Deja partajat</string>
<string name="forum_invitation_response_accepted_sent">Ați acceptat invitația la forum de la %s.</string>
<string name="forum_invitation_response_declined_sent">Ați refuzat invitația la forum pentru %s.</string>
<string name="forum_invitation_response_accepted_received">%s a acceptat invitația la forum.</string>
<string name="forum_invitation_response_declined_received">%s a refuzat invitația la forum.</string>
<string name="sharing_status">Partajare stare</string>
<string name="shared_with">Partajat cu %1$d (%2$d conectați)</string>
<plurals name="forums_shared">
<item quantity="one">%d forum partajat de contacte</item>
<item quantity="few">%d forumuri partajate de contacte</item>
<item quantity="other">%d de forumuri partajate de contacte</item>
</plurals>
<string name="nobody">Nimeni</string>
<!--Blogs-->
<string name="read_more">citește mai mult</string>
<string name="blogs_write_blog_post">Scrie mesaj pe blog</string>
<string name="blogs_publish_blog_post">Publică</string>
<string name="blogs_blog_post_created">Mesaj pe blog creat</string>
<string name="blogs_blog_post_received">O nou mesaj pe blog s-a primit</string>
<string name="blogs_blog_post_scroll_to">Derulează la</string>
<string name="blogs_remove_blog">Elimină blog</string>
<string name="blogs_remove_blog_ok">Eliminare</string>
<string name="blogs_reblog_comment_hint">Adaugă un comentariu (opțional)</string>
<string name="blogs_reblog_button">Repune mesaj</string>
<!--Blog Sharing-->
<string name="blogs_sharing_share">Partajează blog</string>
<string name="blogs_sharing_error">A apărut o eroare la partajarea acestui blog.</string>
<string name="blogs_sharing_button">Partajează blog</string>
<string name="blogs_sharing_snackbar">Blog partajat cu contactele alese</string>
<string name="blogs_sharing_response_accepted_sent">Ați acceptat invitația la blog de la %s.</string>
<string name="blogs_sharing_response_declined_sent">Ați refuzat invitația la blog de la %s.</string>
<string name="blogs_sharing_response_accepted_received">%s a acceptat invitația la blog.</string>
<string name="blogs_sharing_response_declined_received">%s a refuzat invitația la blog.</string>
<string name="blogs_sharing_invitation_received">%1$s a partajat blogul \"%2$s\" cu dumneavoastră.</string>
<string name="blogs_sharing_invitation_sent">Ați partajat blogul \"%1$s\" cu %2$s.</string>
<string name="blogs_sharing_invitations_title">Invitații la blog-uri</string>
<!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Importă flux RSS</string>
<string name="blogs_rss_feeds_import_button">Importă</string>
<string name="blogs_rss_feeds_import_hint">Introduceți URL-ul fluxului RSS</string>
<string name="blogs_rss_feeds_import_error">Ne pare rău! A apărut o eroare la importul fluxului dumneavoastră.</string>
<string name="blogs_rss_feeds_manage">Administrare fluxuri RSS</string>
<string name="blogs_rss_feeds_manage_imported">Importat:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Actualizat ultima dată:</string>
<string name="blogs_rss_remove_feed">Șterge flux</string>
<string name="blogs_rss_remove_feed_ok">Eliminare</string>
<string name="blogs_rss_feeds_manage_delete_error">Fluxul nu a putut fi șters!</string>
<string name="blogs_rss_feeds_manage_error">A apărut o eroare la încărcarea fluxurilor dumneavoastră. Vă rugăm să încercați din nou mai târziu.</string>
<!--Settings Network-->
<string name="network_settings_title">Rețele</string>
<string name="bluetooth_setting">Conectare prin Bluetooth</string>
<string name="bluetooth_setting_enabled">Atunci când contactele vă sunt în apropiere</string>
<string name="bluetooth_setting_disabled">Doar la adăugarea contactelor</string>
<string name="tor_network_setting">Conectare prin rețeaua Tor</string>
<string name="tor_network_setting_never">Niciodată</string>
<string name="tor_network_setting_wifi">Doar când se folosește Wi-Fi</string>
<string name="tor_network_setting_always">Când se folosește Wi-Fi sau date mobile</string>
<!--Settings Security and Panic-->
<string name="security_settings_title">Securitate</string>
<string name="change_password">Schimbă parola</string>
<string name="current_password">Introduceți parola curentă:</string>
<string name="choose_new_password">Alegeți-vă parola nouă:</string>
<string name="confirm_new_password">Confirmați parola nouă:</string>
<string name="password_changed">Parola a fost schimbată.</string>
<string name="panic_setting">Setare buton de panică</string>
<string name="panic_setting_title">Buton de panică</string>
<string name="panic_setting_hint">Configurați cum va reacționa Briar atunci când folosiți o aplicație de buton de panică.</string>
<string name="panic_app_setting_title">Aplicația buton de panică</string>
<string name="unknown_app">aplicație necunoscută</string>
<string name="panic_app_setting_summary">Nu a fost setată nici o aplicație</string>
<string name="panic_app_setting_none">Nici una</string>
<string name="dialog_title_connect_panic_app">Confirmare aplicație de panică</string>
<string name="dialog_message_connect_panic_app">Sigur doriți să permiteți %1$s să declanșeze acțiuni destructive pentru butonul de panică?</string>
<string name="lock_setting_title">Ieșire</string>
<string name="lock_setting_summary">Ieși din Briar dacă un buton de panică este apăsat</string>
<string name="purge_setting_title">Șterge cont</string>
<string name="uninstall_setting_title">Dezinstalare Briar</string>
<string name="uninstall_setting_summary">Aceasta necesită o confirmare manuală în timpul unui eveniment de panică</string>
<!--Settings Notifications-->
<string name="notification_settings_title">Notificări</string>
<string name="notify_private_messages_setting_title">Mesaje private</string>
<string name="notify_private_messages_setting_summary">Arată alerte pentru mesajele private</string>
<string name="notify_group_messages_setting_title">Mesaje de grup</string>
<string name="notify_group_messages_setting_summary">Arată alerte pentru mesajele de grup</string>
<string name="notify_forum_posts_setting_title">Mesaje pe forum</string>
<string name="notify_forum_posts_setting_summary">Arată alerte pentru mesajele de pe forum</string>
<string name="notify_blog_posts_setting_title">Mesaje pe blog</string>
<string name="notify_blog_posts_setting_summary">Arată alerte pentru mesajele de pe blog</string>
<string name="notify_vibration_setting">Vibrează</string>
<string name="notify_lock_screen_setting_title">Ecran de blocare</string>
<string name="notify_lock_screen_setting_summary">Arată notificări pe ecranul de blocare</string>
<string name="notify_sound_setting">Sunet</string>
<string name="notify_sound_setting_default">Sunet implicit</string>
<string name="notify_sound_setting_disabled">Nici unul</string>
<string name="choose_ringtone_title">Alegeți sunetul</string>
<string name="cannot_load_ringtone">Nu se poate încărca sunetul</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Feed-back</string>
<string name="send_feedback">Trimiteți feed-back</string>
<!--Link Warning-->
<string name="link_warning_title">Avertizare adresă</string>
<string name="link_warning_intro">Urmează să deschideți adresa următoare cu o aplicație externă</string>
<string name="link_warning_open_link">Deschide adresă</string>
<!--Crash Reporter-->
<string name="crash_report_title">Raport de erori Briar</string>
<string name="briar_crashed">Ne pare rău, Briar a întâmpinat o eroare.</string>
<string name="not_your_fault">Nu este vina dumneavoastră.</string>
<string name="please_send_report">Vă rugăm să ne ajutați să facem Briar mai bun trimițându-ne raportul de erori.</string>
<string name="report_is_encrypted">Vă promitem ca raportul este criptat și este trimis securizat.</string>
<string name="feedback_title">Feed-back</string>
<string name="describe_crash">Descrieți ce s-a întâmplat (opțional)</string>
<string name="enter_feedback">Introduceți feed-back-ul dumneavoastră</string>
<string name="optional_contact_email">Adresa de email (opțional)</string>
<string name="include_debug_report_crash">Include date anonime despre eroare</string>
<string name="include_debug_report_feedback">Include date anonime despre acest dispozitiv</string>
<string name="could_not_load_report_data">Nu s-au putut încărca datele din raport.</string>
<string name="send_report">Trimite raport</string>
<string name="close">Închide</string>
<string name="dev_report_saved">Raport salvat. V-a fi trimis data viitoare când vă conectați la Briar.</string>
<!--Sign Out-->
<string name="progress_title_logout">Ieșire din Briar...</string>
<!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">S-a detectat ceva suprapus pe ecran</string>
<string name="screen_filter_allow">Permite acestor aplicații să deseneze deasupra</string>
<!--Permission Requests-->
<string name="permission_camera_title">Permisiune de acces la camera foto</string>
<string name="permission_camera_request_body">Pentru a scana codul QR, Briar are nevoie să acceseze camera foto.</string>
<string name="permission_camera_denied_body">Ați refuzat accesul la camera foto, dar pentru a adăuga contacte este necesară folosirea camerei foto.\n\nVă rugăm să luați în considerare acordarea accesului.</string>
<string name="permission_camera_denied_toast">Permisiunea de acces la camera foto nu a fost acordată</string>
</resources>

View File

@@ -45,6 +45,8 @@
</plurals> </plurals>
<string name="expiry_update">Дата окончания тестирования была продлена. Срок действия вашей учетной записи истечет через %d дней.</string> <string name="expiry_update">Дата окончания тестирования была продлена. Срок действия вашей учетной записи истечет через %d дней.</string>
<string name="expiry_date_reached">Срок действия этого программного обеспечения истек.\nСпасибо за тестирование!</string> <string name="expiry_date_reached">Срок действия этого программного обеспечения истек.\nСпасибо за тестирование!</string>
<string name="startup_open_database">Расшифровка базы данных...</string>
<string name="startup_migrate_database">Обновление базы данных...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Открыть навигационное меню</string> <string name="nav_drawer_open_description">Открыть навигационное меню</string>
<string name="nav_drawer_close_description">Закрыть навигационное меню</string> <string name="nav_drawer_close_description">Закрыть навигационное меню</string>
@@ -223,7 +225,7 @@
<string name="choose_forum_hint">Выберите имя для вашего форума</string> <string name="choose_forum_hint">Выберите имя для вашего форума</string>
<string name="create_forum_button">Создать форум</string> <string name="create_forum_button">Создать форум</string>
<string name="forum_created_toast">Форум создан</string> <string name="forum_created_toast">Форум создан</string>
<string name="no_forum_posts">Этот форум пуст.\n\nСоздайте свою первую запись.\n\nЧувствуете одиночество? Поделитесь этим форумом с вашими контактами!</string> <string name="no_forum_posts">Нет постов для показа</string>
<string name="no_posts">Нет постов</string> <string name="no_posts">Нет постов</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d пост</item> <item quantity="one">%d пост</item>
@@ -271,7 +273,7 @@
</plurals> </plurals>
<string name="nobody">Никого</string> <string name="nobody">Никого</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Этот блог пуст.\n\nЛибо автор еще ничего не написал, либо человек, который поделился с вами этим блогом, должен выйти в Интернет, чтобы сообщения могли быть синхронизированы.</string> <string name="blogs_other_blog_empty_state">Нет постов для показа</string>
<string name="read_more">подробнее</string> <string name="read_more">подробнее</string>
<string name="blogs_write_blog_post">Написать в блоге</string> <string name="blogs_write_blog_post">Написать в блоге</string>
<string name="blogs_write_blog_post_body_hint">Введите ваше сообщение здесь</string> <string name="blogs_write_blog_post_body_hint">Введите ваше сообщение здесь</string>

View File

@@ -43,6 +43,8 @@
</plurals> </plurals>
<string name="expiry_update">Data e skadimit të periudhës së testimit është shtyrë më tej. Tani llogaria juaj do të skadojë për %d ditë.</string> <string name="expiry_update">Data e skadimit të periudhës së testimit është shtyrë më tej. Tani llogaria juaj do të skadojë për %d ditë.</string>
<string name="expiry_date_reached">Ky software ka skaduar.\nFaleminderit që e provuat!</string> <string name="expiry_date_reached">Ky software ka skaduar.\nFaleminderit që e provuat!</string>
<string name="startup_open_database">Po shfshehtëzohet Baza e të dhënave…</string>
<string name="startup_migrate_database">Po përditësohet Baza e të dhënave…</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Hap sirtarin e lëvizjeve</string> <string name="nav_drawer_open_description">Hap sirtarin e lëvizjeve</string>
<string name="nav_drawer_close_description">Mbylle sirtarin e lëvizjeve</string> <string name="nav_drawer_close_description">Mbylle sirtarin e lëvizjeve</string>
@@ -99,9 +101,9 @@
<string name="help">Ndihmë</string> <string name="help">Ndihmë</string>
<string name="sorry">Na ndjeni</string> <string name="sorry">Na ndjeni</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Duket se jeni i ri këtu dhe ende s\keni kontakte.\n\nPrekni ikonën + në krye dhe ndiqni udhëzimet që të shtoni ca shokë e miq te lista juaj.\n\nJu lutemi, mbani mend: Mundeni të shtoni kontakte të rinj vetëm duke qenë ballë për ballë me ta. që kështu të pengoni cilindo të hiqet si ju ose të lexojë në të ardhmen mesazhet tuaj.</string> <string name="no_contacts">Ska kontakte për shfaqje\n\nPrekni ikonën + që të shtoni një kontakt</string>
<string name="date_no_private_messages">S\ka mesazhe.</string> <string name="date_no_private_messages">S\ka mesazhe.</string>
<string name="no_private_messages">Kjo është pamja e bisedave.\n\nDuket se ka mungesë bisedash.\n\nQë të filloni një bisedë, thjesht prekni fushën e teksteve në fund.</string> <string name="no_private_messages">Ska mesazhe për shfaqje</string>
<string name="message_hint">Shtypni mesazhin</string> <string name="message_hint">Shtypni mesazhin</string>
<string name="delete_contact">Fshije kontaktin</string> <string name="delete_contact">Fshije kontaktin</string>
<string name="dialog_title_delete_contact">Ripohoni Fshirje Kontakti</string> <string name="dialog_title_delete_contact">Ripohoni Fshirje Kontakti</string>
@@ -149,7 +151,7 @@
<item quantity="other">U shtuan %d kontakte të rinj.</item> <item quantity="other">U shtuan %d kontakte të rinj.</item>
</plurals> </plurals>
<!--Private Groups--> <!--Private Groups-->
<string name="groups_list_empty">S\merrni pjesë në ndonjë grup.\n\nPrekni ikonën + në krye që të krijoni një grup ose kërkojuni kontakteve tuaj t\ju ftojnë në një prej grupeve të tyre.</string> <string name="groups_list_empty">S\ka grupe për shfaqje.\n\nPrekni ikonën + që të krijoni një grup ose kërkojuni kontakteve tuaj t\ju ftojnë në një prej grupeve të tyre</string>
<string name="groups_created_by">Krijuar nga %s</string> <string name="groups_created_by">Krijuar nga %s</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d mesazh</item> <item quantity="one">%d mesazh</item>
@@ -202,12 +204,12 @@
<string name="groups_reveal_visible_revealed_by_contact">Marrëdhënia e kontaktit është e dukshme për grupin (shfaqur nga %s)</string> <string name="groups_reveal_visible_revealed_by_contact">Marrëdhënia e kontaktit është e dukshme për grupin (shfaqur nga %s)</string>
<string name="groups_reveal_invisible">Marrëdhënia e kontaktit s\është e dukshme për grupin</string> <string name="groups_reveal_invisible">Marrëdhënia e kontaktit s\është e dukshme për grupin</string>
<!--Forums--> <!--Forums-->
<string name="no_forums">Ende s\keni forume.\n\nPse nuk krijoni vetë një të tillë duke prekur ikonën +krye?\n\nMund edhe t\u kërkoni kontakteve tuaj të ndajnë me ju forume.</string> <string name="no_forums">S\ka forume për shfaqje.\n\nPrekni ikonën + që të krijoni një forum ose kërkojuni kontakteve tuaj t\ju ftojnë në një prej forumeve të tyre</string>
<string name="create_forum_title">Krijoje Forumin</string> <string name="create_forum_title">Krijoje Forumin</string>
<string name="choose_forum_hint">Zgjidhni një emër për forumin tuaj</string> <string name="choose_forum_hint">Zgjidhni një emër për forumin tuaj</string>
<string name="create_forum_button">Krijoje Forumin</string> <string name="create_forum_button">Krijoje Forumin</string>
<string name="forum_created_toast">Forumi u krijua</string> <string name="forum_created_toast">Forumi u krijua</string>
<string name="no_forum_posts">Ky forum është i zbrazët.\n\nPërdorni ikonën penë që të hartoni postimin tuaj të parë.\n\nNdiheni vetëm? Ndajeni këtë forum me kontaktet tuaj!</string> <string name="no_forum_posts">S\ka postime për shfaqje</string>
<string name="no_posts">S\ka postime</string> <string name="no_posts">S\ka postime</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d postim</item> <item quantity="one">%d postim</item>
@@ -219,23 +221,23 @@
<string name="btn_reply">Përgjigju</string> <string name="btn_reply">Përgjigju</string>
<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?\n\nÇfarëdo kontaktesh me të cilët e keni ndarë këtë forum mundet të reshtin së marri përditësime.</string>
<string name="dialog_button_leave">Braktise</string> <string name="dialog_button_leave">Braktise</string>
<string name="forum_left_toast">E braktisët Forumin</string> <string name="forum_left_toast">E braktisët forumin</string>
<!--Forum Sharing--> <!--Forum Sharing-->
<string name="forum_share_button">Ndajeni Forumin Me të Tjerë</string> <string name="forum_share_button">Ndajeni Forumin Me të Tjerë</string>
<string name="contacts_selected">Kontaktet u përzgjodhën</string> <string name="contacts_selected">Kontaktet u përzgjodhën</string>
<string name="activity_share_toolbar_header">Zgjidhni Kontakte</string> <string name="activity_share_toolbar_header">Zgjidhni Kontakte</string>
<string name="no_contacts_selector">Duket se jeni i ri këtu dhe s\keni ende kontakte.\n\nJu lutemi, rikthehuni këtu pasi të keni shtuar kontaktin tuaj të parë.</string> <string name="no_contacts_selector">Ska kontakte për shfaqje\n\nJu lutemi, rikthehuni këtu pasi të shtoni një kontakt</string>
<string name="forum_shared_snackbar">Forumi u nda me kontaktet e zgjedhur</string> <string name="forum_shared_snackbar">Forumi u nda me kontaktet e zgjedhur</string>
<string name="forum_share_message">Shtoni një mesazh (në daçi)</string> <string name="forum_share_message">Shtoni një mesazh (në daçi)</string>
<string name="forum_share_error">Pati një gabim në ndarjen e këtij forumi me të tjerët.</string> <string name="forum_share_error">Pati një gabim në ndarjen e këtij forumi me të tjerët.</string>
<string name="forum_invitation_received">%1$s ndau me ju forumin \"%2$s\".</string> <string name="forum_invitation_received">%1$s ndau me ju forumin \"%2$s\".</string>
<string name="forum_invitation_sent">Ndatë me \"%2$s\" forumin %1$s.</string> <string name="forum_invitation_sent">Ndatë me \"%2$s\" forumin %1$s.</string>
<string name="forum_invitations_title">Ftesa Forumi</string> <string name="forum_invitations_title">Ftesa Forumi</string>
<string name="forum_invitation_exists">Keni pranuar tashmë një ftesë te ky forum. Pranimi i më shumë ftesave do të rrisë dhe forcojë komunikimin në forum.</string> <string name="forum_invitation_exists">Keni pranuar tashmë një ftesë për te ky forum.\n\nPranimi i më tepër ftesave do ta bëjë lidhjen tuaj me këtë forum më të shpejtë dhe më të qëndrueshme.</string>
<string name="forum_joined_toast">U bëtë Pjesë e Forumit</string> <string name="forum_joined_toast">Hytë në forum</string>
<string name="forum_declined_toast">Ftesa e Forumit u Hodh Poshtë</string> <string name="forum_declined_toast">Ftesa u hodh poshtë</string>
<string name="shared_by_format">Ndarë nga %s</string> <string name="shared_by_format">Ndarë nga %s</string>
<string name="forum_invitation_already_sharing">Ndarë tashmë</string> <string name="forum_invitation_already_sharing">Ndarë tashmë</string>
<string name="forum_invitation_response_accepted_sent">Pranuat ftesën e forumit nga %s.</string> <string name="forum_invitation_response_accepted_sent">Pranuat ftesën e forumit nga %s.</string>
@@ -251,19 +253,19 @@
</plurals> </plurals>
<string name="nobody">Askush</string> <string name="nobody">Askush</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Hëpërhë ky blog është i zbrazët.\n\nOse autori s\ka shkruar gjë ende, ose personi që ndau blogun me ju lypset të jetë në linjë, që të mund të njëkohësohen postimet.</string> <string name="blogs_other_blog_empty_state">S\ka postime për shfaqje</string>
<string name="read_more">lexoni më tepër</string> <string name="read_more">lexoni më tepër</string>
<string name="blogs_write_blog_post">Shkruani Postim Blogu</string> <string name="blogs_write_blog_post">Shkruani Postim Blogu</string>
<string name="blogs_write_blog_post_body_hint">Shtypni këtu postimin tuaj të blogut</string> <string name="blogs_write_blog_post_body_hint">Shtypni postimin tuaj të blogut</string>
<string name="blogs_publish_blog_post">Botoje</string> <string name="blogs_publish_blog_post">Botoje</string>
<string name="blogs_blog_post_created">Postimi i Blogut u Krijua</string> <string name="blogs_blog_post_created">Postimi i Blogut u Krijua</string>
<string name="blogs_blog_post_received">U morën Postime të Reja Blogu</string> <string name="blogs_blog_post_received">U morën Postime të Reja Blogu</string>
<string name="blogs_blog_post_scroll_to">Kalo Te</string> <string name="blogs_blog_post_scroll_to">Kalo Te</string>
<string name="blogs_feed_empty_state">Kjo është prurja globale e blogjeve.\n\nDuket se askush s\ka shkruar gjë në ndonjë blog.\n\nBëhuni ju i pari dhe prekni ikonën penë që të shkruani postimin e parë të një blogu të ri.</string> <string name="blogs_feed_empty_state">Ska postime për shfaqje\n\nPostimet prej kontakteve tuaja dhe blogjeve ku pajtoheni do të shfaqen këtu\n\nPrekni ikonën penë që të shkruani një postim</string>
<string name="blogs_remove_blog">hiqe Blogun</string> <string name="blogs_remove_blog">hiqe Blogun</string>
<string name="blogs_remove_blog_dialog_message">Jeni i sigurt se doni të hiqet ky blog dhe krejt postimet?\nMbani parasysh që kjo nuk do ta heqë blogun nga pajisjet e personave të tjerë.</string> <string name="blogs_remove_blog_dialog_message">Jeni i sigurt se doni të hiqet ky blog?\n\nPostimet do të hiqen nga pajisja juaj, por jo nga pajisjet e personave të tjerë.\n\nÇfarëdo kontaktesh me të cilët e keni ndarë këtë blog mund të reshtin së marri përditësime.</string>
<string name="blogs_remove_blog_ok">Hiqe Blogun</string> <string name="blogs_remove_blog_ok">Hiqe</string>
<string name="blogs_blog_removed">Blogu u Hoq</string> <string name="blogs_blog_removed">Blogu u hoq</string>
<string name="blogs_reblog_comment_hint">Shtoni një koment (në daçi)</string> <string name="blogs_reblog_comment_hint">Shtoni një koment (në daçi)</string>
<string name="blogs_reblog_button">Riblogojeni</string> <string name="blogs_reblog_button">Riblogojeni</string>
<!--Blog Sharing--> <!--Blog Sharing-->
@@ -278,8 +280,8 @@
<string name="blogs_sharing_invitation_received">%1$s ndau me ju \"%2$s\".</string> <string name="blogs_sharing_invitation_received">%1$s ndau me ju \"%2$s\".</string>
<string name="blogs_sharing_invitation_sent">Ndatë blogun \"%1$s\" me %2$s.</string> <string name="blogs_sharing_invitation_sent">Ndatë blogun \"%1$s\" me %2$s.</string>
<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 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 s\mund t\i shihni.</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 s\mund t\i 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>
@@ -291,10 +293,10 @@
<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">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_dialog_message">Jeni i sigurt se doni të hiqet kjo prurje?\n\nPostimet do të hiqen nga pajisja juaj, por jo nga pajisjet e njerëzve të tjerë.\n\nÇfarëdo kontaktesh me të cilët e keni ndarë këtë prurje mund të reshtin së marri përditësime.</string>
<string name="blogs_rss_remove_feed_ok">Hiqe Prurjen</string> <string name="blogs_rss_remove_feed_ok">Hiqe</string>
<string name="blogs_rss_feeds_manage_delete_error">S\u fshi dot prurja!</string> <string name="blogs_rss_feeds_manage_delete_error">S\u fshi dot prurja!</string>
<string name="blogs_rss_feeds_manage_empty_state">S\keni 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">Ska prurje RSS për shfaqje\n\nPrekni ikonën + që të importohet një prurje</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>
<!--Settings Network--> <!--Settings Network-->
<string name="network_settings_title">Rrjete</string> <string name="network_settings_title">Rrjete</string>
@@ -331,12 +333,16 @@
<string name="notification_settings_title">Njoftime</string> <string name="notification_settings_title">Njoftime</string>
<string name="notify_private_messages_setting_title">Mesazhe private</string> <string name="notify_private_messages_setting_title">Mesazhe private</string>
<string name="notify_private_messages_setting_summary">Shfaq sinjalizime për mesazhe private</string> <string name="notify_private_messages_setting_summary">Shfaq sinjalizime për mesazhe private</string>
<string name="notify_private_messages_setting_summary_26">Formësoni sinjalizimet për mesazhe private</string>
<string name="notify_group_messages_setting_title">Mesazhe grupi</string> <string name="notify_group_messages_setting_title">Mesazhe grupi</string>
<string name="notify_group_messages_setting_summary">Shfaq sinjalizime për mesazhe grupi</string> <string name="notify_group_messages_setting_summary">Shfaq sinjalizime për mesazhe grupi</string>
<string name="notify_group_messages_setting_summary_26">Formësoni sinjalizimet për mesazhi grupi</string>
<string name="notify_forum_posts_setting_title">Postime forumi</string> <string name="notify_forum_posts_setting_title">Postime forumi</string>
<string name="notify_forum_posts_setting_summary">Shfaq sinjalizime për postime forumi</string> <string name="notify_forum_posts_setting_summary">Shfaq sinjalizime për postime forumi</string>
<string name="notify_forum_posts_setting_summary_26">Formësoni sinjalizimet për postime forumi</string>
<string name="notify_blog_posts_setting_title">Postime blogu</string> <string name="notify_blog_posts_setting_title">Postime blogu</string>
<string name="notify_blog_posts_setting_summary">Shfaq sinjalizime për postime blogu</string> <string name="notify_blog_posts_setting_summary">Shfaq sinjalizime për postime blogu</string>
<string name="notify_blog_posts_setting_summary_26">Formësoni sinjalizime për postime blogu</string>
<string name="notify_vibration_setting">Dridhu</string> <string name="notify_vibration_setting">Dridhu</string>
<string name="notify_lock_screen_setting_title">Kyçe Ekranin</string> <string name="notify_lock_screen_setting_title">Kyçe Ekranin</string>
<string name="notify_lock_screen_setting_summary">Shfaqi njoftimet edhe me ekran të kyçur</string> <string name="notify_lock_screen_setting_summary">Shfaqi njoftimet edhe me ekran të kyçur</string>
@@ -380,4 +386,6 @@
<string name="permission_camera_request_body">Që të skanojë kodin QR, Briar-i lypset të hyjë te kamera.</string> <string name="permission_camera_request_body">Që të skanojë kodin QR, Briar-i lypset të hyjë te kamera.</string>
<string name="permission_camera_denied_body">Keni mohuar hyrjen në kamera, por shtimi i kontakteve lyp përdorimin e kamerës.\n\nJu lutemi, shihni mundësinë e akordimit të hyrjes.</string> <string name="permission_camera_denied_body">Keni mohuar hyrjen në kamera, por shtimi i kontakteve lyp përdorimin e kamerës.\n\nJu lutemi, shihni mundësinë e akordimit të hyrjes.</string>
<string name="permission_camera_denied_toast">S\u dhanë leje mbi kamerën</string> <string name="permission_camera_denied_toast">S\u dhanë leje mbi kamerën</string>
<string name="qr_code">Kod QR</string>
<string name="show_qr_code_fullscreen">Shfaqe kodin QR sa tërë ekrani</string>
</resources> </resources>

View File

@@ -108,11 +108,13 @@
<string name="dialog_button_leave">Tillbaka</string> <string name="dialog_button_leave">Tillbaka</string>
<!--Forum Sharing--> <!--Forum Sharing-->
<!--Blogs--> <!--Blogs-->
<string name="blogs_remove_blog_ok">&amp;Ta bort</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import_button">Importera</string> <string name="blogs_rss_feeds_import_button">Importera</string>
<string name="blogs_rss_feeds_manage_author">Författare:</string> <string name="blogs_rss_feeds_manage_author">Författare:</string>
<string name="blogs_rss_feeds_manage_updated">Senast uppdaterad:</string> <string name="blogs_rss_feeds_manage_updated">Senast uppdaterad:</string>
<string name="blogs_rss_remove_feed_ok">&amp;Ta bort</string>
<!--Settings Network--> <!--Settings Network-->
<string name="network_settings_title">Nätverk</string> <string name="network_settings_title">Nätverk</string>
<string name="bluetooth_setting">Anslut via Bluetooth</string> <string name="bluetooth_setting">Anslut via Bluetooth</string>

View File

@@ -42,6 +42,8 @@
</plurals> </plurals>
<string name="expiry_update">测试到期时间延长,您的帐户将在 %d 天后过期。</string> <string name="expiry_update">测试到期时间延长,您的帐户将在 %d 天后过期。</string>
<string name="expiry_date_reached">本软件已过期。\n感谢您的测试</string> <string name="expiry_date_reached">本软件已过期。\n感谢您的测试</string>
<string name="startup_open_database">正在解密数据库……</string>
<string name="startup_migrate_database">正在升级数据库……</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">打开抽屉式导航栏</string> <string name="nav_drawer_open_description">打开抽屉式导航栏</string>
<string name="nav_drawer_close_description">关闭抽屉式导航栏</string> <string name="nav_drawer_close_description">关闭抽屉式导航栏</string>
@@ -199,7 +201,7 @@
<string name="choose_forum_hint">为论坛命名</string> <string name="choose_forum_hint">为论坛命名</string>
<string name="create_forum_button">创建论坛</string> <string name="create_forum_button">创建论坛</string>
<string name="forum_created_toast">论坛已创建</string> <string name="forum_created_toast">论坛已创建</string>
<string name="no_forum_posts">该论坛是空的\n\n使用上方的钢笔按钮创建第一篇博文\n\n感觉很孤单将此论坛分享给您的联系人吧</string> <string name="no_forum_posts">尚无帖子可供展示</string>
<string name="no_posts">无帖子</string> <string name="no_posts">无帖子</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="other">%d 条帖子</item> <item quantity="other">%d 条帖子</item>
@@ -241,7 +243,7 @@
</plurals> </plurals>
<string name="nobody">没有人</string> <string name="nobody">没有人</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">这个博客目前是空的。\n\n这有可能是因为作者尚未发布任何内容或者是分享该博客给您的人需要上线以使博文同步。</string> <string name="blogs_other_blog_empty_state">尚无帖子可供展示</string>
<string name="read_more">阅读更多</string> <string name="read_more">阅读更多</string>
<string name="blogs_write_blog_post">写博文</string> <string name="blogs_write_blog_post">写博文</string>
<string name="blogs_write_blog_post_body_hint">在此输入博文</string> <string name="blogs_write_blog_post_body_hint">在此输入博文</string>

View File

@@ -36,8 +36,8 @@
<string name="startup_failed_notification_title">Briar could not start</string> <string name="startup_failed_notification_title">Briar could not start</string>
<string name="startup_failed_notification_text">Tap for more information.</string> <string name="startup_failed_notification_text">Tap for more information.</string>
<string name="startup_failed_activity_title">Briar Startup Failure</string> <string name="startup_failed_activity_title">Briar Startup Failure</string>
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar and set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string> <string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or delete your old account by choosing \'I have forgotten my password\' at the password prompt.</string> <string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string> <string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string>
<string name="startup_failed_service_error">Briar was unable to start a required plugin. Reinstalling Briar usually solves this problem. However, please note that you will then lose your account and all data associated with it since Briar is not using central servers to store your data on.</string> <string name="startup_failed_service_error">Briar was unable to start a required plugin. Reinstalling Briar usually solves this problem. However, please note that you will then lose your account and all data associated with it since Briar is not using central servers to store your data on.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
@@ -108,9 +108,9 @@
<string name="sorry">Sorry</string> <string name="sorry">Sorry</string>
<!-- Contacts and Private Conversations--> <!-- Contacts and Private Conversations-->
<string name="no_contacts">It seems that you are new here and have no contacts yet.\n\nTap the + icon at the top and follow the instructions to add some friends to your list.\n\nPlease remember: You can only add new contacts face-to-face to prevent anyone from impersonating you or reading your messages in the future.</string> <string name="no_contacts">No contacts to show\n\nTap the + icon to add a contact</string>
<string name="date_no_private_messages">No messages.</string> <string name="date_no_private_messages">No messages.</string>
<string name="no_private_messages">This is the conversation view.\n\nThere seems to be a lack of conversation.\n\nJust tap the input field at the bottom to start a conversation.</string> <string name="no_private_messages">No messages to show</string>
<string name="message_hint">Type message</string> <string name="message_hint">Type message</string>
<string name="delete_contact">Delete contact</string> <string name="delete_contact">Delete contact</string>
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string> <string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
@@ -160,7 +160,7 @@
</plurals> </plurals>
<!-- Private Groups --> <!-- Private Groups -->
<string name="groups_list_empty">You are not participating in any groups.\n\nTap the + icon at the top to create a group yourself or ask your contacts to get invited into one of their groups.</string> <string name="groups_list_empty">No groups to show\n\nTap the + icon to create a group, or ask your contacts to share groups with you</string>
<string name="groups_created_by">Created by %s</string> <string name="groups_created_by">Created by %s</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d message</item> <item quantity="one">%d message</item>
@@ -216,12 +216,12 @@
<string name="groups_reveal_invisible">Contact relationship is not visible to the group</string> <string name="groups_reveal_invisible">Contact relationship is not visible to the group</string>
<!-- Forums --> <!-- Forums -->
<string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string> <string name="no_forums">No forums to show\n\nTap the + icon to create a forum, or ask your contacts to share forums with you</string>
<string name="create_forum_title">Create Forum</string> <string name="create_forum_title">Create Forum</string>
<string name="choose_forum_hint">Choose a name for your forum</string> <string name="choose_forum_hint">Choose a name for your forum</string>
<string name="create_forum_button">Create Forum</string> <string name="create_forum_button">Create Forum</string>
<string name="forum_created_toast">Forum created</string> <string name="forum_created_toast">Forum created</string>
<string name="no_forum_posts">This forum is empty.\n\nUse the pen icon at the top to compose the first post.\n\nFeeling lonely here? Share this forum with more of your contacts!</string> <string name="no_forum_posts">No posts to show</string>
<string name="no_posts">No posts</string> <string name="no_posts">No posts</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d post</item> <item quantity="one">%d post</item>
@@ -233,24 +233,24 @@
<string name="btn_reply">Reply</string> <string name="btn_reply">Reply</string>
<string name="forum_leave">Leave Forum</string> <string name="forum_leave">Leave Forum</string>
<string name="dialog_title_leave_forum">Confirm Leaving Forum</string> <string name="dialog_title_leave_forum">Confirm Leaving Forum</string>
<string name="dialog_message_leave_forum">Are you sure that you want to leave this forum? Contacts you have shared this forum with might get cut off from receiving updates for this forum.</string> <string name="dialog_message_leave_forum">Are you sure that you want to leave this forum?\n\nAny contacts you\'ve shared this forum with might stop receiving updates.</string>
<string name="dialog_button_leave">Leave</string> <string name="dialog_button_leave">Leave</string>
<string name="forum_left_toast">Left Forum</string> <string name="forum_left_toast">Left forum</string>
<!-- Forum Sharing --> <!-- Forum Sharing -->
<string name="forum_share_button">Share Forum</string> <string name="forum_share_button">Share Forum</string>
<string name="contacts_selected">Contacts selected</string> <string name="contacts_selected">Contacts selected</string>
<string name="activity_share_toolbar_header">Choose Contacts</string> <string name="activity_share_toolbar_header">Choose Contacts</string>
<string name="no_contacts_selector">It seems that you are new here and have no contacts yet.\n\nPlease come back here after you added your first contact.</string> <string name="no_contacts_selector">No contacts to show\n\nPlease come back here after adding a contact</string>
<string name="forum_shared_snackbar">Forum shared with chosen contacts</string> <string name="forum_shared_snackbar">Forum shared with chosen contacts</string>
<string name="forum_share_message">Add a message (optional)</string> <string name="forum_share_message">Add a message (optional)</string>
<string name="forum_share_error">There was an error sharing this forum.</string> <string name="forum_share_error">There was an error sharing this forum.</string>
<string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string> <string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string>
<string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string> <string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string>
<string name="forum_invitations_title">Forum Invitations</string> <string name="forum_invitations_title">Forum Invitations</string>
<string name="forum_invitation_exists">You accepted an invitation to this forum already. Accepting more invitations will grow and strengthen the communication in the forum.</string> <string name="forum_invitation_exists">You accepted an invitation to this forum already.\n\nAccepting more invitations will make your connection to the forum faster and more reliable.</string>
<string name="forum_joined_toast">Joined Forum</string> <string name="forum_joined_toast">Joined forum</string>
<string name="forum_declined_toast">Forum Invitation Declined</string> <string name="forum_declined_toast">Invitation declined</string>
<string name="shared_by_format">Shared by %s</string> <string name="shared_by_format">Shared by %s</string>
<string name="forum_invitation_already_sharing">Already sharing</string> <string name="forum_invitation_already_sharing">Already sharing</string>
<string name="forum_invitation_response_accepted_sent">You accepted the forum invitation from %s.</string> <string name="forum_invitation_response_accepted_sent">You accepted the forum invitation from %s.</string>
@@ -268,19 +268,19 @@
<string name="nobody">Nobody</string> <string name="nobody">Nobody</string>
<!-- Blogs --> <!-- Blogs -->
<string name="blogs_other_blog_empty_state">This blog is currently empty.\n\nEither the author hasn\'t written anything yet, or the person who shared this blog with you needs to come online, so posts can be synchronized.</string> <string name="blogs_other_blog_empty_state">No posts to show</string>
<string name="read_more">read more</string> <string name="read_more">read more</string>
<string name="blogs_write_blog_post">Write Blog Post</string> <string name="blogs_write_blog_post">Write Blog Post</string>
<string name="blogs_write_blog_post_body_hint">Type your blog post here</string> <string name="blogs_write_blog_post_body_hint">Type your blog post</string>
<string name="blogs_publish_blog_post">Publish</string> <string name="blogs_publish_blog_post">Publish</string>
<string name="blogs_blog_post_created">Blog Post Created</string> <string name="blogs_blog_post_created">Blog Post Created</string>
<string name="blogs_blog_post_received">New Blog Post Received</string> <string name="blogs_blog_post_received">New Blog Post Received</string>
<string name="blogs_blog_post_scroll_to">Scroll To</string> <string name="blogs_blog_post_scroll_to">Scroll To</string>
<string name="blogs_feed_empty_state">This is the global blog feed.\n\nIt looks like nobody blogged anything, yet.\n\nBe the first and tap the pen icon to write a new blog post.</string> <string name="blogs_feed_empty_state">No posts to show\n\nPosts from your contacts and blogs you subscribe to will appear here\n\nTap the pen icon to write a post</string>
<string name="blogs_remove_blog">Remove Blog</string> <string name="blogs_remove_blog">Remove Blog</string>
<string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog and all posts?\nNote that this will not remove the blog from other people\'s devices.</string> <string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog?\n\nPosts will be removed from your device but not from other people\'s devices.\n\nAny contacts you\'ve shared this blog with might stop receiving updates.</string>
<string name="blogs_remove_blog_ok">Remove Blog</string> <string name="blogs_remove_blog_ok">Remove</string>
<string name="blogs_blog_removed">Blog Removed</string> <string name="blogs_blog_removed">Blog removed</string>
<string name="blogs_reblog_comment_hint">Add a comment (optional)</string> <string name="blogs_reblog_comment_hint">Add a comment (optional)</string>
<string name="blogs_reblog_button">Reblog</string> <string name="blogs_reblog_button">Reblog</string>
@@ -296,8 +296,8 @@
<string name="blogs_sharing_invitation_received">%1$s has shared the blog \"%2$s\" with you.</string> <string name="blogs_sharing_invitation_received">%1$s has shared the blog \"%2$s\" with you.</string>
<string name="blogs_sharing_invitation_sent">You have shared the blog \"%1$s\" with %2$s.</string> <string name="blogs_sharing_invitation_sent">You have shared the blog \"%1$s\" with %2$s.</string>
<string name="blogs_sharing_invitations_title">Blog Invitations</string> <string name="blogs_sharing_invitations_title">Blog Invitations</string>
<string name="blogs_sharing_joined_toast">Subscribed to Blog</string> <string name="blogs_sharing_joined_toast">Subscribed to blog</string>
<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string> <string name="blogs_sharing_declined_toast">Invitation declined</string>
<string name="sharing_status_blog">Anyone who subscribes to a blog can share it with their contacts. You are sharing this blog with the following contacts. There may also be other subscribers who you can\'t see.</string> <string name="sharing_status_blog">Anyone who subscribes to a blog can share it with their contacts. You are sharing this blog with the following contacts. There may also be other subscribers who you can\'t see.</string>
<!-- RSS Feeds --> <!-- RSS Feeds -->
@@ -310,10 +310,10 @@
<string name="blogs_rss_feeds_manage_author">Author:</string> <string name="blogs_rss_feeds_manage_author">Author:</string>
<string name="blogs_rss_feeds_manage_updated">Last Updated:</string> <string name="blogs_rss_feeds_manage_updated">Last Updated:</string>
<string name="blogs_rss_remove_feed">Remove Feed</string> <string name="blogs_rss_remove_feed">Remove Feed</string>
<string name="blogs_rss_remove_feed_dialog_message">Are you sure you want to remove this feed and all its posts?\nAny posts you have shared will not be removed from other people\'s devices.</string> <string name="blogs_rss_remove_feed_dialog_message">Are you sure that you want to remove this feed?\n\nPosts will be removed from your device but not from other people\'s devices.\n\nAny contacts you\'ve shared this feed with might stop receiving updates.</string>
<string name="blogs_rss_remove_feed_ok">Remove Feed</string> <string name="blogs_rss_remove_feed_ok">Remove</string>
<string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string> <string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string>
<string name="blogs_rss_feeds_manage_empty_state">You haven\'t imported any RSS feeds.\n\nWhy don\'t you click the plus in the top right screen corner to add your first?</string> <string name="blogs_rss_feeds_manage_empty_state">No RSS feeds to show\n\nTap the + icon to import a feed</string>
<string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string> <string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string>
<!-- Settings Network --> <!-- Settings Network -->
@@ -353,12 +353,16 @@
<string name="notification_settings_title">Notifications</string> <string name="notification_settings_title">Notifications</string>
<string name="notify_private_messages_setting_title">Private messages</string> <string name="notify_private_messages_setting_title">Private messages</string>
<string name="notify_private_messages_setting_summary">Show alerts for private messages</string> <string name="notify_private_messages_setting_summary">Show alerts for private messages</string>
<string name="notify_private_messages_setting_summary_26">Configure alerts for private messages</string>
<string name="notify_group_messages_setting_title">Group messages</string> <string name="notify_group_messages_setting_title">Group messages</string>
<string name="notify_group_messages_setting_summary">Show alerts for group messages</string> <string name="notify_group_messages_setting_summary">Show alerts for group messages</string>
<string name="notify_group_messages_setting_summary_26">Configure alerts for group messages</string>
<string name="notify_forum_posts_setting_title">Forum posts</string> <string name="notify_forum_posts_setting_title">Forum posts</string>
<string name="notify_forum_posts_setting_summary">Show alerts for forum posts</string> <string name="notify_forum_posts_setting_summary">Show alerts for forum posts</string>
<string name="notify_forum_posts_setting_summary_26">Configure alerts for forum posts</string>
<string name="notify_blog_posts_setting_title">Blog posts</string> <string name="notify_blog_posts_setting_title">Blog posts</string>
<string name="notify_blog_posts_setting_summary">Show alerts for blog posts</string> <string name="notify_blog_posts_setting_summary">Show alerts for blog posts</string>
<string name="notify_blog_posts_setting_summary_26">Configure alerts for blog posts</string>
<string name="notify_vibration_setting">Vibrate</string> <string name="notify_vibration_setting">Vibrate</string>
<string name="notify_lock_screen_setting_title">Lock Screen</string> <string name="notify_lock_screen_setting_title">Lock Screen</string>
<string name="notify_lock_screen_setting_summary">Show notifications on the lock screen</string> <string name="notify_lock_screen_setting_summary">Show notifications on the lock screen</string>
@@ -408,5 +412,10 @@
<string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string> <string name="permission_camera_request_body">To scan the QR code, Briar needs access to the camera.</string>
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string> <string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
<string name="permission_camera_denied_toast">Camera permission was not granted</string> <string name="permission_camera_denied_toast">Camera permission was not granted</string>
<string name="qr_code">QR code</string>
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
<!-- Low Memory Notification -->
<string name="low_memory_shutdown_notification_title">Signed out of Briar</string>
<string name="low_memory_shutdown_notification_text">Signed out due to lack of memory.</string>
</resources> </resources>