mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
50 Commits
beta-0.16.
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f50bbdaf | ||
|
|
3eed7df1a4 | ||
|
|
f7af0dc3b0 | ||
|
|
fbaf446570 | ||
|
|
fb6d962131 | ||
|
|
d007de48ac | ||
|
|
95a08eed5c | ||
|
|
040894b205 | ||
|
|
41d3bd4f19 | ||
|
|
347868684c | ||
|
|
1038a3532b | ||
|
|
4e6d514a0d | ||
|
|
f178ce807f | ||
|
|
a2c827ef24 | ||
|
|
9496148182 | ||
|
|
bb27ca186a | ||
|
|
be38431e03 | ||
|
|
e314b39661 | ||
|
|
4aa8d0b6c0 | ||
|
|
6220a8c00e | ||
|
|
dcd9b0a637 | ||
|
|
94b17caf0f | ||
|
|
fce8d9fa9f | ||
|
|
f4c798a2da | ||
|
|
accef2e51b | ||
|
|
34b4c35f44 | ||
|
|
9b253fc965 | ||
|
|
4d97cad842 | ||
|
|
ba99f58559 | ||
|
|
edbb0a3c13 | ||
|
|
fdbcc0736c | ||
|
|
f4722b2a67 | ||
|
|
d316e126a9 | ||
|
|
20bd72844c | ||
|
|
02c88eb907 | ||
|
|
1afc0d4fda | ||
|
|
5a7f39df4d | ||
|
|
e30b190209 | ||
|
|
31d35a7dd8 | ||
|
|
53f85d4b71 | ||
|
|
54b0bb6084 | ||
|
|
f2cfca1460 | ||
|
|
0cbdc47649 | ||
|
|
536853343e | ||
|
|
93de06ed0c | ||
|
|
d7f5da305a | ||
|
|
7c48bc5a00 | ||
|
|
9493e242cc | ||
|
|
3e28323ab1 | ||
|
|
c7e496230b |
@@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1619
|
||||
versionName "0.16.19"
|
||||
versionCode 1620
|
||||
versionName "0.16.20"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class AndroidPluginModule {
|
||||
appContext, locationUtils, reporter, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||
backoffFactory, appContext);
|
||||
scheduler, backoffFactory, appContext);
|
||||
Collection<DuplexPluginFactory> duplex =
|
||||
Arrays.asList(bluetooth, tor, lan);
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -5,37 +5,84 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
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.plugin.Backoff;
|
||||
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.ScheduledExecutorService;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
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.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
|
||||
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 =
|
||||
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 ConnectivityManager connectivityManager;
|
||||
@Nullable
|
||||
private final WifiManager wifiManager;
|
||||
|
||||
@Nullable
|
||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||
private volatile SocketFactory socketFactory;
|
||||
|
||||
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
|
||||
Context appContext, DuplexPluginCallback callback, int maxLatency,
|
||||
int maxIdleTime) {
|
||||
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||
Backoff backoff, Context appContext, DuplexPluginCallback callback,
|
||||
int maxLatency, int maxIdleTime) {
|
||||
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
||||
this.scheduler = scheduler;
|
||||
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
|
||||
@@ -44,7 +91,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
running = true;
|
||||
// Register to receive network status events
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -56,21 +105,92 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
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 {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (!running) return;
|
||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
||||
ConnectivityManager cm = (ConnectivityManager) o;
|
||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
|
||||
LOG.info("Connected to Wi-Fi");
|
||||
if (socket == null || socket.isClosed()) bind();
|
||||
if (isApEnabledEvent(i)) {
|
||||
// The state change may be broadcast before the AP address is
|
||||
// visible, so delay handling the event
|
||||
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
|
||||
} else {
|
||||
LOG.info("Not connected to Wi-Fi");
|
||||
tryToClose(socket);
|
||||
handleConnectivityChange();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
@@ -27,12 +28,15 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
private static final double BACKOFF_BASE = 1.2;
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final BackoffFactory backoffFactory;
|
||||
private final Context appContext;
|
||||
|
||||
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
||||
BackoffFactory backoffFactory, Context appContext) {
|
||||
ScheduledExecutorService scheduler, BackoffFactory backoffFactory,
|
||||
Context appContext) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.scheduler = scheduler;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
@@ -51,7 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
|
||||
callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
return new AndroidLanTcpPlugin(ioExecutor, scheduler, backoff,
|
||||
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.os.PowerManager;
|
||||
import net.freehaven.tor.control.EventHandler;
|
||||
import net.freehaven.tor.control.TorControlConnection;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
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.atomic.AtomicBoolean;
|
||||
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.regex.Pattern;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@@ -111,7 +110,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TorPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final Executor ioExecutor, connectionStatusExecutor;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Context appContext;
|
||||
private final LocationUtils locationUtils;
|
||||
@@ -125,7 +124,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final PowerManager.WakeLock wakeLock;
|
||||
private final Lock connectionStatusLock;
|
||||
private final AtomicReference<Future<?>> connectivityCheck =
|
||||
new AtomicReference<>();
|
||||
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.
|
||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||
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
|
||||
@@ -697,56 +697,46 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private void updateConnectionStatus() {
|
||||
ioExecutor.execute(() -> {
|
||||
connectionStatusExecutor.execute(() -> {
|
||||
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 {
|
||||
connectionStatusLock.lock();
|
||||
updateConnectionStatusLocked();
|
||||
} finally {
|
||||
connectionStatusLock.unlock();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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() {
|
||||
Future<?> newConnectivityCheck =
|
||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||
|
||||
@@ -259,31 +259,30 @@ public interface DatabaseComponent {
|
||||
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that need to be validated by the given
|
||||
* client.
|
||||
* Returns the IDs of any messages that need to be validated.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
|
||||
Collection<MessageId> getMessagesToValidate(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that are valid but pending delivery due
|
||||
* to dependencies on other messages for the given client.
|
||||
* Returns the IDs of any messages that are pending delivery due to
|
||||
* dependencies on other messages.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
|
||||
Collection<MessageId> getPendingMessages(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages from the given client
|
||||
* that have a shared dependent, but are still not shared themselves.
|
||||
* Returns the IDs of any messages that have shared dependents but have
|
||||
* not yet been shared themselves.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToShare(Transaction txn,
|
||||
ClientId c) throws DbException;
|
||||
Collection<MessageId> getMessagesToShare(Transaction txn)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the message with the given ID, in serialised form, or null if
|
||||
|
||||
@@ -97,9 +97,12 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* 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)
|
||||
throws DbException;
|
||||
void addMessage(T txn, Message m, State state, boolean shared,
|
||||
@Nullable ContactId sender) throws DbException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -280,7 +273,7 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
|
||||
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -431,31 +424,27 @@ interface Database<T> {
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that need to be validated by the given
|
||||
* client.
|
||||
* Returns the IDs of any messages that need to be validated.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
|
||||
throws DbException;
|
||||
Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages that are still pending due to
|
||||
* dependencies to other messages for the given client.
|
||||
* Returns the IDs of any messages that are pending delivery due to
|
||||
* dependencies on other messages.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getPendingMessages(T txn, ClientId c)
|
||||
throws DbException;
|
||||
Collection<MessageId> getPendingMessages(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the IDs of any messages from the given client
|
||||
* that have a shared dependent, but are still not shared themselves.
|
||||
* Returns the IDs of any messages that have a shared dependent but have
|
||||
* not yet been shared themselves.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||
throws DbException;
|
||||
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* contact.
|
||||
@@ -598,12 +580,6 @@ interface Database<T> {
|
||||
void removeOfferedMessages(T txn, ContactId c,
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -215,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsGroup(txn, m.getGroupId()))
|
||||
throw new NoSuchGroupException();
|
||||
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 MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
@@ -224,16 +224,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
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
|
||||
public void addTransport(Transaction transaction, TransportId t,
|
||||
int maxLatency) throws DbException {
|
||||
@@ -465,24 +455,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToValidate(txn, c);
|
||||
return db.getMessagesToValidate(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getPendingMessages(Transaction transaction,
|
||||
ClientId c) throws DbException {
|
||||
public Collection<MessageId> getPendingMessages(Transaction transaction)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getPendingMessages(txn, c);
|
||||
return db.getPendingMessages(txn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(
|
||||
Transaction transaction, ClientId c) throws DbException {
|
||||
public Collection<MessageId> getMessagesToShare(Transaction transaction)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getMessagesToShare(txn, c);
|
||||
return db.getMessagesToShare(txn);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -583,7 +573,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
@@ -682,7 +672,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.raiseSeenFlag(txn, c, m.getId());
|
||||
db.raiseAckFlag(txn, c, m.getId());
|
||||
} else {
|
||||
addMessage(txn, m, UNKNOWN, false, c);
|
||||
db.addMessage(txn, m, UNKNOWN, false, c);
|
||||
transaction.attach(new MessageAddedEvent(m, c));
|
||||
}
|
||||
transaction.attach(new MessageToAckEvent(c));
|
||||
@@ -750,7 +740,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
GroupId id = g.getId();
|
||||
if (!db.containsGroup(txn, id))
|
||||
throw new NoSuchGroupException();
|
||||
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
|
||||
Collection<ContactId> affected =
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
@@ -820,19 +811,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchGroupException();
|
||||
Visibility old = db.getGroupVisibility(txn, c, g);
|
||||
if (old == v) return;
|
||||
if (old == INVISIBLE) {
|
||||
db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
for (MessageId m : db.getMessageIds(txn, g)) {
|
||||
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);
|
||||
}
|
||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
List<ContactId> affected = Collections.singletonList(c);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -72,7 +73,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 31;
|
||||
static final int CODE_SCHEMA_VERSION = 32;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -188,6 +189,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE TABLE statuses"
|
||||
+ " (messageId HASH 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,"
|
||||
+ " seen BOOLEAN NOT NULL,"
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
@@ -199,6 +207,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_TRANSPORTS =
|
||||
@@ -252,6 +263,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " 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 =
|
||||
Logger.getLogger(JdbcDatabase.class.getName());
|
||||
|
||||
@@ -343,7 +362,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
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)
|
||||
@@ -401,6 +420,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
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();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
@@ -578,7 +599,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addGroupVisibility(Connection txn, ContactId c, GroupId g,
|
||||
boolean shared) throws DbException {
|
||||
boolean groupShared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groupVisibilities"
|
||||
@@ -587,16 +608,50 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBoolean(3, shared);
|
||||
ps.setBoolean(3, groupShared);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Create a status row for each message in the group
|
||||
addStatus(txn, c, g, groupShared);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
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
|
||||
public void addLocalAuthor(Connection txn, LocalAuthor a)
|
||||
throws DbException {
|
||||
@@ -622,7 +677,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addMessage(Connection txn, Message m, State state,
|
||||
boolean shared) throws DbException {
|
||||
boolean messageShared, @Nullable ContactId sender)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
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.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, shared);
|
||||
ps.setBoolean(5, messageShared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
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) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -677,19 +744,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack,
|
||||
boolean seen) throws DbException {
|
||||
private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g,
|
||||
long timestamp, int length, State state, boolean groupShared,
|
||||
boolean messageShared, boolean deleted, boolean seen)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
|
||||
+ " seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, FALSE, 0, 0)";
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setBoolean(3, ack);
|
||||
ps.setBoolean(4, seen);
|
||||
ps.setBytes(3, g.getBytes());
|
||||
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();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -941,12 +1017,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT NULL FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?"
|
||||
+ " AND m.shared = TRUE";
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?"
|
||||
+ " AND messageShared = TRUE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -998,6 +1071,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (affected < 0) throw new DbStateException();
|
||||
if (affected > 1) throw new DbStateException();
|
||||
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) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -1220,18 +1300,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getGroupVisibility(Connection txn, GroupId g)
|
||||
public Map<ContactId, Boolean> getGroupVisibility(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT contactId FROM groupVisibilities"
|
||||
String sql = "SELECT contactId, shared FROM groupVisibilities"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> visible = new ArrayList<>();
|
||||
while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
|
||||
Map<ContactId, Boolean> visible = new HashMap<>();
|
||||
while (rs.next())
|
||||
visible.put(new ContactId(rs.getInt(1)), rs.getBoolean(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return visible;
|
||||
@@ -1509,12 +1590,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId, txCount > 0, seen"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " WHERE groupId = ?"
|
||||
+ " AND contactId = ?";
|
||||
String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
|
||||
+ " WHERE groupId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1537,15 +1614,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageStatus getMessageStatus(Connection txn,
|
||||
ContactId c, MessageId m) throws DbException {
|
||||
public MessageStatus getMessageStatus(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT txCount > 0, seen"
|
||||
+ " FROM statuses"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?";
|
||||
String sql = "SELECT txCount > 0, seen FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1687,14 +1762,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " 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"
|
||||
String sql = "SELECT messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
@@ -1748,14 +1819,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " 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"
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -1783,28 +1850,26 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToValidate(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, UNKNOWN);
|
||||
public Collection<MessageId> getMessagesToValidate(Connection txn)
|
||||
throws DbException {
|
||||
return getMessagesInState(txn, UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getPendingMessages(Connection txn,
|
||||
ClientId c) throws DbException {
|
||||
return getMessagesInState(txn, c, PENDING);
|
||||
public Collection<MessageId> getPendingMessages(Connection txn)
|
||||
throws DbException {
|
||||
return getMessagesInState(txn, PENDING);
|
||||
}
|
||||
|
||||
private Collection<MessageId> getMessagesInState(Connection txn, ClientId c,
|
||||
private Collection<MessageId> getMessagesInState(Connection txn,
|
||||
State state) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId FROM messages AS m"
|
||||
+ " JOIN groups AS g ON m.groupId = g.groupId"
|
||||
+ " WHERE state = ? AND clientId = ? AND raw IS NOT NULL";
|
||||
String sql = "SELECT messageId FROM messages"
|
||||
+ " WHERE state = ? AND raw IS NOT NULL";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setString(2, c.getString());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
@@ -1819,8 +1884,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(
|
||||
Connection txn, ClientId c) throws DbException {
|
||||
public Collection<MessageId> getMessagesToShare(Connection txn)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1829,12 +1894,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " ON m.messageId = d.dependencyId"
|
||||
+ " JOIN messages AS m1"
|
||||
+ " ON d.messageId = m1.messageId"
|
||||
+ " JOIN groups AS g"
|
||||
+ " ON m.groupId = g.groupId"
|
||||
+ " WHERE m.shared = FALSE AND m1.shared = TRUE"
|
||||
+ " AND g.clientId = ?";
|
||||
+ " WHERE m.state = ?"
|
||||
+ " AND m.shared = FALSE AND m1.shared = TRUE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setString(1, c.getString());
|
||||
ps.setInt(1, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
while (rs.next()) ids.add(new MessageId(rs.getBytes(1)));
|
||||
@@ -1854,15 +1917,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT expiry FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " 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"
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " ORDER BY expiry LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
@@ -1914,14 +1972,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " 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"
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -2397,6 +2451,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Remove status rows for the messages in the group
|
||||
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2436,8 +2492,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
private boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
@@ -2481,16 +2536,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
private void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "DELETE FROM statuses"
|
||||
+ " WHERE contactId = ? AND messageId = ?";
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -2586,6 +2640,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
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) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2604,6 +2668,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
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) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2630,6 +2702,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
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) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,10 +241,11 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||
Socket s = createSocket();
|
||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||
s.connect(remote);
|
||||
s.setSoTimeout(socketTimeout);
|
||||
if (LOG.isLoggable(INFO))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.PoliteExecutor;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
@@ -47,7 +48,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(TcpPlugin.class.getName());
|
||||
|
||||
protected final Executor ioExecutor;
|
||||
protected final Executor ioExecutor, bindExecutor;
|
||||
protected final Backoff backoff;
|
||||
protected final DuplexPluginCallback callback;
|
||||
protected final int maxLatency, maxIdleTime, socketTimeout;
|
||||
@@ -90,6 +91,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||
socketTimeout = Integer.MAX_VALUE;
|
||||
else socketTimeout = maxIdleTime * 2;
|
||||
// Don't execute more than one bind operation at a time
|
||||
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,8 +113,9 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
|
||||
protected void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
bindExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
if (socket != null && !socket.isClosed()) return;
|
||||
ServerSocket ss = null;
|
||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||
try {
|
||||
@@ -243,10 +247,11 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||
Socket s = createSocket();
|
||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
||||
s.connect(remote);
|
||||
s.setSoTimeout(socketTimeout);
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -261,6 +266,10 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Socket createSocket() throws IOException {
|
||||
return new Socket();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
InetSocketAddress parseSocketAddress(String ipPort) {
|
||||
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
||||
|
||||
@@ -71,11 +71,9 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
@Override
|
||||
public void startService() {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
for (ClientId c : validators.keySet()) {
|
||||
validateOutstandingMessagesAsync(c);
|
||||
deliverOutstandingMessagesAsync(c);
|
||||
shareOutstandingMessagesAsync(c);
|
||||
}
|
||||
validateOutstandingMessagesAsync();
|
||||
deliverOutstandingMessagesAsync();
|
||||
shareOutstandingMessagesAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,17 +91,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
hooks.put(c, hook);
|
||||
}
|
||||
|
||||
private void validateOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> validateOutstandingMessages(c));
|
||||
private void validateOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::validateOutstandingMessages);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void validateOutstandingMessages(ClientId c) {
|
||||
private void validateOutstandingMessages() {
|
||||
try {
|
||||
Queue<MessageId> unvalidated = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn, c));
|
||||
unvalidated.addAll(db.getMessagesToValidate(txn));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -148,17 +146,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> deliverOutstandingMessages(c));
|
||||
private void deliverOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::deliverOutstandingMessages);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void deliverOutstandingMessages(ClientId c) {
|
||||
private void deliverOutstandingMessages() {
|
||||
try {
|
||||
Queue<MessageId> pending = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
pending.addAll(db.getPendingMessages(txn, c));
|
||||
pending.addAll(db.getPendingMessages(txn));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -353,17 +351,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
return pending;
|
||||
}
|
||||
|
||||
private void shareOutstandingMessagesAsync(ClientId c) {
|
||||
dbExecutor.execute(() -> shareOutstandingMessages(c));
|
||||
private void shareOutstandingMessagesAsync() {
|
||||
dbExecutor.execute(this::shareOutstandingMessages);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void shareOutstandingMessages(ClientId c) {
|
||||
private void shareOutstandingMessages() {
|
||||
try {
|
||||
Queue<MessageId> toShare = new LinkedList<>();
|
||||
Transaction txn = db.startTransaction(true);
|
||||
try {
|
||||
toShare.addAll(db.getMessagesToShare(txn, c));
|
||||
toShare.addAll(db.getMessagesToShare(txn));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
|
||||
@@ -4,8 +4,9 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
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.ScheduledThreadPoolExecutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -25,7 +26,10 @@ public class SystemModule {
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
public SystemModule() {
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
// Discard tasks that are submitted during shutdown
|
||||
RejectedExecutionHandler policy =
|
||||
new ScheduledThreadPoolExecutor.DiscardPolicy();
|
||||
scheduler = new ScheduledThreadPoolExecutor(1, policy);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
@@ -56,9 +57,12 @@ import org.junit.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -160,7 +164,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
ContactStatusChangedEvent.class)));
|
||||
// getContacts()
|
||||
oneOf(database).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
will(returnValue(singletonList(contact)));
|
||||
// addGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
@@ -171,12 +175,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
// getGroups()
|
||||
oneOf(database).getGroups(txn, clientId);
|
||||
will(returnValue(Collections.singletonList(group)));
|
||||
will(returnValue(singletonList(group)));
|
||||
// removeGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
will(returnValue(emptyMap()));
|
||||
oneOf(database).removeGroup(txn, groupId);
|
||||
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
@@ -206,11 +210,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
assertEquals(contactId,
|
||||
db.addContact(transaction, author, localAuthorId, true,
|
||||
true));
|
||||
assertEquals(Collections.singletonList(contact),
|
||||
assertEquals(singletonList(contact),
|
||||
db.getContacts(transaction));
|
||||
db.addGroup(transaction, group); // First time - listeners called
|
||||
db.addGroup(transaction, group); // Second time - not called
|
||||
assertEquals(Collections.singletonList(group),
|
||||
assertEquals(singletonList(group),
|
||||
db.getGroups(transaction, clientId));
|
||||
db.removeGroup(transaction, group);
|
||||
db.removeContact(transaction, contactId);
|
||||
@@ -255,13 +259,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
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).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);
|
||||
// The message was added, so the listeners should be called
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
@@ -397,7 +396,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -418,7 +417,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Offer o = new Offer(Collections.singletonList(messageId));
|
||||
Offer o = new Offer(singletonList(messageId));
|
||||
db.receiveOffer(transaction, contactId, o);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -429,7 +428,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
Request r = new Request(singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -1022,7 +1021,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
@@ -1042,12 +1041,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false);
|
||||
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);
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
|
||||
// Second time
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -1197,7 +1191,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
Request r = new Request(singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
@@ -1206,7 +1200,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
|
||||
throws Exception {
|
||||
AtomicReference<GroupVisibilityUpdatedEvent> event =
|
||||
new AtomicReference<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1215,16 +1213,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(INVISIBLE)); // Not yet visible
|
||||
will(returnValue(INVISIBLE));
|
||||
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(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
will(new CaptureArgumentAction<>(event,
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -1236,6 +1231,48 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
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
|
||||
@@ -1267,8 +1304,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||
contactId, transportKeys);
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
singletonMap(contactId, transportKeys);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1476,13 +1513,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
// addMessageDependencies()
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
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.plugin.TransportId;
|
||||
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.TestDatabaseConfig;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -45,7 +43,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
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.SHARED;
|
||||
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.PENDING;
|
||||
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.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -74,7 +77,6 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
private final ClientId clientId;
|
||||
private final Group group;
|
||||
private final Author author;
|
||||
private final AuthorId localAuthorId;
|
||||
private final LocalAuthor localAuthor;
|
||||
private final MessageId messageId;
|
||||
private final long timestamp;
|
||||
@@ -85,19 +87,16 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
private final ContactId contactId;
|
||||
|
||||
public H2DatabaseTest() throws Exception {
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
clientId = new ClientId(StringUtils.getRandomString(5));
|
||||
groupId = new GroupId(getRandomId());
|
||||
clientId = new ClientId(getRandomString(123));
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = getAuthor();
|
||||
localAuthor = getLocalAuthor();
|
||||
messageId = new MessageId(getRandomId());
|
||||
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;
|
||||
raw = TestUtils.getRandomBytes(size);
|
||||
raw = getRandomBytes(size);
|
||||
message = new Message(messageId, groupId, timestamp, raw);
|
||||
transportId = new TransportId("id");
|
||||
contactId = new ContactId(1);
|
||||
@@ -115,14 +114,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
Connection txn = db.startTransaction();
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertTrue(db.containsContact(txn, contactId));
|
||||
assertFalse(db.containsGroup(txn, groupId));
|
||||
db.addGroup(txn, group);
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -160,7 +159,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Removing the group should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
@@ -178,22 +177,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
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
|
||||
Collection<MessageId> ids = 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);
|
||||
// The contact has not seen the message, so it should be sendable
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
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
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, UNKNOWN, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, UNKNOWN, true, null);
|
||||
|
||||
// The message has not been validated, so it should not be sendable
|
||||
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
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The group is invisible, so the message should not be sendable
|
||||
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
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, false);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, false, null);
|
||||
|
||||
// The message is not shared, so it should not be sendable
|
||||
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
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -373,20 +361,16 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a visible group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
|
||||
// Add some messages to ack
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId1, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
db.addMessage(txn, message, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
|
||||
// Both message IDs should be returned
|
||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
@@ -399,6 +383,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
||||
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.close();
|
||||
}
|
||||
@@ -410,12 +402,11 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -456,7 +447,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Storing a message should reduce the free space
|
||||
Connection txn = db.startTransaction();
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.commitTransaction(txn);
|
||||
assertTrue(db.getFreeSpace() < free);
|
||||
|
||||
@@ -568,7 +559,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a shared group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
@@ -588,7 +579,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
|
||||
// The group is not in the database
|
||||
@@ -604,15 +595,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
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);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The group is not visible
|
||||
// The group is not visible so the message should not be visible
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -626,37 +616,37 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact and a group
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group visible to the contact
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Share the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, true),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Unshare the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group invisible again
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -676,7 +666,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, the transport and the transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.addTransportKeys(txn, contactId, keys);
|
||||
@@ -738,7 +728,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
@@ -774,7 +764,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the contact, transport and transport keys
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addTransport(txn, transportId, 123);
|
||||
db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
|
||||
@@ -809,7 +799,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
|
||||
// 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));
|
||||
|
||||
// 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
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
Collection<ContactId> contacts = db.getContacts(txn, localAuthorId);
|
||||
Collection<ContactId> contacts =
|
||||
db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
|
||||
// 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));
|
||||
contacts = db.getContacts(txn, localAuthorId);
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(Collections.singletonList(contactId), contacts);
|
||||
|
||||
// Remove the local author - the contact should be removed
|
||||
db.removeLocalAuthor(txn, localAuthorId);
|
||||
contacts = db.getContacts(txn, localAuthorId);
|
||||
db.removeLocalAuthor(txn, localAuthor.getId());
|
||||
contacts = db.getContacts(txn, localAuthor.getId());
|
||||
assertEquals(Collections.emptyList(), contacts);
|
||||
assertFalse(db.containsContact(txn, contactId));
|
||||
|
||||
@@ -860,14 +851,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact - initially there should be no offered messages
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
assertEquals(0, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
// Add some offered messages and count them
|
||||
List<MessageId> ids = new ArrayList<>();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
MessageId m = new MessageId(TestUtils.getRandomId());
|
||||
MessageId m = new MessageId(getRandomId());
|
||||
db.addOfferedMessage(txn, contactId, m);
|
||||
ids.add(m);
|
||||
}
|
||||
@@ -876,8 +867,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Remove some of the offered messages and count again
|
||||
List<MessageId> half = ids.subList(0, 5);
|
||||
db.removeOfferedMessages(txn, contactId, half);
|
||||
assertTrue(db.removeOfferedMessage(txn, contactId, ids.get(5)));
|
||||
assertEquals(4, db.countOfferedMessages(txn, contactId));
|
||||
assertEquals(5, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -928,7 +918,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -999,7 +989,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1052,7 +1042,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMetadataQueries() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1060,8 +1050,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1156,7 +1146,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1164,8 +1154,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1227,10 +1217,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMessageDependencies() throws Exception {
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId4 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId1 = new MessageId(getRandomId());
|
||||
MessageId messageId2 = new MessageId(getRandomId());
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
MessageId messageId4 = new MessageId(getRandomId());
|
||||
Message message1 = new Message(messageId1, 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
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message2, INVALID, true);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
||||
|
||||
// Add dependencies
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1308,26 +1298,26 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
|
||||
// Add a second group
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
GroupId groupId1 = new GroupId(getRandomId());
|
||||
Group group1 = new Group(groupId1, clientId,
|
||||
TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// 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);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
|
||||
// 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
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId messageId3 = new MessageId(getRandomId());
|
||||
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
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1360,10 +1350,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetPendingMessagesForDelivery() throws Exception {
|
||||
MessageId mId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId4 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId1 = new MessageId(getRandomId());
|
||||
MessageId mId2 = new MessageId(getRandomId());
|
||||
MessageId mId3 = new MessageId(getRandomId());
|
||||
MessageId mId4 = new MessageId(getRandomId());
|
||||
Message m1 = new Message(mId1, groupId, timestamp, raw);
|
||||
Message m2 = new Message(mId2, 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
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, UNKNOWN, true);
|
||||
db.addMessage(txn, m2, INVALID, true);
|
||||
db.addMessage(txn, m3, PENDING, true);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
db.addMessage(txn, m1, UNKNOWN, true, contactId);
|
||||
db.addMessage(txn, m2, INVALID, true, contactId);
|
||||
db.addMessage(txn, m3, PENDING, true, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
|
||||
Collection<MessageId> result;
|
||||
|
||||
// Retrieve messages to be validated
|
||||
result = db.getMessagesToValidate(txn, clientId);
|
||||
result = db.getMessagesToValidate(txn);
|
||||
assertEquals(1, result.size());
|
||||
assertTrue(result.contains(mId1));
|
||||
|
||||
// Retrieve pending messages
|
||||
result = db.getPendingMessages(txn, clientId);
|
||||
result = db.getPendingMessages(txn);
|
||||
assertEquals(1, result.size());
|
||||
assertTrue(result.contains(mId3));
|
||||
|
||||
@@ -1397,10 +1387,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testGetMessagesToShare() throws Exception {
|
||||
MessageId mId1 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId2 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId3 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId4 = new MessageId(TestUtils.getRandomId());
|
||||
MessageId mId1 = new MessageId(getRandomId());
|
||||
MessageId mId2 = new MessageId(getRandomId());
|
||||
MessageId mId3 = new MessageId(getRandomId());
|
||||
MessageId mId4 = new MessageId(getRandomId());
|
||||
Message m1 = new Message(mId1, groupId, timestamp, raw);
|
||||
Message m2 = new Message(mId2, 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
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, DELIVERED, true);
|
||||
db.addMessage(txn, m2, DELIVERED, false);
|
||||
db.addMessage(txn, m3, DELIVERED, false);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
db.addMessage(txn, m1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, m2, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m3, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
|
||||
// Introduce dependencies between the messages
|
||||
db.addMessageDependency(txn, groupId, mId1, mId2);
|
||||
@@ -1422,8 +1412,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.addMessageDependency(txn, groupId, mId4, mId3);
|
||||
|
||||
// Retrieve messages to be shared
|
||||
Collection<MessageId> result =
|
||||
db.getMessagesToShare(txn, clientId);
|
||||
Collection<MessageId> result = db.getMessagesToShare(txn);
|
||||
assertEquals(2, result.size());
|
||||
assertTrue(result.contains(mId2));
|
||||
assertTrue(result.contains(mId3));
|
||||
@@ -1439,12 +1428,11 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message should not be sent or seen
|
||||
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
||||
@@ -1508,9 +1496,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testDifferentLocalAuthorsCanHaveTheSameContact()
|
||||
throws Exception {
|
||||
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
||||
LocalAuthor localAuthor1 = getLocalAuthor();
|
||||
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
@@ -1521,15 +1507,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add the same contact for each local author
|
||||
ContactId contactId =
|
||||
db.addContact(txn, author, localAuthorId, true, true);
|
||||
db.addContact(txn, author, localAuthor.getId(), true, true);
|
||||
ContactId contactId1 =
|
||||
db.addContact(txn, author, localAuthorId1, true, true);
|
||||
db.addContact(txn, author, localAuthor1.getId(), true, true);
|
||||
|
||||
// The contacts should be distinct
|
||||
assertNotEquals(contactId, contactId1);
|
||||
assertEquals(2, db.getContacts(txn).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthorId).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthorId1).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
|
||||
assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -1542,12 +1528,11 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact, a shared group and a shared message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message should be visible to the contact
|
||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
@@ -1588,7 +1573,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a contact
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
|
||||
// The contact should be active
|
||||
@@ -1621,7 +1606,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
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
|
||||
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
|
||||
@@ -1647,14 +1632,13 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false);
|
||||
db.addMessage(txn, message, UNKNOWN, false, null);
|
||||
|
||||
// There should be no messages to send
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the group with the contact - still no messages to send
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// 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);
|
||||
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
|
||||
// message should be sendable after one round-trip
|
||||
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||
@@ -1713,20 +1701,20 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private TransportKeys createTransportKeys() {
|
||||
SecretKey inPrevTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inPrevHeaderKey = TestUtils.getSecretKey();
|
||||
SecretKey inPrevTagKey = getSecretKey();
|
||||
SecretKey inPrevHeaderKey = getSecretKey();
|
||||
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
||||
1, 123, new byte[4]);
|
||||
SecretKey inCurrTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inCurrHeaderKey = TestUtils.getSecretKey();
|
||||
SecretKey inCurrTagKey = getSecretKey();
|
||||
SecretKey inCurrHeaderKey = getSecretKey();
|
||||
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
||||
2, 234, new byte[4]);
|
||||
SecretKey inNextTagKey = TestUtils.getSecretKey();
|
||||
SecretKey inNextHeaderKey = TestUtils.getSecretKey();
|
||||
SecretKey inNextTagKey = getSecretKey();
|
||||
SecretKey inNextHeaderKey = getSecretKey();
|
||||
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
||||
3, 345, new byte[4]);
|
||||
SecretKey outCurrTagKey = TestUtils.getSecretKey();
|
||||
SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
|
||||
SecretKey outCurrTagKey = getSecretKey();
|
||||
SecretKey outCurrHeaderKey = getSecretKey();
|
||||
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
||||
2, 456);
|
||||
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
||||
|
||||
@@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertNotNull;
|
||||
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.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
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 =
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupID BINARY(32) NOT NULL,"
|
||||
+ " (groupId BINARY(32) NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
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 GroupId groupId = new GroupId(getRandomId());
|
||||
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||
private final Message message = TestUtils.getMessage(groupId);
|
||||
private final Message message1 = TestUtils.getMessage(groupId1);
|
||||
private final Message message = getMessage(groupId);
|
||||
private final Message message1 = getMessage(groupId1);
|
||||
private final Metadata meta = new Metadata(), meta1 = new Metadata();
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,21 +100,21 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// validateOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// deliverOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
// shareOutstandingMessages()
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getMessagesToShare(txn2, clientId);
|
||||
oneOf(db).getMessagesToShare(txn2);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
@@ -138,7 +138,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -199,14 +199,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getPendingMessages(txn5, clientId);
|
||||
oneOf(db).getPendingMessages(txn5);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn6));
|
||||
oneOf(db).getMessagesToShare(txn6, clientId);
|
||||
oneOf(db).getMessagesToShare(txn6);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn6);
|
||||
oneOf(db).endTransaction(txn6);
|
||||
@@ -227,14 +227,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
@@ -292,7 +292,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getMessagesToShare(txn4, clientId);
|
||||
oneOf(db).getMessagesToShare(txn4);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
@@ -313,14 +313,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// No messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
// No pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn1));
|
||||
oneOf(db).getPendingMessages(txn1, clientId);
|
||||
oneOf(db).getPendingMessages(txn1);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn1);
|
||||
oneOf(db).endTransaction(txn1);
|
||||
@@ -328,7 +328,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn2));
|
||||
oneOf(db).getMessagesToShare(txn2, clientId);
|
||||
oneOf(db).getMessagesToShare(txn2);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
@@ -416,7 +416,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -457,14 +457,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getPendingMessages(txn4, clientId);
|
||||
oneOf(db).getPendingMessages(txn4);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getMessagesToShare(txn5, clientId);
|
||||
oneOf(db).getMessagesToShare(txn5);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
@@ -487,7 +487,7 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get messages to validate
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getMessagesToValidate(txn, clientId);
|
||||
oneOf(db).getMessagesToValidate(txn);
|
||||
will(returnValue(Arrays.asList(messageId, messageId1)));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
@@ -533,14 +533,14 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
||||
// Get pending messages to deliver
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn4));
|
||||
oneOf(db).getPendingMessages(txn4, clientId);
|
||||
oneOf(db).getPendingMessages(txn4);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn4);
|
||||
oneOf(db).endTransaction(txn4);
|
||||
// Get messages to share
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn5));
|
||||
oneOf(db).getMessagesToShare(txn5, clientId);
|
||||
oneOf(db).getMessagesToShare(txn5);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
oneOf(db).commitTransaction(txn5);
|
||||
oneOf(db).endTransaction(txn5);
|
||||
|
||||
@@ -189,8 +189,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1619
|
||||
versionName "0.16.19"
|
||||
versionCode 1620
|
||||
versionName "0.16.20"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_name", "Briar Beta"
|
||||
|
||||
@@ -374,7 +374,12 @@
|
||||
</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">
|
||||
</activity>
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ import javax.inject.Inject;
|
||||
import static android.app.Notification.DEFAULT_LIGHTS;
|
||||
import static android.app.Notification.DEFAULT_SOUND;
|
||||
import static android.app.Notification.DEFAULT_VIBRATE;
|
||||
import static android.app.Notification.VISIBILITY_SECRET;
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
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 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 Logger LOG =
|
||||
@@ -175,6 +170,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
NotificationChannel nc =
|
||||
new NotificationChannel(channelId, appContext.getString(name),
|
||||
IMPORTANCE_DEFAULT);
|
||||
nc.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nc.enableVibration(true);
|
||||
nc.enableLights(true);
|
||||
nc.setLightColor(
|
||||
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
||||
|
||||
@@ -4,8 +4,11 @@ import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
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.system.AndroidExecutor;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.logout.HideUiActivity;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
import org.briarproject.briar.android.splash.SplashScreenActivity;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_NONE;
|
||||
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_EXCLUDE_FROM_RECENTS;
|
||||
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.support.v4.app.NotificationCompat.CATEGORY_SERVICE;
|
||||
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 Binder binder = new BriarBinder();
|
||||
|
||||
@Nullable
|
||||
private BroadcastReceiver receiver = null;
|
||||
|
||||
@Inject
|
||||
protected DatabaseConfig databaseConfig;
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@@ -143,6 +156,19 @@ public class BriarService extends Service {
|
||||
}
|
||||
}
|
||||
}.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) {
|
||||
@@ -187,6 +213,7 @@ public class BriarService extends Service {
|
||||
super.onDestroy();
|
||||
LOG.info("Destroyed");
|
||||
stopForeground(true);
|
||||
if (receiver != null) unregisterReceiver(receiver);
|
||||
// Stop the services in a background thread
|
||||
new Thread() {
|
||||
@Override
|
||||
@@ -200,7 +227,48 @@ public class BriarService extends Service {
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.briarproject.briar.android.controller.BriarController;
|
||||
import org.briarproject.briar.android.controller.DbController;
|
||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||
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;
|
||||
|
||||
|
||||
@@ -52,19 +52,15 @@ class ForumListAdapter
|
||||
// Post Count
|
||||
int postCount = item.getPostCount();
|
||||
if (postCount > 0) {
|
||||
ui.avatar.setProblem(false);
|
||||
ui.postCount.setText(ctx.getResources()
|
||||
.getQuantityString(R.plurals.posts, postCount,
|
||||
postCount));
|
||||
ui.postCount.setTextColor(
|
||||
ContextCompat
|
||||
.getColor(ctx, R.color.briar_text_secondary));
|
||||
ContextCompat.getColor(ctx, R.color.briar_text_secondary));
|
||||
} else {
|
||||
ui.avatar.setProblem(true);
|
||||
ui.postCount.setText(ctx.getString(R.string.no_posts));
|
||||
ui.postCount.setTextColor(
|
||||
ContextCompat
|
||||
.getColor(ctx, R.color.briar_text_tertiary));
|
||||
ContextCompat.getColor(ctx, R.color.briar_text_tertiary));
|
||||
}
|
||||
|
||||
// Date
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog.Builder;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
@@ -206,11 +207,14 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
|
||||
private void showQrCodeFragment() {
|
||||
// FIXME #824
|
||||
BaseFragment f = ShowQrCodeFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
.addToBackStack(f.getUniqueTag())
|
||||
.commit();
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentByTag(ShowQrCodeFragment.TAG) == null) {
|
||||
BaseFragment f = ShowQrCodeFragment.newInstance();
|
||||
fm.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
.addToBackStack(f.getUniqueTag())
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPermissions() {
|
||||
|
||||
@@ -65,7 +65,6 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||
if (camera == this.camera) {
|
||||
LOG.info("Got preview frame");
|
||||
try {
|
||||
Size size = camera.getParameters().getPreviewSize();
|
||||
// The preview should be in NV21 format: width * height bytes of
|
||||
@@ -103,19 +102,12 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
long now = System.currentTimeMillis();
|
||||
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
||||
Result result = null;
|
||||
Result result;
|
||||
try {
|
||||
result = reader.decode(bitmap);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Decoding barcode took " + duration + " ms");
|
||||
} catch (ReaderException e) {
|
||||
// No barcode found
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("No barcode found after " + duration + " ms");
|
||||
return null;
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warning("Invalid preview frame");
|
||||
|
||||
@@ -15,6 +15,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -50,6 +52,8 @@ import javax.inject.Inject;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.view.View.INVISIBLE;
|
||||
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 java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
@@ -59,7 +63,8 @@ import static java.util.logging.Level.WARNING;
|
||||
public class ShowQrCodeFragment extends BaseEventFragment
|
||||
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);
|
||||
|
||||
@Inject
|
||||
@@ -80,6 +85,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
private ImageView qrCode;
|
||||
private TextView mainProgressTitle;
|
||||
private ViewGroup mainProgressContainer;
|
||||
private boolean fullscreen = false;
|
||||
|
||||
private boolean gotRemotePayload;
|
||||
private volatile boolean gotLocalPayload;
|
||||
@@ -124,6 +130,34 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
qrCode = view.findViewById(R.id.qr_code);
|
||||
mainProgressTitle = view.findViewById(R.id.title_progress_bar);
|
||||
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
|
||||
@@ -204,6 +238,15 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
|
||||
@UiThread
|
||||
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);
|
||||
cameraView.setVisibility(VISIBLE);
|
||||
gotRemotePayload = false;
|
||||
@@ -218,12 +261,17 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Remote payload is " + encoded.length + " bytes");
|
||||
Payload remotePayload = payloadParser.parse(encoded);
|
||||
gotRemotePayload = true;
|
||||
cameraView.stop();
|
||||
cameraView.setVisibility(INVISIBLE);
|
||||
statusView.setVisibility(VISIBLE);
|
||||
status.setText(R.string.connecting_to_device);
|
||||
task.connectAndRunProtocol(remotePayload);
|
||||
} catch (CameraException e) {
|
||||
logCameraExceptionAndFinish(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,
|
||||
LENGTH_LONG).show();
|
||||
}
|
||||
@@ -261,6 +309,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
new AsyncTask<Void, Void, Bitmap>() {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Bitmap doInBackground(Void... params) {
|
||||
byte[] encoded = payloadEncoder.encode(payload);
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -325,13 +374,8 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
LOG.info("Got result from decoder");
|
||||
// Ignore results until the KeyAgreementTask is ready
|
||||
if (!gotLocalPayload) {
|
||||
return;
|
||||
}
|
||||
if (!gotRemotePayload) {
|
||||
gotRemotePayload = true;
|
||||
qrCodeScanned(result.getText());
|
||||
}
|
||||
if (!gotLocalPayload) return;
|
||||
if (!gotRemotePayload) qrCodeScanned(result.getText());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.panic;
|
||||
package org.briarproject.briar.android.logout;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
package org.briarproject.briar.android.logout;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -7,15 +7,17 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class SignOutFragment extends BaseFragment {
|
||||
|
||||
private static final String TAG = SignOutFragment.class.getName();
|
||||
public static final String TAG = SignOutFragment.class.getName();
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
public View onCreateView(@Nonnull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_sign_out, container, false);
|
||||
@@ -30,5 +32,4 @@ public class SignOutFragment extends BaseFragment {
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
// no need to inject
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
@@ -22,6 +23,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.LanTcpConstants;
|
||||
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.fragment.BaseFragment;
|
||||
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.privategroup.list.GroupListFragment;
|
||||
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.view.View.GONE;
|
||||
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.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
||||
@@ -73,6 +76,8 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
|
||||
@Inject
|
||||
NavDrawerController controller;
|
||||
@Inject
|
||||
LifecycleManager lifecycleManager;
|
||||
|
||||
private DrawerLayout drawerLayout;
|
||||
private NavigationView navigation;
|
||||
@@ -128,7 +133,9 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
initializeTransports(getLayoutInflater());
|
||||
transportsView.setAdapter(transportsAdapter);
|
||||
|
||||
if (state == null) {
|
||||
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
|
||||
showSignOutFragment();
|
||||
} else if (state == null) {
|
||||
startFragment(ContactListFragment.newInstance(),
|
||||
R.id.nav_btn_contacts);
|
||||
}
|
||||
@@ -212,19 +219,23 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
public void onBackPressed() {
|
||||
if (drawerLayout.isDrawerOpen(START)) {
|
||||
drawerLayout.closeDrawer(START);
|
||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||
getSupportFragmentManager()
|
||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||
} else {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
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
|
||||
* user sees is the same as the last fragment the user sees before
|
||||
* exiting. This models the typical Google navigation behaviour such
|
||||
* as in Gmail/Inbox.
|
||||
*/
|
||||
startFragment(ContactListFragment.newInstance(),
|
||||
R.id.nav_btn_contacts);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
startFragment(ContactListFragment.newInstance(),
|
||||
R.id.nav_btn_contacts);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +251,15 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
private void signOut() {
|
||||
private void showSignOutFragment() {
|
||||
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
||||
startFragment(new SignOutFragment());
|
||||
}
|
||||
|
||||
private void signOut() {
|
||||
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
|
||||
signOut(false);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment, int itemId) {
|
||||
|
||||
@@ -74,9 +74,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
||||
if (group.isEmpty()) {
|
||||
postCount.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);
|
||||
} else {
|
||||
// Message Count
|
||||
@@ -91,7 +89,6 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
|
||||
long lastUpdate = group.getTimestamp();
|
||||
date.setText(UiUtils.formatDate(ctx, lastUpdate));
|
||||
date.setVisibility(VISIBLE);
|
||||
avatar.setProblem(false);
|
||||
status.setVisibility(GONE);
|
||||
}
|
||||
remove.setVisibility(GONE);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.briarproject.briar.android.settings;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.ListPreference;
|
||||
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_TYPE;
|
||||
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.widget.Toast.LENGTH_SHORT;
|
||||
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.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
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_FORUM;
|
||||
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");
|
||||
notifySound = findPreference("pref_key_notify_sound");
|
||||
|
||||
setSettingsEnabled(false);
|
||||
|
||||
enableBluetooth.setOnPreferenceChangeListener(this);
|
||||
torNetwork.setOnPreferenceChangeListener(this);
|
||||
notifyPrivateMessages.setOnPreferenceChangeListener(this);
|
||||
notifyGroupMessages.setOnPreferenceChangeListener(this);
|
||||
notifyForumPosts.setOnPreferenceChangeListener(this);
|
||||
notifyBlogPosts.setOnPreferenceChangeListener(this);
|
||||
notifyVibration.setOnPreferenceChangeListener(this);
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (SDK_INT >= 21) {
|
||||
notifyLockscreen.setVisible(true);
|
||||
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(
|
||||
preference -> {
|
||||
@@ -218,39 +206,105 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
enableBluetooth.setValue(Boolean.toString(btSetting));
|
||||
torNetwork.setValue(Integer.toString(torSetting));
|
||||
|
||||
notifyPrivateMessages.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_PRIVATE, true));
|
||||
|
||||
notifyGroupMessages.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_GROUP, true));
|
||||
|
||||
notifyForumPosts.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_FORUM, true));
|
||||
|
||||
notifyBlogPosts.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_BLOG, true));
|
||||
|
||||
notifyVibration.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_VIBRATION, true));
|
||||
|
||||
notifyLockscreen.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_LOCK_SCREEN, false));
|
||||
|
||||
String text;
|
||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||
String ringtoneName = settings.get(PREF_NOTIFY_RINGTONE_NAME);
|
||||
if (StringUtils.isNullOrEmpty(ringtoneName)) {
|
||||
text = getString(R.string.notify_sound_setting_default);
|
||||
if (SDK_INT < 26) {
|
||||
notifyPrivateMessages.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_PRIVATE, true));
|
||||
notifyGroupMessages.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_GROUP, true));
|
||||
notifyForumPosts.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_FORUM, true));
|
||||
notifyBlogPosts.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_BLOG, true));
|
||||
notifyVibration.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_VIBRATION, true));
|
||||
notifyPrivateMessages.setOnPreferenceChangeListener(this);
|
||||
notifyGroupMessages.setOnPreferenceChangeListener(this);
|
||||
notifyForumPosts.setOnPreferenceChangeListener(this);
|
||||
notifyBlogPosts.setOnPreferenceChangeListener(this);
|
||||
notifyVibration.setOnPreferenceChangeListener(this);
|
||||
notifyLockscreen.setChecked(settings.getBoolean(
|
||||
PREF_NOTIFY_LOCK_SCREEN, false));
|
||||
notifySound.setOnPreferenceClickListener(
|
||||
pref -> onNotificationSoundClicked());
|
||||
String text;
|
||||
if (settings.getBoolean(PREF_NOTIFY_SOUND, true)) {
|
||||
String ringtoneName =
|
||||
settings.get(PREF_NOTIFY_RINGTONE_NAME);
|
||||
if (StringUtils.isNullOrEmpty(ringtoneName)) {
|
||||
text = getString(R.string.notify_sound_setting_default);
|
||||
} else {
|
||||
text = ringtoneName;
|
||||
}
|
||||
} else {
|
||||
text = ringtoneName;
|
||||
text = getString(R.string.notify_sound_setting_disabled);
|
||||
}
|
||||
notifySound.setSummary(text);
|
||||
} 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() {
|
||||
androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter()
|
||||
.handleException(new UserFeedback(), false));
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -26,7 +25,6 @@ public class TextAvatarView extends FrameLayout {
|
||||
private final AppCompatTextView character;
|
||||
private final CircleImageView background;
|
||||
private final TextView badge;
|
||||
private int unreadCount;
|
||||
|
||||
public TextAvatarView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -49,30 +47,14 @@ public class TextAvatarView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void setUnreadCount(int count) {
|
||||
unreadCount = count;
|
||||
if (count > 0) {
|
||||
badge.setBackgroundResource(R.drawable.bubble);
|
||||
badge.setText(String.valueOf(count));
|
||||
badge.setTextColor(ContextCompat.getColor(getContext(),
|
||||
R.color.briar_text_primary_inverse));
|
||||
badge.setVisibility(VISIBLE);
|
||||
} else {
|
||||
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) {
|
||||
int r = getByte(bytes, 0) * 3 / 4 + 96;
|
||||
int g = getByte(bytes, 1) * 3 / 4 + 96;
|
||||
|
||||
@@ -21,6 +21,12 @@ public interface AndroidNotificationManager {
|
||||
String PREF_NOTIFY_VIBRATION = "notifyVibration";
|
||||
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
|
||||
String CONTACT_URI = "content://org.briarproject.briar/contact";
|
||||
String GROUP_URI = "content://org.briarproject.briar/group";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<vector android:alpha="0.54" android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -15,39 +15,35 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
android:baselineAligned="false">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/status_container"
|
||||
android:layout_width="0dp"
|
||||
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
|
||||
android:id="@+id/status_container"
|
||||
<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="match_parent"
|
||||
android:background="@android:color/background_light"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:visibility="invisible">
|
||||
|
||||
<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>
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
tools:text="Connection failed"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qr_code_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
@@ -59,12 +55,31 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qr_code"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<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>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -92,5 +107,4 @@
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -15,39 +15,35 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:weightSum="2">
|
||||
android:baselineAligned="false">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/status_container"
|
||||
android:layout_width="match_parent"
|
||||
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
|
||||
android:id="@+id/status_container"
|
||||
<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="match_parent"
|
||||
android:background="@android:color/background_light"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:visibility="invisible">
|
||||
|
||||
<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>
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
tools:text="Connection failed"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/qr_code_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
@@ -59,12 +55,31 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qr_code"
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"/>
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<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>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -92,5 +107,4 @@
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<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
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"/>
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
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
|
||||
android:id="@+id/title_progress_bar"
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/progressBar"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
android:text="@string/progress_title_logout"/>
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/progress_title_logout"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
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>
|
||||
@@ -234,8 +234,8 @@
|
||||
<string name="blogs_blog_post_scroll_to">Отвори</string>
|
||||
<string name="blogs_feed_empty_state">Това е глобалната блог емисия.\n\nНикой още не е публикувал нищо.\n\nБъдете първия и натиснете писалката, за да напишете първата блог публикация.</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_ok">Премахни блог</string>
|
||||
<string name="blogs_blog_removed">Блогът е премахнат</string>
|
||||
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</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_updated">Последно актуализиране:</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_ok">Емисията е премахната</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_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string>
|
||||
|
||||
@@ -134,8 +134,10 @@
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_message">Ouzhpennañ ur gemennadenn (diret)</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_remove_blog_ok">Dilemel</string>
|
||||
<!--Blog Sharing-->
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_remove_feed_ok">Dilemel</string>
|
||||
<!--Settings Network-->
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="lock_setting_title">Digevreañ</string>
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
</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_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-->
|
||||
<string name="nav_drawer_open_description">Obre 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="create_forum_button">Crea 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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d publicacio</item>
|
||||
@@ -251,7 +253,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">Ningú</string>
|
||||
<!--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="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>
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
<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="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_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>
|
||||
<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>
|
||||
@@ -39,6 +41,8 @@
|
||||
</plurals>
|
||||
<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="startup_open_database">Datu-basea deszifratzen...</string>
|
||||
<string name="startup_migrate_database">Datu-basea eguneratzen...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Ireki 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="create_forum_button">Sortu foroa</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">Bidalketa %d</item>
|
||||
@@ -247,7 +251,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">Inor ez</string>
|
||||
<!--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="blogs_write_blog_post">Idatzi blog sarrera</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Idatzi zure blog sarrera hemen</string>
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
</plurals>
|
||||
<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="startup_open_database">Puretaan tietokannan salaus...</string>
|
||||
<string name="startup_migrate_database">Päivitetään tietokanta...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Avaa 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="fix">Korjaa</string>
|
||||
<string name="help">Ohje</string>
|
||||
<string name="sorry">Anteeksi</string>
|
||||
<string name="sorry">Pahoittelemme</string>
|
||||
<!--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="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="delete_contact">Poista yhteystieto</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="create_forum_button">Luo foorumi</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d viesti</item>
|
||||
@@ -231,7 +233,7 @@
|
||||
<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_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="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>
|
||||
@@ -247,10 +249,10 @@
|
||||
</plurals>
|
||||
<string name="nobody">Ei kukaan</string>
|
||||
<!--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="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_blog_post_created">Blogikirjoitus julkaistu</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_invitations_title">Blogi kutsut</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>
|
||||
<!--RSS Feeds-->
|
||||
<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_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="qr_code">QR-koodi</string>
|
||||
<string name="show_qr_code_fullscreen">Näytä QR-koodi koko näytöllä</string>
|
||||
</resources>
|
||||
|
||||
@@ -33,16 +33,18 @@
|
||||
<string name="startup_failed_notification_title">Impossible de démarrer Briar</string>
|
||||
<string name="startup_failed_notification_text">Toucher pour plus d’informations.</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 « J’ai oublié mon mot de passe » dans l’invite 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 l’ancienne version soit supprimer votre compte en choisissant « J’ai oublié mon mot de passe » dans l’invite 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 « J’ai oublié mon mot de passe » dans l’invite 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 réinstaller l’ancienne version, soit créer un nouveau compte en choisissant « J’ai oublié mon mot de passe » dans l’invite de mot de passe.</string>
|
||||
<string name="startup_failed_data_too_new_error">Cette version de l’appli est trop ancienne. Veuillez la mettre à niveau vers la dernière version et ressayer.</string>
|
||||
<string name="startup_failed_service_error">Briar n’a 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 n’utilise pas de serveurs centralisés sur lesquels enregistrer vos données.</string>
|
||||
<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="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>
|
||||
<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 l’avoir 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-->
|
||||
<string name="nav_drawer_open_description">Ouvrir 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="sorry">Désolé</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Il semble que soyez nouveau ici, sans encore aucun contact.\n\nTouchez l’icô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 quelqu’un se fasse passer pour vous et puisse lire vos messages à l’avenir.</string>
|
||||
<string name="no_contacts">Aucun contact à afficher\n\nTouchez l’icône + pour ajouter un contact</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="delete_contact">Supprimer le 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>
|
||||
</plurals>
|
||||
<!--Private Groups-->
|
||||
<string name="groups_list_empty">Vous ne participez à aucun groupe.\n\nTouchez l’icône + ci-haut pour en créer un ou demandez à vos contacts de vous inviter dans l’un des leurs.</string>
|
||||
<string name="groups_list_empty">Aucun groupe à afficher\n\nTouchez l’icô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>
|
||||
<plurals name="messages">
|
||||
<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_invisible">Votre lien avec le contact n’est pas visible par le groupe</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Vous n’avez pas encore de forums.\n\nPourquoi ne pas en créer un en touchant l’icône + ci-haut ?\n\nVous pouvez aussi demander à vos contacts d’en partager avec vous.</string>
|
||||
<string name="no_forums">Aucun forum à afficher\n\nTouchez l’icô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="choose_forum_hint">Choisir un nom pour votre 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="no_forum_posts">Ce forum est vide.\n\nUtilisez l’icô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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d article</item>
|
||||
@@ -219,23 +221,23 @@
|
||||
<string name="btn_reply">Répondre</string>
|
||||
<string name="forum_leave">Quitter le 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 l’avez 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 l’avez partagé pourraient ne plus en recevoir les mises à jour.</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-->
|
||||
<string name="forum_share_button">Partager le forum</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="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_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_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_invitations_title">Invitations au forum</string>
|
||||
<string name="forum_invitation_exists">Vous avez déjà accepté une invitation à ce forum. En acceptant d’autres 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 d’autres 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_declined_toast">L’invitation au forum a été refusée</string>
|
||||
<string name="forum_declined_toast">L’invitation a été refusée</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_response_accepted_sent">Vous avez accepté l’invitation de %s au forum.</string>
|
||||
@@ -251,18 +253,18 @@
|
||||
</plurals>
|
||||
<string name="nobody">Personne</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_other_blog_empty_state">Ce blogue est actuellement vide.\n\nSoit l’auteur n’a encore rien écrit, soit la personne qui l’a 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="blogs_write_blog_post">Écrire un article de blogue</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Saisir votre message de blogue ici</string>
|
||||
<string name="blogs_write_blog_post">Écrire un billet de blogue</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_blog_post_created">L’article 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_created">Le billet de blogue a été créé</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_feed_empty_state">Ceci est le fil global des blogues.\n\nIl semble que personne n’ait encore rien écrit.\n\nSoyez le premier et touchez l’icô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 l’icône de crayon pour rédiger un billet</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 d’autrui.</string>
|
||||
<string name="blogs_remove_blog_ok">Supprimer le blogue</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 d’autrui.\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</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_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_invitations_title">Invitations au blogue</string>
|
||||
<string name="blogs_sharing_joined_toast">Est abonné au blogue</string>
|
||||
<string name="blogs_sharing_declined_toast">L’invitation au blogue a été refusée</string>
|
||||
<string name="blogs_sharing_declined_toast">L’invitation 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 d’autres abonnés que vous ne pouvez pas voir.</string>
|
||||
<!--RSS Feeds-->
|
||||
<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_updated">Dernière mise à jour :</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 d’autrui.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Supprimer le fil</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 d’autrui.\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</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Impossible de supprimer le fil !</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Vous n’avez encore importé aucun fil RSS.\n\nPourquoi ne pas cliquer sur l’icône + en haut à droite pour ajouter votre premier fil ?</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Aucun fil RSS à afficher\n\nTouchez l’icô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>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Réseaux</string>
|
||||
@@ -331,12 +333,16 @@
|
||||
<string name="notification_settings_title">Notifications</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_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_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_summary">Afficher des alertes pour les articles de forum</string>
|
||||
<string name="notify_blog_posts_setting_title">Articles de blogue</string>
|
||||
<string name="notify_blog_posts_setting_summary">Afficher des alertes pour pour les 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_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_lock_screen_setting_title">É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_denied_body">Vous avez refusé l’accès à la caméra, mais l’ajout de contacts exige l’utilisation de celle-ci.\n\nVeuillez envisager d’y accorder l’accès.</string>
|
||||
<string name="permission_camera_denied_toast">L’accès à la caméra n’a 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>
|
||||
|
||||
@@ -107,11 +107,13 @@
|
||||
<!--Blogs-->
|
||||
<string name="read_more">ler mais</string>
|
||||
<string name="blogs_publish_blog_post">Publicar</string>
|
||||
<string name="blogs_remove_blog_ok">Eliminar</string>
|
||||
<!--Blog Sharing-->
|
||||
<!--RSS Feeds-->
|
||||
<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_updated">Última actualización:</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Eliminar</string>
|
||||
<!--Settings Network-->
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="change_password">Cambiar contrasinal</string>
|
||||
|
||||
@@ -241,11 +241,13 @@
|
||||
<string name="forum_declined_toast">הזמנה לפורום נדחתה</string>
|
||||
<string name="shared_by_format">שותף על ידי %s</string>
|
||||
<!--Blogs-->
|
||||
<string name="blogs_remove_blog_ok">להסיר</string>
|
||||
<!--Blog Sharing-->
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import_button">ייבא</string>
|
||||
<string name="blogs_rss_feeds_manage_author">מחבר:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">עודכן לאחרונה:</string>
|
||||
<string name="blogs_rss_remove_feed_ok">להסיר</string>
|
||||
<!--Settings Network-->
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">אבטחה</string>
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
</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_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-->
|
||||
<string name="nav_drawer_open_description">Apri 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="create_forum_button">Crea Forum</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d post</item>
|
||||
@@ -251,7 +253,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">Nessuno</string>
|
||||
<!--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="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>
|
||||
|
||||
@@ -97,11 +97,13 @@
|
||||
<!--Forum Sharing-->
|
||||
<!--Blogs-->
|
||||
<string name="blogs_publish_blog_post">公開</string>
|
||||
<string name="blogs_remove_blog_ok">解除</string>
|
||||
<!--Blog Sharing-->
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import_button">インポート</string>
|
||||
<string name="blogs_rss_feeds_manage_author">著者:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">最終更新:</string>
|
||||
<string name="blogs_rss_remove_feed_ok">解除</string>
|
||||
<!--Settings Network-->
|
||||
<string name="tor_network_setting_never">二度としない</string>
|
||||
<!--Settings Security and Panic-->
|
||||
@@ -113,6 +115,7 @@
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_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>
|
||||
<!--Settings Feedback-->
|
||||
<!--Link Warning-->
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
<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="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_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>
|
||||
<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>
|
||||
@@ -39,6 +41,8 @@
|
||||
</plurals>
|
||||
<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="startup_open_database">Database aan het ontsleutelen…</string>
|
||||
<string name="startup_migrate_database">Database aan het upgraden…</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Open 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="fix">Fiks</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="sorry">Excuses</string>
|
||||
<!--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="date_no_private_messages">Geen berichten.</string>
|
||||
@@ -114,6 +119,7 @@
|
||||
<string name="contact_already_exists">Contact %s bestaat al</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_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="connecting_to_device">Aan het verbinden 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>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<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-->
|
||||
<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>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<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_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_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_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 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_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">
|
||||
@@ -43,6 +43,8 @@
|
||||
</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_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-->
|
||||
<string name="nav_drawer_open_description">Abrir 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="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="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="describe_crash">Descreva o que aconteceu (opcional)</string>
|
||||
<string name="enter_feedback">Digite seu comentário</string>
|
||||
@@ -378,6 +380,7 @@
|
||||
<!--Permission Requests-->
|
||||
<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_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="qr_code">Código QR</string>
|
||||
</resources>
|
||||
|
||||
359
briar-android/src/main/res/values-ro/strings.xml
Normal file
359
briar-android/src/main/res/values-ro/strings.xml
Normal 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>
|
||||
@@ -45,6 +45,8 @@
|
||||
</plurals>
|
||||
<string name="expiry_update">Дата окончания тестирования была продлена. Срок действия вашей учетной записи истечет через %d дней.</string>
|
||||
<string name="expiry_date_reached">Срок действия этого программного обеспечения истек.\nСпасибо за тестирование!</string>
|
||||
<string name="startup_open_database">Расшифровка базы данных...</string>
|
||||
<string name="startup_migrate_database">Обновление базы данных...</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Открыть навигационное меню</string>
|
||||
<string name="nav_drawer_close_description">Закрыть навигационное меню</string>
|
||||
@@ -223,7 +225,7 @@
|
||||
<string name="choose_forum_hint">Выберите имя для вашего форума</string>
|
||||
<string name="create_forum_button">Создать форум</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d пост</item>
|
||||
@@ -271,7 +273,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">Никого</string>
|
||||
<!--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="blogs_write_blog_post">Написать в блоге</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Введите ваше сообщение здесь</string>
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
</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_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-->
|
||||
<string name="nav_drawer_open_description">Hap 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="sorry">Na ndjeni</string>
|
||||
<!--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">S’ka 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="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">S’ka mesazhe për shfaqje</string>
|
||||
<string name="message_hint">Shtypni mesazhin</string>
|
||||
<string name="delete_contact">Fshije kontaktin</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>
|
||||
</plurals>
|
||||
<!--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>
|
||||
<plurals name="messages">
|
||||
<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_invisible">Marrëdhënia e kontaktit s\’është e dukshme për grupin</string>
|
||||
<!--Forums-->
|
||||
<string name="no_forums">Ende s\’keni forume.\n\nPse nuk krijoni vetë një të tillë duke prekur ikonën + 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="choose_forum_hint">Zgjidhni një emër për forumin tuaj</string>
|
||||
<string name="create_forum_button">Krijoje Forumin</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d postim</item>
|
||||
@@ -219,23 +221,23 @@
|
||||
<string name="btn_reply">Përgjigju</string>
|
||||
<string name="forum_leave">Braktiseni Forumin</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="forum_left_toast">E braktisët Forumin</string>
|
||||
<string name="forum_left_toast">E braktisët forumin</string>
|
||||
<!--Forum Sharing-->
|
||||
<string name="forum_share_button">Ndajeni Forumin Me të Tjerë</string>
|
||||
<string name="contacts_selected">Kontaktet u përzgjodhën</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">S’ka 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_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_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_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_joined_toast">U bëtë Pjesë e Forumit</string>
|
||||
<string name="forum_declined_toast">Ftesa e Forumit u Hodh Poshtë</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">Hytë në forum</string>
|
||||
<string name="forum_declined_toast">Ftesa u hodh poshtë</string>
|
||||
<string name="shared_by_format">Ndarë nga %s</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>
|
||||
@@ -251,19 +253,19 @@
|
||||
</plurals>
|
||||
<string name="nobody">Askush</string>
|
||||
<!--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="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_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_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">S’ka 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_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_ok">Hiqe Blogun</string>
|
||||
<string name="blogs_blog_removed">Blogu u Hoq</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</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_button">Riblogojeni</string>
|
||||
<!--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_sent">Ndatë blogun \"%1$s\" me %2$s.</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_declined_toast">Ftesa e Blogut u Hodh Poshtë</string>
|
||||
<string name="blogs_sharing_joined_toast">U pajtuat te blogu</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>
|
||||
<!--RSS Feeds-->
|
||||
<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_updated">Përditësuar Së Fundi:</string>
|
||||
<string name="blogs_rss_remove_feed">Hiqe Prurjen</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Jeni i sigurt se doni të hiqet kjo prurje dhe krejt postimet e saj?\nÇfarëdo postimi që keni ndarë me të tjerët, nuk do të hiqet nga pajisjet e personave të tjerë.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Hiqe Prurjen</string>
|
||||
<string name="blogs_rss_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</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">S’ka 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>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Rrjete</string>
|
||||
@@ -331,12 +333,16 @@
|
||||
<string name="notification_settings_title">Njoftime</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_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_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_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_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_lock_screen_setting_title">Kyçe Ekranin</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_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="qr_code">Kod QR</string>
|
||||
<string name="show_qr_code_fullscreen">Shfaqe kodin QR sa tërë ekrani</string>
|
||||
</resources>
|
||||
|
||||
@@ -108,11 +108,13 @@
|
||||
<string name="dialog_button_leave">Tillbaka</string>
|
||||
<!--Forum Sharing-->
|
||||
<!--Blogs-->
|
||||
<string name="blogs_remove_blog_ok">&Ta bort</string>
|
||||
<!--Blog Sharing-->
|
||||
<!--RSS Feeds-->
|
||||
<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_updated">Senast uppdaterad:</string>
|
||||
<string name="blogs_rss_remove_feed_ok">&Ta bort</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Nätverk</string>
|
||||
<string name="bluetooth_setting">Anslut via Bluetooth</string>
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
</plurals>
|
||||
<string name="expiry_update">测试到期时间延长,您的帐户将在 %d 天后过期。</string>
|
||||
<string name="expiry_date_reached">本软件已过期。\n感谢您的测试!</string>
|
||||
<string name="startup_open_database">正在解密数据库……</string>
|
||||
<string name="startup_migrate_database">正在升级数据库……</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">打开抽屉式导航栏</string>
|
||||
<string name="nav_drawer_close_description">关闭抽屉式导航栏</string>
|
||||
@@ -199,7 +201,7 @@
|
||||
<string name="choose_forum_hint">为论坛命名</string>
|
||||
<string name="create_forum_button">创建论坛</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="other">%d 条帖子</item>
|
||||
@@ -241,7 +243,7 @@
|
||||
</plurals>
|
||||
<string name="nobody">没有人</string>
|
||||
<!--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="blogs_write_blog_post">写博文</string>
|
||||
<string name="blogs_write_blog_post_body_hint">在此输入博文</string>
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
<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_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_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_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 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_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">
|
||||
@@ -108,9 +108,9 @@
|
||||
<string name="sorry">Sorry</string>
|
||||
|
||||
<!-- 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="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="delete_contact">Delete contact</string>
|
||||
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
||||
@@ -160,7 +160,7 @@
|
||||
</plurals>
|
||||
|
||||
<!-- 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>
|
||||
<plurals name="messages">
|
||||
<item quantity="one">%d message</item>
|
||||
@@ -216,12 +216,12 @@
|
||||
<string name="groups_reveal_invisible">Contact relationship is not visible to the group</string>
|
||||
|
||||
<!-- 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="choose_forum_hint">Choose a name for your forum</string>
|
||||
<string name="create_forum_button">Create Forum</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>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d post</item>
|
||||
@@ -233,24 +233,24 @@
|
||||
<string name="btn_reply">Reply</string>
|
||||
<string name="forum_leave">Leave 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="forum_left_toast">Left Forum</string>
|
||||
<string name="forum_left_toast">Left forum</string>
|
||||
|
||||
<!-- Forum Sharing -->
|
||||
<string name="forum_share_button">Share Forum</string>
|
||||
<string name="contacts_selected">Contacts selected</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_share_message">Add a message (optional)</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_sent">You have shared the forum \"%1$s\" with %2$s.</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_joined_toast">Joined Forum</string>
|
||||
<string name="forum_declined_toast">Forum Invitation Declined</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_declined_toast">Invitation declined</string>
|
||||
<string name="shared_by_format">Shared by %s</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>
|
||||
@@ -268,19 +268,19 @@
|
||||
<string name="nobody">Nobody</string>
|
||||
|
||||
<!-- 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="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_blog_post_created">Blog Post Created</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_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_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_ok">Remove Blog</string>
|
||||
<string name="blogs_blog_removed">Blog Removed</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</string>
|
||||
<string name="blogs_blog_removed">Blog removed</string>
|
||||
<string name="blogs_reblog_comment_hint">Add a comment (optional)</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_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_joined_toast">Subscribed to Blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string>
|
||||
<string name="blogs_sharing_joined_toast">Subscribed to blog</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>
|
||||
|
||||
<!-- RSS Feeds -->
|
||||
@@ -310,10 +310,10 @@
|
||||
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Last Updated:</string>
|
||||
<string name="blogs_rss_remove_feed">Remove Feed</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Are you sure you want to remove this feed and all its posts?\nAny posts you have shared will not be removed from other people\'s devices.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Remove Feed</string>
|
||||
<string name="blogs_rss_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</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>
|
||||
|
||||
<!-- Settings Network -->
|
||||
@@ -353,12 +353,16 @@
|
||||
<string name="notification_settings_title">Notifications</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_26">Configure alerts for private 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_26">Configure alerts for group messages</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_26">Configure alerts for forum 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_26">Configure alerts for blog posts</string>
|
||||
<string name="notify_vibration_setting">Vibrate</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>
|
||||
@@ -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_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
|
||||
<string name="permission_camera_denied_toast">Camera permission was not granted</string>
|
||||
<string name="qr_code">QR code</string>
|
||||
<string name="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>
|
||||
|
||||
Reference in New Issue
Block a user