mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
44 Commits
beta-0.16.
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8e96a654 | ||
|
|
84e040605b | ||
|
|
b0aa1517e5 | ||
|
|
2ac9f567dc | ||
|
|
792cfd7d6f | ||
|
|
2112d4fa7d | ||
|
|
31ca04e070 | ||
|
|
9693a5cb93 | ||
|
|
82266345ae | ||
|
|
0942fe6053 | ||
|
|
4a1f58705d | ||
|
|
cfe0d9a656 | ||
|
|
3cf61e7b3d | ||
|
|
7bb7f8ad5b | ||
|
|
fc50bb1c6c | ||
|
|
19be4d6edf | ||
|
|
b2e4de91a4 | ||
|
|
9b184fe1d9 | ||
|
|
f4ddc01641 | ||
|
|
08b63201d9 | ||
|
|
1c41181f1c | ||
|
|
246b330b36 | ||
|
|
fd3e74cefc | ||
|
|
ef12191ec8 | ||
|
|
a9fc310762 | ||
|
|
0a70c2d44d | ||
|
|
af1fc6f095 | ||
|
|
21956f2627 | ||
|
|
55db6e524a | ||
|
|
dac3de24e7 | ||
|
|
f93f41893e | ||
|
|
7dacb43e01 | ||
|
|
6a962bad24 | ||
|
|
489c0154e9 | ||
|
|
85dc99da72 | ||
|
|
ec808fd9f7 | ||
|
|
4c661cd4bb | ||
|
|
6324fb72a5 | ||
|
|
d3aebc4aba | ||
|
|
65c0e110c5 | ||
|
|
67aeb40d34 | ||
|
|
8280b2e3b8 | ||
|
|
4e0b9145c1 | ||
|
|
0ad4f2f39b |
@@ -12,8 +12,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1618
|
||||
versionName "0.16.18"
|
||||
versionCode 1619
|
||||
versionName "0.16.19"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.reporting.DevReporter;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.api.system.LocationUtils;
|
||||
import org.briarproject.bramble.api.system.Scheduler;
|
||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||
|
||||
@@ -41,8 +41,9 @@ public class AndroidPluginModule {
|
||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||
EventBus eventBus) {
|
||||
Context appContext = app.getApplicationContext();
|
||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory bluetooth =
|
||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
||||
appContext, random, eventBus, backoffFactory);
|
||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||
appContext, locationUtils, reporter, eventBus,
|
||||
torSocketFactory, backoffFactory);
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
||||
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
super.start();
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialiseAdapter() throws IOException {
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
BluetoothAdapter::getDefaultAdapter).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
if (adapter == null)
|
||||
throw new IOException("Bluetooth is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAdapterEnabled() {
|
||||
return adapter != null && adapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
void enableAdapter() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void disableAdapterIfEnabledByUs() {
|
||||
if (isAdapterEnabled() && wasEnabledByUs) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
wasEnabledByUs = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setEnabledByUs() {
|
||||
wasEnabledByUs = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
String getBluetoothAddress() {
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||
return address.isEmpty() ? null : address;
|
||||
}
|
||||
|
||||
@Override
|
||||
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
|
||||
return adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", UUID.fromString(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
|
||||
throws IOException {
|
||||
return wrapSocket(ss.accept());
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new AndroidBluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isValidAddress(String address) {
|
||||
return BluetoothAdapter.checkBluetoothAddress(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
UUID u = UUID.fromString(uuid);
|
||||
BluetoothSocket s = null;
|
||||
try {
|
||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||
s.connect();
|
||||
return wrapSocket(s);
|
||||
} catch (IOException e) {
|
||||
tryToClose(s);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Closeable c) {
|
||||
try {
|
||||
if (c != null) c.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) onAdapterEnabled();
|
||||
else if (state == STATE_OFF) onAdapterDisabled();
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
LOG.info("Scan mode: None");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||
LOG.info("Scan mode: Connectable");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
LOG.info("Scan mode: Discoverable");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||
@@ -35,7 +35,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
private final EventBus eventBus;
|
||||
private final BackoffFactory backoffFactory;
|
||||
|
||||
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor, Context appContext,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
@@ -61,7 +61,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
|
||||
@@ -11,11 +11,12 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||
class AndroidBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final BluetoothSocket socket;
|
||||
|
||||
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||
super(plugin);
|
||||
this.socket = socket;
|
||||
}
|
||||
@@ -1,490 +0,0 @@
|
||||
package org.briarproject.bramble.plugin.droidtooth;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.PluginException;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.util.AndroidUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final Context appContext;
|
||||
private final SecureRandom secureRandom;
|
||||
private final Backoff backoff;
|
||||
private final DuplexPluginCallback callback;
|
||||
private final int maxLatency;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile boolean wasEnabledByUs = false;
|
||||
private volatile BluetoothStateReceiver receiver = null;
|
||||
private volatile BluetoothServerSocket socket = null;
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile BluetoothAdapter adapter = null;
|
||||
|
||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||
DuplexPluginCallback callback, int maxLatency) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.appContext = appContext;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoff = backoff;
|
||||
this.callback = callback;
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return maxLatency;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxIdleTime() {
|
||||
// Bluetooth detects dead connections so we don't need keepalives
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||
// with a message queue, so submit it to the AndroidExecutor
|
||||
try {
|
||||
adapter = androidExecutor.runOnBackgroundThread(
|
||||
BluetoothAdapter::getDefaultAdapter).get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOG.warning("Interrupted while getting BluetoothAdapter");
|
||||
throw new PluginException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
if (adapter == null) {
|
||||
LOG.info("Bluetooth is not supported");
|
||||
throw new PluginException();
|
||||
}
|
||||
running = true;
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
appContext.registerReceiver(receiver, filter);
|
||||
// If Bluetooth is enabled, bind a socket
|
||||
if (adapter.isEnabled()) {
|
||||
bind();
|
||||
} else {
|
||||
// Enable Bluetooth if settings allow
|
||||
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
||||
enableAdapter();
|
||||
} else {
|
||||
LOG.info("Not enabling Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!isRunning()) return;
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext,
|
||||
adapter);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, address);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
// Bind a server socket to accept connections from contacts
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", getUuid());
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
LOG.info("Socket bound");
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections();
|
||||
});
|
||||
}
|
||||
|
||||
private UUID getUuid() {
|
||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_UUID, uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
}
|
||||
return UUID.fromString(uuid);
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
callback.transportDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptContactConnections() {
|
||||
while (isRunning()) {
|
||||
BluetoothSocket s;
|
||||
try {
|
||||
s = socket.accept();
|
||||
} catch (IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
return;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
String address = s.getRemoteDevice().getAddress();
|
||||
LOG.info("Connection from " + scrubMacAddress(address));
|
||||
}
|
||||
backoff.reset();
|
||||
callback.incomingConnectionCreated(wrapSocket(s));
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private void enableAdapter() {
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
if (adapter.enable()) {
|
||||
LOG.info("Enabling Bluetooth");
|
||||
wasEnabledByUs = true;
|
||||
} else {
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||
tryToClose(socket);
|
||||
disableAdapter();
|
||||
}
|
||||
|
||||
private void disableAdapter() {
|
||||
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
|
||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||
else LOG.info("Could not disable Bluetooth");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running && adapter != null && adapter.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldPoll() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPollingInterval() {
|
||||
return backoff.getPollingInterval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!isRunning()) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
callback.getRemoteProperties();
|
||||
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
if (connected.contains(c)) continue;
|
||||
String address = e.getValue().get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||
String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BluetoothSocket connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
// not scrubbing here to be able to figure out the problem
|
||||
LOG.warning("Invalid address " + address);
|
||||
return null;
|
||||
}
|
||||
// Validate the UUID
|
||||
UUID u;
|
||||
try {
|
||||
u = UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
// Try to connect
|
||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||
BluetoothSocket s = null;
|
||||
try {
|
||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
s.connect();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connected to " + scrubMacAddress(address));
|
||||
return s;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
||||
+ ": " + e);
|
||||
}
|
||||
tryToClose(s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Closeable c) {
|
||||
try {
|
||||
if (c != null) c.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!isRunning()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
BluetoothSocket s = connect(address, uuid);
|
||||
if (s == null) return null;
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsKeyAgreement() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!isRunning()) return null;
|
||||
// There's no point listening if we can't discover our own address
|
||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||
if (address.isEmpty()) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
BluetoothServerSocket ss;
|
||||
try {
|
||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||
"RFCOMM", uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
descriptor.add(StringUtils.macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
if (!isRunning()) return null;
|
||||
String address;
|
||||
try {
|
||||
address = parseAddress(descriptor);
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid address in key agreement descriptor");
|
||||
return null;
|
||||
}
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
BluetoothSocket s = connect(address, uuid.toString());
|
||||
if (s == null) return null;
|
||||
return new DroidtoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
byte[] mac = descriptor.getRaw(1);
|
||||
if (mac.length != 6) throw new FormatException();
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
enableAdapterAsync();
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
disableAdapterAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableAdapterAsync() {
|
||||
ioExecutor.execute(this::enableAdapter);
|
||||
}
|
||||
|
||||
private void disableAdapterAsync() {
|
||||
ioExecutor.execute(this::disableAdapter);
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) {
|
||||
LOG.info("Bluetooth enabled");
|
||||
bind();
|
||||
} else if (state == STATE_OFF) {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
}
|
||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||
if (scanMode == SCAN_MODE_NONE) {
|
||||
LOG.info("Scan mode: None");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||
LOG.info("Scan mode: Connectable");
|
||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||
LOG.info("Scan mode: Discoverable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final BluetoothServerSocket ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
||||
BluetoothServerSocket ss) {
|
||||
super(descriptor);
|
||||
this.ss = ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return () -> {
|
||||
BluetoothSocket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new DroidtoothTransportConnection(
|
||||
DroidtoothPlugin.this, s), ID);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,7 +614,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public interface DatabaseComponent {
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open() throws DbException;
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
|
||||
/**
|
||||
* Waits for any open transactions to finish and closes the database.
|
||||
@@ -378,6 +378,16 @@ public interface DatabaseComponent {
|
||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/*
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
||||
* no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all settings in the given namespace.
|
||||
* <p/>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.bramble.api.db;
|
||||
|
||||
public interface MigrationListener {
|
||||
|
||||
/**
|
||||
* This is called when a migration is started while opening the database.
|
||||
* It will be called once for each migration being applied.
|
||||
*/
|
||||
void onMigrationRun();
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An class for managing a particular key agreement listener.
|
||||
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts listening for incoming connections, and returns a Callable that
|
||||
* will return a KeyAgreementConnection when an incoming connection is
|
||||
* received.
|
||||
* Blocks until an incoming connection is received and returns it.
|
||||
*
|
||||
* @throws IOException if an error occurs or {@link #close()} is called.
|
||||
*/
|
||||
public abstract Callable<KeyAgreementConnection> listen();
|
||||
public abstract KeyAgreementConnection accept() throws IOException;
|
||||
|
||||
/**
|
||||
* Closes the underlying server socket.
|
||||
|
||||
@@ -21,7 +21,25 @@ public interface LifecycleManager {
|
||||
* The result of calling {@link #startServices(String)}.
|
||||
*/
|
||||
enum StartResult {
|
||||
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
|
||||
ALREADY_RUNNING,
|
||||
DB_ERROR,
|
||||
DATA_TOO_OLD_ERROR,
|
||||
DATA_TOO_NEW_ERROR,
|
||||
SERVICE_ERROR,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
/**
|
||||
* The state the lifecycle can be in.
|
||||
* Returned by {@link #getLifecycleState()}
|
||||
*/
|
||||
enum LifecycleState {
|
||||
|
||||
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
|
||||
|
||||
public boolean isAfter(LifecycleState state) {
|
||||
return ordinal() > state.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,4 +89,10 @@ public interface LifecycleManager {
|
||||
* the {@link DatabaseComponent} to be closed before returning.
|
||||
*/
|
||||
void waitForShutdown() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Returns the current state of the lifecycle.
|
||||
*/
|
||||
LifecycleState getLifecycleState();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app enters a new lifecycle state.
|
||||
*/
|
||||
public class LifecycleEvent extends Event {
|
||||
|
||||
private final LifecycleState state;
|
||||
|
||||
public LifecycleEvent(LifecycleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.briarproject.bramble.api.lifecycle.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
|
||||
/**
|
||||
* An event that is broadcast when the app is shutting down.
|
||||
*/
|
||||
public class ShutdownEvent extends Event {
|
||||
}
|
||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||
* Returns null if no connection can be established within the given time.
|
||||
* Returns null if no connection can be established.
|
||||
*/
|
||||
@Nullable
|
||||
DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
||||
byte[] remoteCommitment, BdfList descriptor);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.bramble.api.plugin.event;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event that informs the Bluetooth plugin that we have enabled the
|
||||
* Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BluetoothEnabledEvent extends Event {
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||
*/
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
|
||||
@@ -126,6 +126,10 @@ public class StringUtils {
|
||||
return toUtf8(s).length > maxLength;
|
||||
}
|
||||
|
||||
public static boolean isValidMac(String mac) {
|
||||
return MAC.matcher(mac).matches();
|
||||
}
|
||||
|
||||
public static byte[] macToBytes(String mac) {
|
||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||
return fromHexString(mac.replaceAll(":", ""));
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -45,7 +46,7 @@ interface Database<T> {
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
boolean open() throws DbException;
|
||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||
|
||||
/**
|
||||
* Prevents new transactions from starting, waits for all current
|
||||
@@ -456,6 +457,16 @@ interface Database<T> {
|
||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||
* message is due to be sent to the given contact. The returned value may
|
||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||
* if no messages are scheduled to be sent.
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||
|
||||
/**
|
||||
* Returns the message with the given ID, in serialised form, or null if
|
||||
* the message has been deleted.
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||
@@ -100,8 +101,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() throws DbException {
|
||||
boolean reopened = db.open();
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopened = db.open(listener);
|
||||
shutdown.addShutdownHook(() -> {
|
||||
try {
|
||||
close();
|
||||
@@ -579,6 +581,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
return db.getMessageDependents(txn, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||
throws DbException {
|
||||
T txn = unbox(transaction);
|
||||
return db.getNextSendTime(txn, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings getSettings(Transaction transaction, String namespace)
|
||||
throws DbException {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
@@ -40,10 +42,11 @@ class H2Database extends JdbcDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean open() throws DbException {
|
||||
public boolean open(@Nullable MigrationListener listener)
|
||||
throws DbException {
|
||||
boolean reopen = config.databaseExists();
|
||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||
super.open("org.h2.Driver", reopen);
|
||||
super.open("org.h2.Driver", reopen, listener);
|
||||
return reopen;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DbClosedException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
@@ -279,7 +280,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected void open(String driverClass, boolean reopen) throws DbException {
|
||||
protected void open(String driverClass, boolean reopen,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
// Load the JDBC driver
|
||||
try {
|
||||
Class.forName(driverClass);
|
||||
@@ -290,7 +292,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
Connection txn = startTransaction();
|
||||
try {
|
||||
if (reopen) {
|
||||
checkSchemaVersion(txn);
|
||||
checkSchemaVersion(txn, listener);
|
||||
} else {
|
||||
createTables(txn);
|
||||
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||
@@ -313,7 +315,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
* @throws DataTooOldException if the data uses an older schema than the
|
||||
* current code and cannot be migrated
|
||||
*/
|
||||
private void checkSchemaVersion(Connection txn) throws DbException {
|
||||
private void checkSchemaVersion(Connection txn,
|
||||
@Nullable MigrationListener listener) throws DbException {
|
||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||
if (dataSchemaVersion == -1) throw new DbException();
|
||||
@@ -326,6 +329,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (start == dataSchemaVersion) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Migrating from schema " + start + " to " + end);
|
||||
if (listener != null) listener.onMigrationRun();
|
||||
// Apply the migration
|
||||
m.migrate(txn);
|
||||
// Store the new schema version
|
||||
@@ -1844,6 +1848,41 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNextSendTime(Connection txn, ContactId c)
|
||||
throws DbException {
|
||||
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"
|
||||
+ " ORDER BY expiry LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
long nextSendTime = Long.MAX_VALUE;
|
||||
if (rs.next()) {
|
||||
nextSendTime = rs.getLong(1);
|
||||
if (rs.next()) throw new AssertionError();
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
return nextSendTime;
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] getRawMessage(Connection txn, MessageId m)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
interface ConnectionChooser {
|
||||
|
||||
/**
|
||||
* Submits a connection task to the chooser.
|
||||
*/
|
||||
void submit(Callable<KeyAgreementConnection> task);
|
||||
|
||||
/**
|
||||
* Returns a connection returned by any of the tasks submitted to the
|
||||
* chooser, waiting up to the given amount of time for a connection if
|
||||
* necessary. Returns null if the time elapses without a connection
|
||||
* becoming available.
|
||||
*
|
||||
* @param timeout the timeout in milliseconds
|
||||
* @throws InterruptedException if the thread is interrupted while waiting
|
||||
* for a connection to become available
|
||||
*/
|
||||
@Nullable
|
||||
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Stops the chooser. Any connections already returned to the chooser are
|
||||
* closed unless they have been removed from the chooser by calling
|
||||
* {@link #poll(long)}. Any connections subsequently returned to the
|
||||
* chooser will also be closed.
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@NotNullByDefault
|
||||
@ThreadSafe
|
||||
class ConnectionChooserImpl implements ConnectionChooser {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
||||
|
||||
private final Clock clock;
|
||||
private final Executor ioExecutor;
|
||||
private final Object lock = new Object();
|
||||
|
||||
// The following are locking: lock
|
||||
private boolean stopped = false;
|
||||
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
|
||||
|
||||
@Inject
|
||||
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
||||
this.clock = clock;
|
||||
this.ioExecutor = ioExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(Callable<KeyAgreementConnection> task) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
KeyAgreementConnection c = task.call();
|
||||
if (c != null) addResult(c);
|
||||
} catch (Exception e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection poll(long timeout)
|
||||
throws InterruptedException {
|
||||
long now = clock.currentTimeMillis();
|
||||
long end = now + timeout;
|
||||
synchronized (lock) {
|
||||
while (!stopped && results.isEmpty() && now < end) {
|
||||
lock.wait(end - now);
|
||||
now = clock.currentTimeMillis();
|
||||
}
|
||||
return results.poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
List<KeyAgreementConnection> unused;
|
||||
synchronized (lock) {
|
||||
unused = new ArrayList<>(results);
|
||||
results.clear();
|
||||
stopped = true;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing " + unused.size() + " unused connections");
|
||||
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
|
||||
}
|
||||
|
||||
private void addResult(KeyAgreementConnection c) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Got connection for " + c.getTransportId());
|
||||
boolean close = false;
|
||||
synchronized (lock) {
|
||||
if (stopped) {
|
||||
close = true;
|
||||
} else {
|
||||
results.add(c);
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
if (close) {
|
||||
LOG.info("Already stopped");
|
||||
tryToClose(c.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn) {
|
||||
try {
|
||||
conn.getReader().dispose(false, true);
|
||||
conn.getWriter().dispose(false);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,23 +13,19 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||
@@ -45,29 +41,27 @@ class KeyAgreementConnector {
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final CompletionService<KeyAgreementConnection> connect;
|
||||
private final ConnectionChooser connectionChooser;
|
||||
|
||||
private final List<KeyAgreementListener> listeners = new ArrayList<>();
|
||||
private final List<Future<KeyAgreementConnection>> pending =
|
||||
new ArrayList<>();
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
||||
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean connecting = false;
|
||||
private volatile boolean alice = false;
|
||||
private volatile boolean alice = false, stopped = false;
|
||||
|
||||
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||
KeyAgreementConnector(Callbacks callbacks,
|
||||
CryptoComponent crypto, PluginManager pluginManager,
|
||||
Executor ioExecutor) {
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.callbacks = callbacks;
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.pluginManager = pluginManager;
|
||||
connect = new ExecutorCompletionService<>(ioExecutor);
|
||||
this.connectionChooser = connectionChooser;
|
||||
}
|
||||
|
||||
public Payload listen(KeyPair localKeyPair) {
|
||||
Payload listen(KeyPair localKeyPair) {
|
||||
LOG.info("Starting BQP listeners");
|
||||
// Derive commitment
|
||||
byte[] commitment = crypto.deriveKeyCommitment(
|
||||
@@ -80,8 +74,9 @@ class KeyAgreementConnector {
|
||||
if (l != null) {
|
||||
TransportId id = plugin.getId();
|
||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||
pending.add(connect.submit(new ReadableTask(l.listen())));
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
||||
listeners.add(l);
|
||||
connectionChooser.submit(new ReadableTask(l::accept));
|
||||
}
|
||||
}
|
||||
return new Payload(commitment, descriptors);
|
||||
@@ -89,125 +84,92 @@ class KeyAgreementConnector {
|
||||
|
||||
void stopListening() {
|
||||
LOG.info("Stopping BQP listeners");
|
||||
for (KeyAgreementListener l : listeners) {
|
||||
l.close();
|
||||
}
|
||||
listeners.clear();
|
||||
stopped = true;
|
||||
aliceLatch.countDown();
|
||||
for (KeyAgreementListener l : listeners) l.close();
|
||||
connectionChooser.stop();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyAgreementTransport connect(Payload remotePayload,
|
||||
boolean alice) {
|
||||
// Let the listeners know if we are Alice
|
||||
this.connecting = true;
|
||||
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
||||
// Let the ReadableTasks know if we are Alice
|
||||
this.alice = alice;
|
||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
aliceLatch.countDown();
|
||||
|
||||
// Start connecting over supported transports
|
||||
LOG.info("Starting outgoing BQP connections");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Starting outgoing BQP connections as "
|
||||
+ (alice ? "Alice" : "Bob"));
|
||||
}
|
||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||
Plugin p = pluginManager.getPlugin(d.getId());
|
||||
if (p instanceof DuplexPlugin) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting via " + d.getId());
|
||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||
pending.add(connect.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
||||
d.getDescriptor(), end))));
|
||||
byte[] commitment = remotePayload.getCommitment();
|
||||
BdfList descriptor = d.getDescriptor();
|
||||
connectionChooser.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, commitment, descriptor)));
|
||||
}
|
||||
}
|
||||
|
||||
// Get chosen connection
|
||||
KeyAgreementConnection chosen = null;
|
||||
try {
|
||||
long now = clock.currentTimeMillis();
|
||||
Future<KeyAgreementConnection> f =
|
||||
connect.poll(end - now, MILLISECONDS);
|
||||
if (f == null)
|
||||
return null; // No task completed within the timeout.
|
||||
chosen = f.get();
|
||||
KeyAgreementConnection chosen =
|
||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
||||
if (chosen == null) return null;
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException | IOException e) {
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
stopListening();
|
||||
// Close all other connections
|
||||
closePending(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closePending(@Nullable KeyAgreementConnection chosen) {
|
||||
for (Future<KeyAgreementConnection> f : pending) {
|
||||
try {
|
||||
if (f.cancel(true)) {
|
||||
LOG.info("Cancelled task");
|
||||
} else if (!f.isCancelled()) {
|
||||
KeyAgreementConnection c = f.get();
|
||||
if (c != null && c != chosen)
|
||||
tryToClose(c.getConnection(), false);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing connection, exception: " + exception);
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
private void waitingForAlice() {
|
||||
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
||||
}
|
||||
|
||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final byte[] commitment;
|
||||
private final BdfList descriptor;
|
||||
private final long end;
|
||||
private final DuplexPlugin plugin;
|
||||
|
||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||
BdfList descriptor, long end) {
|
||||
BdfList descriptor) {
|
||||
this.plugin = plugin;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect, get interrupted, or time out
|
||||
while (true) {
|
||||
long now = clock.currentTimeMillis();
|
||||
if (now > end) throw new IOException();
|
||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||
while (!stopped) {
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment,
|
||||
descriptor, end - now);
|
||||
descriptor);
|
||||
if (conn != null) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(plugin.getId().getString() +
|
||||
": Outgoing connection");
|
||||
LOG.info(plugin.getId() + ": Outgoing connection");
|
||||
return new KeyAgreementConnection(conn, plugin.getId());
|
||||
}
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadableTask
|
||||
implements Callable<KeyAgreementConnection> {
|
||||
private class ReadableTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final Callable<KeyAgreementConnection> connectionTask;
|
||||
|
||||
@@ -215,24 +177,23 @@ class KeyAgreementConnector {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
KeyAgreementConnection c = connectionTask.call();
|
||||
if (c == null) return null;
|
||||
aliceLatch.await();
|
||||
if (alice || stopped) return c;
|
||||
// Bob waits here for Alice to scan his QR code, determine her
|
||||
// role, and send her key
|
||||
InputStream in = c.getConnection().getReader().getInputStream();
|
||||
boolean waitingSent = false;
|
||||
while (!alice && in.available() == 0) {
|
||||
if (!waitingSent && connecting && !alice) {
|
||||
// Bob waits here until Alice obtains his payload.
|
||||
callbacks.connectionWaiting();
|
||||
waitingSent = true;
|
||||
}
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(c.getTransportId().getString() +
|
||||
": Waiting for connection");
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
while (!stopped && in.available() == 0) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId() + ": Waiting for data");
|
||||
waitingForAlice();
|
||||
Thread.sleep(500);
|
||||
}
|
||||
if (!alice && LOG.isLoggable(INFO))
|
||||
if (!stopped && LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@@ -22,13 +13,9 @@ import dagger.Provides;
|
||||
public class KeyAgreementModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||
CryptoComponent crypto, EventBus eventBus,
|
||||
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager) {
|
||||
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||
ioExecutor, payloadEncoder, pluginManager);
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
|
||||
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
|
||||
return keyAgreementTaskFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -40,4 +27,10 @@ public class KeyAgreementModule {
|
||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||
return new PayloadParserImpl(bdfReaderFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ConnectionChooser provideConnectionChooser(
|
||||
ConnectionChooserImpl connectionChooser) {
|
||||
return connectionChooser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ class KeyAgreementProtocol {
|
||||
byte[] theirPublicKey;
|
||||
if (alice) {
|
||||
sendKey();
|
||||
// Alice waits here until Bob obtains her payload.
|
||||
// Alice waits here for Bob to scan her QR code, determine his
|
||||
// role, receive her key and respond with his key
|
||||
callbacks.connectionWaiting();
|
||||
theirPublicKey = receiveKey();
|
||||
} else {
|
||||
|
||||
@@ -5,42 +5,37 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final EventBus eventBus;
|
||||
private final Executor ioExecutor;
|
||||
private final PayloadEncoder payloadEncoder;
|
||||
private final PluginManager pluginManager;
|
||||
private final Provider<ConnectionChooser> connectionChooserProvider;
|
||||
|
||||
@Inject
|
||||
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||
this.clock = clock;
|
||||
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
Provider<ConnectionChooser> connectionChooserProvider) {
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
this.pluginManager = pluginManager;
|
||||
this.connectionChooserProvider = connectionChooserProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementTask createTask() {
|
||||
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||
pluginManager, ioExecutor);
|
||||
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
|
||||
pluginManager, connectionChooserProvider.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,16 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class KeyAgreementTaskImpl extends Thread implements
|
||||
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
||||
KeyAgreementProtocol.Callbacks {
|
||||
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
||||
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||
@@ -43,15 +40,15 @@ class KeyAgreementTaskImpl extends Thread implements
|
||||
private Payload localPayload;
|
||||
private Payload remotePayload;
|
||||
|
||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager, Executor ioExecutor) {
|
||||
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||
ConnectionChooser connectionChooser) {
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||
pluginManager, ioExecutor);
|
||||
connector = new KeyAgreementConnector(this, crypto, pluginManager,
|
||||
connectionChooser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,10 +62,8 @@ class KeyAgreementTaskImpl extends Thread implements
|
||||
@Override
|
||||
public synchronized void stopListening() {
|
||||
if (localPayload != null) {
|
||||
if (remotePayload == null)
|
||||
connector.stopListening();
|
||||
else
|
||||
interrupt();
|
||||
if (remotePayload == null) connector.stopListening();
|
||||
else interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ package org.briarproject.bramble.lifecycle;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.MigrationListener;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
@@ -12,7 +15,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
|
||||
@@ -29,14 +32,21 @@ import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class LifecycleManagerImpl implements LifecycleManager {
|
||||
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
||||
@@ -54,6 +64,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
private volatile LifecycleState state = STARTING;
|
||||
|
||||
@Inject
|
||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||
CryptoComponent crypto, AuthorFactory authorFactory,
|
||||
@@ -119,7 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
LOG.info("Starting services");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
boolean reopened = db.open();
|
||||
boolean reopened = db.open(this);
|
||||
long duration = System.currentTimeMillis() - start;
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
if (reopened)
|
||||
@@ -131,7 +143,10 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
registerLocalAuthor(createLocalAuthor(nickname));
|
||||
}
|
||||
|
||||
state = STARTING_SERVICES;
|
||||
dbLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
for (Client c : clients) {
|
||||
@@ -157,8 +172,17 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
+ " took " + duration + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
state = RUNNING;
|
||||
startupLatch.countDown();
|
||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
||||
return SUCCESS;
|
||||
} catch (DataTooOldException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_OLD_ERROR;
|
||||
} catch (DataTooNewException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DATA_TOO_NEW_ERROR;
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return DB_ERROR;
|
||||
@@ -170,6 +194,12 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMigrationRun() {
|
||||
state = MIGRATING_DATABASE;
|
||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
try {
|
||||
@@ -180,7 +210,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
}
|
||||
try {
|
||||
LOG.info("Stopping services");
|
||||
eventBus.broadcast(new ShutdownEvent());
|
||||
state = STOPPING;
|
||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
||||
for (Service s : services) {
|
||||
long start = System.currentTimeMillis();
|
||||
s.stopService();
|
||||
@@ -225,4 +256,8 @@ class LifecycleManagerImpl implements LifecycleManager {
|
||||
shutdownLatch.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.briarproject.bramble.plugin.bluetooth;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -13,8 +15,11 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.util.OsUtils;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -23,30 +28,25 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static javax.bluetooth.DiscoveryAgent.GIAC;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class BluetoothPlugin implements DuplexPlugin {
|
||||
abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BluetoothPlugin.class.getName());
|
||||
@@ -58,9 +58,38 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
private final int maxLatency;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
private volatile boolean running = false;
|
||||
private volatile StreamConnectionNotifier socket = null;
|
||||
private volatile LocalDevice localDevice = null;
|
||||
private volatile boolean running = false, contactConnections = false;
|
||||
private volatile String contactConnectionsUuid = null;
|
||||
private volatile SS socket = null;
|
||||
|
||||
abstract void initialiseAdapter() throws IOException;
|
||||
|
||||
abstract boolean isAdapterEnabled();
|
||||
|
||||
abstract void enableAdapter();
|
||||
|
||||
abstract void disableAdapterIfEnabledByUs();
|
||||
|
||||
abstract void setEnabledByUs();
|
||||
|
||||
/**
|
||||
* Returns the local Bluetooth address, or null if no valid address can
|
||||
* be found.
|
||||
*/
|
||||
@Nullable
|
||||
abstract String getBluetoothAddress();
|
||||
|
||||
abstract SS openServerSocket(String uuid) throws IOException;
|
||||
|
||||
abstract void tryToClose(@Nullable SS ss);
|
||||
|
||||
abstract DuplexTransportConnection acceptConnection(SS ss)
|
||||
throws IOException;
|
||||
|
||||
abstract boolean isValidAddress(String address);
|
||||
|
||||
abstract DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException;
|
||||
|
||||
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
@@ -71,6 +100,19 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
this.maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
void onAdapterEnabled() {
|
||||
LOG.info("Bluetooth enabled");
|
||||
// We may not have been able to get the local address before
|
||||
ioExecutor.execute(this::updateProperties);
|
||||
if (shouldAllowContactConnections()) bind();
|
||||
}
|
||||
|
||||
void onAdapterDisabled() {
|
||||
LOG.info("Bluetooth disabled");
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return ID;
|
||||
@@ -90,107 +132,103 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
@Override
|
||||
public void start() throws PluginException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
// Initialise the Bluetooth stack
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
// On Linux the user may need to install libbluetooth-dev
|
||||
if (OsUtils.isLinux())
|
||||
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
|
||||
throw new PluginException(e);
|
||||
} catch (BluetoothStateException e) {
|
||||
initialiseAdapter();
|
||||
} catch (IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + localDevice.getBluetoothAddress());
|
||||
updateProperties();
|
||||
running = true;
|
||||
bind();
|
||||
loadSettings();
|
||||
if (shouldAllowContactConnections()) {
|
||||
if (isAdapterEnabled()) bind();
|
||||
else enableAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSettings() {
|
||||
contactConnections =
|
||||
callback.getSettings().getBoolean(PREF_BT_ENABLE, false);
|
||||
}
|
||||
|
||||
private boolean shouldAllowContactConnections() {
|
||||
return contactConnections;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
// Advertise the Bluetooth address to contacts
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
|
||||
callback.mergeLocalProperties(p);
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
// Bind a server socket to accept connections from contacts
|
||||
String url = makeUrl("localhost", getUuid());
|
||||
StreamConnectionNotifier ss;
|
||||
SS ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
ss = openServerSocket(contactConnectionsUuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return;
|
||||
}
|
||||
if (!running) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) {
|
||||
tryToClose(ss);
|
||||
return;
|
||||
}
|
||||
socket = ss;
|
||||
backoff.reset();
|
||||
callback.transportEnabled();
|
||||
acceptContactConnections(ss);
|
||||
acceptContactConnections();
|
||||
});
|
||||
}
|
||||
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private String getUuid() {
|
||||
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
||||
private void updateProperties() {
|
||||
TransportProperties p = callback.getLocalProperties();
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
String uuid = p.get(PROP_UUID);
|
||||
boolean changed = false;
|
||||
if (address == null) {
|
||||
address = getBluetoothAddress();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Local address " + scrubMacAddress(address));
|
||||
if (!StringUtils.isNullOrEmpty(address)) {
|
||||
p.put(PROP_ADDRESS, address);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (uuid == null) {
|
||||
byte[] random = new byte[UUID_BYTES];
|
||||
secureRandom.nextBytes(random);
|
||||
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||
TransportProperties p = new TransportProperties();
|
||||
p.put(PROP_UUID, uuid);
|
||||
callback.mergeLocalProperties(p);
|
||||
changed = true;
|
||||
}
|
||||
return uuid;
|
||||
contactConnectionsUuid = uuid;
|
||||
if (changed) callback.mergeLocalProperties(p);
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
} finally {
|
||||
callback.transportDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void acceptContactConnections(StreamConnectionNotifier ss) {
|
||||
private void acceptContactConnections() {
|
||||
while (true) {
|
||||
StreamConnection s;
|
||||
DuplexTransportConnection conn;
|
||||
try {
|
||||
s = ss.acceptAndOpen();
|
||||
conn = acceptConnection(socket);
|
||||
} catch (IOException e) {
|
||||
// This is expected when the socket is closed
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
return;
|
||||
}
|
||||
backoff.reset();
|
||||
callback.incomingConnectionCreated(wrapSocket(s));
|
||||
callback.incomingConnectionCreated(conn);
|
||||
if (!running) return;
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
running = false;
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
disableAdapterIfEnabledByUs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
return running && isAdapterEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -205,7 +243,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public void poll(Collection<ContactId> connected) {
|
||||
if (!running) return;
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
backoff.increment();
|
||||
// Try to connect to known devices in parallel
|
||||
Map<ContactId, TransportProperties> remote =
|
||||
@@ -218,41 +256,56 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
String uuid = e.getValue().get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||
ioExecutor.execute(() -> {
|
||||
if (!running) return;
|
||||
StreamConnection s = connect(makeUrl(address, uuid));
|
||||
if (s != null) {
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return;
|
||||
DuplexTransportConnection conn = connect(address, uuid);
|
||||
if (conn != null) {
|
||||
backoff.reset();
|
||||
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||
callback.outgoingConnectionCreated(c, conn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private StreamConnection connect(String url) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url);
|
||||
private DuplexTransportConnection connect(String address, String uuid) {
|
||||
// Validate the address
|
||||
if (!isValidAddress(address)) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
// Not scrubbing here to be able to figure out the problem
|
||||
LOG.warning("Invalid address " + address);
|
||||
return null;
|
||||
}
|
||||
// Validate the UUID
|
||||
try {
|
||||
StreamConnection s = (StreamConnection) Connector.open(url);
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url);
|
||||
return s;
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||
return null;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||
try {
|
||||
DuplexTransportConnection conn = connectTo(address, uuid);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connected to " + scrubMacAddress(address));
|
||||
return conn;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Could not connect to " + scrubMacAddress(address));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createConnection(ContactId c) {
|
||||
if (!running) return null;
|
||||
if (!isRunning() || !shouldAllowContactConnections()) return null;
|
||||
TransportProperties p = callback.getRemoteProperties(c);
|
||||
String address = p.get(PROP_ADDRESS);
|
||||
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||
String uuid = p.get(PROP_UUID);
|
||||
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -262,35 +315,34 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||
if (!running) return null;
|
||||
if (!isRunning()) return null;
|
||||
// There's no point listening if we can't discover our own address
|
||||
String address = getBluetoothAddress();
|
||||
if (address == null) return null;
|
||||
// No truncation necessary because COMMIT_LENGTH = 16
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||
String url = makeUrl("localhost", uuid);
|
||||
// Make the device discoverable if possible
|
||||
makeDeviceDiscoverable();
|
||||
// Bind a server socket for receiving key agreementconnections
|
||||
StreamConnectionNotifier ss;
|
||||
// Bind a server socket for receiving key agreement connections
|
||||
SS ss;
|
||||
try {
|
||||
ss = (StreamConnectionNotifier) Connector.open(url);
|
||||
ss = openServerSocket(uuid);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
if (!running) {
|
||||
if (!isRunning()) {
|
||||
tryToClose(ss);
|
||||
return null;
|
||||
}
|
||||
BdfList descriptor = new BdfList();
|
||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||
String address = localDevice.getBluetoothAddress();
|
||||
descriptor.add(StringUtils.macToBytes(address));
|
||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
if (!isRunning()) return null;
|
||||
String address;
|
||||
try {
|
||||
@@ -303,10 +355,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||
String url = makeUrl(address, uuid);
|
||||
StreamConnection s = connect(url);
|
||||
if (s == null) return null;
|
||||
return new BluetoothTransportConnection(this, s);
|
||||
return connect(address, uuid);
|
||||
}
|
||||
|
||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||
@@ -315,44 +364,56 @@ class BluetoothPlugin implements DuplexPlugin {
|
||||
return StringUtils.macToString(mac);
|
||||
}
|
||||
|
||||
private void makeDeviceDiscoverable() {
|
||||
// Try to make the device discoverable (requires root on Linux)
|
||||
try {
|
||||
localDevice.setDiscoverable(GIAC);
|
||||
} catch (BluetoothStateException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof EnableBluetoothEvent) {
|
||||
ioExecutor.execute(this::enableAdapter);
|
||||
} else if (e instanceof DisableBluetoothEvent) {
|
||||
ioExecutor.execute(this::disableAdapterIfEnabledByUs);
|
||||
} else if (e instanceof BluetoothEnabledEvent) {
|
||||
setEnabledByUs();
|
||||
} else if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
if (s.getNamespace().equals(ID.getString()))
|
||||
ioExecutor.execute(this::onSettingsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSettingsUpdated() {
|
||||
boolean wasAllowed = shouldAllowContactConnections();
|
||||
loadSettings();
|
||||
boolean isAllowed = shouldAllowContactConnections();
|
||||
if (wasAllowed && !isAllowed) {
|
||||
LOG.info("Contact connections disabled");
|
||||
tryToClose(socket);
|
||||
callback.transportDisabled();
|
||||
disableAdapterIfEnabledByUs();
|
||||
} else if (!wasAllowed && isAllowed) {
|
||||
LOG.info("Contact connections enabled");
|
||||
if (isAdapterEnabled()) bind();
|
||||
else enableAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||
|
||||
private final StreamConnectionNotifier ss;
|
||||
private final SS ss;
|
||||
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
||||
StreamConnectionNotifier ss) {
|
||||
private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
|
||||
super(descriptor);
|
||||
this.ss = ss;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return () -> {
|
||||
StreamConnection s = ss.acceptAndOpen();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new BluetoothTransportConnection(
|
||||
BluetoothPlugin.this, s), ID);
|
||||
};
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
DuplexTransportConnection conn = acceptConnection(ss);
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
return new KeyAgreementConnection(conn, ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
tryToClose(ss);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -224,7 +223,7 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
if (!isRunning()) return null;
|
||||
InetSocketAddress remote;
|
||||
try {
|
||||
@@ -283,14 +282,11 @@ class LanTcpPlugin extends TcpPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Callable<KeyAgreementConnection> listen() {
|
||||
return () -> {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(ID.getString() + ": Incoming connection");
|
||||
return new KeyAgreementConnection(
|
||||
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
|
||||
};
|
||||
public KeyAgreementConnection accept() throws IOException {
|
||||
Socket s = ss.accept();
|
||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
||||
return new KeyAgreementConnection(new TcpTransportConnection(
|
||||
LanTcpPlugin.this, s), ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -297,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
@@ -30,6 +30,7 @@ import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
@@ -37,6 +38,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -50,13 +52,14 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
|
||||
@NotNullByDefault
|
||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
// Check for retransmittable records once every 60 seconds
|
||||
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||
|
||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||
};
|
||||
private static final ThrowingRunnable<IOException>
|
||||
NEXT_SEND_TIME_DECREASED = () -> {
|
||||
};
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final Executor dbExecutor;
|
||||
@@ -72,6 +75,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
||||
private final AtomicBoolean generateRequestQueued =
|
||||
new AtomicBoolean(false);
|
||||
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
@@ -101,15 +105,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
generateRequest();
|
||||
long now = clock.currentTimeMillis();
|
||||
long nextKeepalive = now + maxIdleTime;
|
||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
boolean dataToFlush = true;
|
||||
// Write records until interrupted
|
||||
try {
|
||||
while (!interrupted) {
|
||||
// Work out how long we should wait for a record
|
||||
now = clock.currentTimeMillis();
|
||||
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
||||
if (wait < 0) wait = 0;
|
||||
long keepaliveWait = Math.max(0, nextKeepalive - now);
|
||||
long sendWait = Math.max(0, nextSendTime.get() - now);
|
||||
long wait = Math.min(keepaliveWait, sendWait);
|
||||
// Flush any unflushed data if we're going to wait
|
||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||
recordWriter.flush();
|
||||
@@ -121,20 +125,25 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
MILLISECONDS);
|
||||
if (task == null) {
|
||||
now = clock.currentTimeMillis();
|
||||
if (now >= nextRetxQuery) {
|
||||
// Check for retransmittable records
|
||||
if (now >= nextSendTime.get()) {
|
||||
// Check for retransmittable messages
|
||||
LOG.info("Checking for retransmittable messages");
|
||||
setNextSendTime(Long.MAX_VALUE);
|
||||
generateBatch();
|
||||
generateOffer();
|
||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
}
|
||||
if (now >= nextKeepalive) {
|
||||
// Flush the stream to keep it alive
|
||||
LOG.info("Sending keepalive");
|
||||
recordWriter.flush();
|
||||
dataToFlush = false;
|
||||
nextKeepalive = now + maxIdleTime;
|
||||
}
|
||||
} else if (task == CLOSE) {
|
||||
LOG.info("Closed");
|
||||
break;
|
||||
} else if (task == NEXT_SEND_TIME_DECREASED) {
|
||||
LOG.info("Next send time decreased");
|
||||
} else {
|
||||
task.run();
|
||||
dataToFlush = true;
|
||||
@@ -170,6 +179,11 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
|
||||
private void setNextSendTime(long time) {
|
||||
long old = nextSendTime.getAndSet(time);
|
||||
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
interrupted = true;
|
||||
@@ -196,8 +210,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
} else if (e instanceof MessageToRequestEvent) {
|
||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||
generateRequest();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +274,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
try {
|
||||
b = db.generateRequestedBatch(txn, contactId,
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -305,6 +321,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
try {
|
||||
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
||||
maxLatency);
|
||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
@@ -27,6 +27,7 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
|
||||
/**
|
||||
* An incoming {@link SyncSession}.
|
||||
@@ -96,8 +97,9 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
@@ -28,6 +28,7 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
@@ -109,8 +110,9 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) interrupt();
|
||||
} else if (e instanceof ShutdownEvent) {
|
||||
interrupt();
|
||||
} else if (e instanceof LifecycleEvent) {
|
||||
LifecycleEvent l = (LifecycleEvent) e;
|
||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
int shutdownHandle = 12345;
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open();
|
||||
oneOf(database).open(null);
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -199,7 +199,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalAuthor(transaction, localAuthor);
|
||||
@@ -1464,7 +1464,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
context.checking(new Expectations() {{
|
||||
// open()
|
||||
oneOf(database).open();
|
||||
oneOf(database).open(null);
|
||||
will(returnValue(false));
|
||||
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
||||
will(returnValue(shutdownHandle));
|
||||
@@ -1511,7 +1511,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.addLocalMessage(transaction, message, metadata, true);
|
||||
|
||||
@@ -62,7 +62,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
public void testDoesNotRunMigrationsWhenCreatingDatabase()
|
||||
throws Exception {
|
||||
Database<Connection> db = createDatabase(singletonList(migration));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -72,14 +72,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, -1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open();
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,12 +87,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
// Reopen the DB - migrations should not be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open());
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -101,14 +101,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open();
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
@@ -116,13 +116,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
throws Exception {
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(emptyList());
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(emptyList());
|
||||
db.open();
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test(expected = DataTooOldException.class)
|
||||
@@ -141,14 +141,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
|
||||
db.close();
|
||||
// Reopen the DB - an exception should be thrown
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
db.open();
|
||||
db.open(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,14 +170,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||
db.close();
|
||||
// Reopen the DB - the first migration should be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open());
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
@@ -202,14 +202,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||
|
||||
// Open the DB for the first time
|
||||
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||
assertFalse(db.open());
|
||||
assertFalse(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
// Override the data schema version
|
||||
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||
db.close();
|
||||
// Reopen the DB - both migrations should be run
|
||||
db = createDatabase(asList(migration, migration1));
|
||||
assertTrue(db.open());
|
||||
assertTrue(db.open(null));
|
||||
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
@@ -1635,6 +1636,53 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNextSendTime() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
Database<Connection> db = open(false, new StoppedClock(now));
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false);
|
||||
|
||||
// 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
|
||||
db.setMessageState(txn, messageId, DELIVERED);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the message - now it should be sendable immediately
|
||||
db.setMessageShared(txn, 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);
|
||||
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Update the message's expiry time again - now it should be sendable
|
||||
// after two round-trips
|
||||
db.updateExpiryTime(txn, contactId, messageId, 1000);
|
||||
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Delete the message - there should be no messages to send
|
||||
db.deleteMessage(txn, messageId);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionHandling() throws Exception {
|
||||
Database<Connection> db = open(false);
|
||||
@@ -1652,10 +1700,15 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume) throws Exception {
|
||||
return open(resume, new SystemClock());
|
||||
}
|
||||
|
||||
private Database<Connection> open(boolean resume, Clock clock)
|
||||
throws Exception {
|
||||
Database<Connection> db = new H2Database(new TestDatabaseConfig(testDir,
|
||||
MAX_SIZE), new SystemClock());
|
||||
MAX_SIZE), clock);
|
||||
if (!resume) TestUtils.deleteTestDirectory(testDir);
|
||||
db.open();
|
||||
db.open(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
@@ -1683,4 +1736,23 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private static class StoppedClock implements Clock {
|
||||
|
||||
private final long time;
|
||||
|
||||
private StoppedClock(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentTimeMillis() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sleep(long milliseconds) throws InterruptedException {
|
||||
Thread.sleep(milliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.tcp;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
@@ -26,11 +25,9 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
@@ -198,9 +195,16 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
KeyAgreementListener kal =
|
||||
plugin.createKeyAgreementListener(new byte[COMMIT_LENGTH]);
|
||||
assertNotNull(kal);
|
||||
Callable<KeyAgreementConnection> c = kal.listen();
|
||||
FutureTask<KeyAgreementConnection> f = new FutureTask<>(c);
|
||||
new Thread(f).start();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean error = new AtomicBoolean(false);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
kal.accept();
|
||||
latch.countDown();
|
||||
} catch (IOException e) {
|
||||
error.set(true);
|
||||
}
|
||||
}).start();
|
||||
// The plugin should have bound a socket and stored the port number
|
||||
BdfList descriptor = kal.getDescriptor();
|
||||
assertEquals(3, descriptor.size());
|
||||
@@ -216,10 +220,12 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
InetSocketAddress socketAddr = new InetSocketAddress(addr, port);
|
||||
Socket s = new Socket();
|
||||
s.connect(socketAddr, 100);
|
||||
assertNotNull(f.get(5, SECONDS));
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
assertFalse(error.get());
|
||||
// Clean up
|
||||
s.close();
|
||||
kal.close();
|
||||
// Stop the plugin
|
||||
plugin.stop();
|
||||
}
|
||||
|
||||
@@ -268,7 +274,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
||||
descriptor.add(local.getPort());
|
||||
// Connect to the port
|
||||
DuplexTransportConnection d = plugin.createKeyAgreementConnection(
|
||||
new byte[COMMIT_LENGTH], descriptor, 5000);
|
||||
new byte[COMMIT_LENGTH], descriptor);
|
||||
assertNotNull(d);
|
||||
// Check that the connection was accepted
|
||||
assertTrue(latch.await(5, SECONDS));
|
||||
|
||||
@@ -17,6 +17,8 @@ import javax.inject.Singleton;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
||||
|
||||
@Module
|
||||
public class TestLifecycleModule {
|
||||
|
||||
@@ -57,6 +59,11 @@ public class TestLifecycleModule {
|
||||
@Override
|
||||
public void waitForShutdown() throws InterruptedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleState getLifecycleState() {
|
||||
return RUNNING;
|
||||
}
|
||||
};
|
||||
return lifecycleManager;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -8,7 +9,7 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.BluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDrivePluginFactory;
|
||||
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
|
||||
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
|
||||
@@ -30,9 +31,10 @@ public class DesktopPluginModule extends PluginModule {
|
||||
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
|
||||
SecureRandom random, BackoffFactory backoffFactory,
|
||||
ReliabilityLayerFactory reliabilityFactory,
|
||||
ShutdownManager shutdownManager) {
|
||||
DuplexPluginFactory bluetooth = new BluetoothPluginFactory(ioExecutor,
|
||||
random, backoffFactory);
|
||||
ShutdownManager shutdownManager, EventBus eventBus) {
|
||||
DuplexPluginFactory bluetooth =
|
||||
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus,
|
||||
backoffFactory);
|
||||
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
|
||||
reliabilityFactory);
|
||||
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.bluetooth.BluetoothStateException;
|
||||
import javax.bluetooth.LocalDevice;
|
||||
import javax.microedition.io.Connector;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
import javax.microedition.io.StreamConnectionNotifier;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.StringUtils.isValidMac;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JavaBluetoothPlugin.class.getName());
|
||||
|
||||
// Non-null if the plugin started successfully
|
||||
private volatile LocalDevice localDevice = null;
|
||||
|
||||
JavaBluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
|
||||
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
|
||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initialiseAdapter() throws IOException {
|
||||
try {
|
||||
localDevice = LocalDevice.getLocalDevice();
|
||||
} catch (UnsatisfiedLinkError | BluetoothStateException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isAdapterEnabled() {
|
||||
return localDevice != null && LocalDevice.isPowerOn();
|
||||
}
|
||||
|
||||
@Override
|
||||
void enableAdapter() {
|
||||
// Nothing we can do on this platform
|
||||
LOG.info("Could not enable Bluetooth");
|
||||
}
|
||||
|
||||
@Override
|
||||
void disableAdapterIfEnabledByUs() {
|
||||
// We didn't enable it so we don't need to disable it
|
||||
}
|
||||
|
||||
@Override
|
||||
void setEnabledByUs() {
|
||||
// Irrelevant on this platform
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getBluetoothAddress() {
|
||||
return localDevice.getBluetoothAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
StreamConnectionNotifier openServerSocket(String uuid) throws IOException {
|
||||
String url = makeUrl("localhost", uuid);
|
||||
return (StreamConnectionNotifier) Connector.open(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
void tryToClose(@Nullable StreamConnectionNotifier ss) {
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection acceptConnection(StreamConnectionNotifier ss)
|
||||
throws IOException {
|
||||
return wrapSocket(ss.acceptAndOpen());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isValidAddress(String address) {
|
||||
return isValidMac(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
DuplexTransportConnection connectTo(String address, String uuid)
|
||||
throws IOException {
|
||||
String url = makeUrl(address, uuid);
|
||||
return wrapSocket((StreamConnection) Connector.open(url));
|
||||
}
|
||||
|
||||
private String makeUrl(String address, String uuid) {
|
||||
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
|
||||
}
|
||||
|
||||
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||
return new JavaBluetoothTransportConnection(this, s);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin.bluetooth;
|
||||
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.Backoff;
|
||||
import org.briarproject.bramble.api.plugin.BackoffFactory;
|
||||
@@ -17,7 +18,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
|
||||
|
||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||
@@ -27,12 +28,15 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
private final Executor ioExecutor;
|
||||
private final SecureRandom secureRandom;
|
||||
private final BackoffFactory backoffFactory;
|
||||
private final EventBus eventBus;
|
||||
|
||||
public BluetoothPluginFactory(Executor ioExecutor,
|
||||
SecureRandom secureRandom, BackoffFactory backoffFactory) {
|
||||
public JavaBluetoothPluginFactory(Executor ioExecutor,
|
||||
SecureRandom secureRandom, EventBus eventBus,
|
||||
BackoffFactory backoffFactory) {
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.secureRandom = secureRandom;
|
||||
this.backoffFactory = backoffFactory;
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,7 +53,9 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
|
||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
return new BluetoothPlugin(ioExecutor, secureRandom, backoff, callback,
|
||||
MAX_LATENCY);
|
||||
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(ioExecutor,
|
||||
secureRandom, backoff, callback, MAX_LATENCY);
|
||||
eventBus.addListener(plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,12 @@ import java.io.OutputStream;
|
||||
import javax.microedition.io.StreamConnection;
|
||||
|
||||
@NotNullByDefault
|
||||
class BluetoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||
class JavaBluetoothTransportConnection
|
||||
extends AbstractDuplexTransportConnection {
|
||||
|
||||
private final StreamConnection stream;
|
||||
|
||||
BluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
JavaBluetoothTransportConnection(Plugin plugin, StreamConnection stream) {
|
||||
super(plugin);
|
||||
this.stream = stream;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
||||
|
||||
@Override
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, BdfList descriptor, long timeout) {
|
||||
byte[] commitment, BdfList descriptor) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = pt_BR: pt-rBR, fr_FR: fr, nb_NO: nb, zh-Hans: zh-rCN
|
||||
lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN
|
||||
|
||||
[briar.stringsxml-5]
|
||||
file_filter = src/main/res/values-<lang>/strings.xml
|
||||
|
||||
@@ -189,8 +189,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 26
|
||||
versionCode 1618
|
||||
versionName "0.16.18"
|
||||
versionCode 1619
|
||||
versionName "0.16.19"
|
||||
applicationId "org.briarproject.briar.beta"
|
||||
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||
resValue "string", "app_name", "Briar Beta"
|
||||
|
||||
@@ -77,6 +77,11 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.login.OpenDatabaseActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"/>
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.net.Uri;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -171,9 +172,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@TargetApi(26)
|
||||
private void createNotificationChannel(String channelId,
|
||||
@StringRes int name) {
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel nc =
|
||||
new NotificationChannel(channelId, appContext.getString(name),
|
||||
IMPORTANCE_DEFAULT));
|
||||
IMPORTANCE_DEFAULT);
|
||||
nc.enableLights(true);
|
||||
nc.setLightColor(
|
||||
ContextCompat.getColor(appContext, R.color.briar_green_light));
|
||||
notificationManager.createNotificationChannel(nc);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,6 +94,7 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public boolean databaseExists() {
|
||||
// FIXME should not run on UiThread #620
|
||||
if (!dir.isDirectory()) return false;
|
||||
File[] files = dir.listFiles();
|
||||
return files != null && files.length > 0;
|
||||
|
||||
@@ -88,6 +88,7 @@ public class BriarService extends Service {
|
||||
stopSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create notification channels
|
||||
if (SDK_INT >= 26) {
|
||||
NotificationManager nm = (NotificationManager)
|
||||
|
||||
@@ -3,29 +3,31 @@ package org.briarproject.briar.android;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
|
||||
public class StartupFailureActivity extends BaseActivity {
|
||||
public class StartupFailureActivity extends BaseActivity implements
|
||||
BaseFragmentListener {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.activity_startup_failure);
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
handleIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent i) {
|
||||
@@ -41,12 +43,31 @@ public class StartupFailureActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
// show proper error message
|
||||
TextView view = findViewById(R.id.errorView);
|
||||
if (result.equals(StartResult.DB_ERROR)) {
|
||||
view.setText(getText(R.string.startup_failed_db_error));
|
||||
} else if (result.equals(StartResult.SERVICE_ERROR)) {
|
||||
view.setText(getText(R.string.startup_failed_service_error));
|
||||
String errorMsg;
|
||||
switch (result) {
|
||||
case DATA_TOO_OLD_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_db_error);
|
||||
break;
|
||||
case DATA_TOO_NEW_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_new_error);
|
||||
break;
|
||||
case DB_ERROR:
|
||||
errorMsg =
|
||||
getString(R.string.startup_failed_data_too_old_error);
|
||||
break;
|
||||
case SERVICE_ERROR:
|
||||
errorMsg = getString(R.string.startup_failed_service_error);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
showInitialFragment(ErrorFragment.newInstance(errorMsg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runOnDbThread(Runnable runnable) {
|
||||
throw new AssertionError("Deprecated and should not be used");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.activity;
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.briar.android.AndroidComponent;
|
||||
import org.briarproject.briar.android.StartupFailureActivity;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.blog.BlogFragment;
|
||||
import org.briarproject.briar.android.blog.BlogModule;
|
||||
@@ -31,6 +32,7 @@ import org.briarproject.briar.android.keyagreement.ShowQrCodeFragment;
|
||||
import org.briarproject.briar.android.login.AuthorNameFragment;
|
||||
import org.briarproject.briar.android.login.ChangePasswordActivity;
|
||||
import org.briarproject.briar.android.login.DozeFragment;
|
||||
import org.briarproject.briar.android.login.OpenDatabaseActivity;
|
||||
import org.briarproject.briar.android.login.PasswordActivity;
|
||||
import org.briarproject.briar.android.login.PasswordFragment;
|
||||
import org.briarproject.briar.android.login.SetupActivity;
|
||||
@@ -87,6 +89,8 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(SetupActivity activity);
|
||||
|
||||
void inject(OpenDatabaseActivity activity);
|
||||
|
||||
void inject(NavDrawerActivity activity);
|
||||
|
||||
void inject(PasswordActivity activity);
|
||||
@@ -151,6 +155,8 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(RssFeedManageActivity activity);
|
||||
|
||||
void inject(StartupFailureActivity activity);
|
||||
|
||||
// Fragments
|
||||
void inject(AuthorNameFragment fragment);
|
||||
|
||||
|
||||
@@ -11,5 +11,6 @@ public interface RequestCodes {
|
||||
int REQUEST_RINGTONE = 7;
|
||||
int REQUEST_PERMISSION_CAMERA = 8;
|
||||
int REQUEST_DOZE_WHITELISTING = 9;
|
||||
int REQUEST_ENABLE_BLUETOOTH = 10;
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getContext().startActivity(i);
|
||||
}
|
||||
});
|
||||
}, getFragmentManager());
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter = new BlogPostAdapter(getActivity(), this);
|
||||
adapter =
|
||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||
list = v.findViewById(R.id.postList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarAdapter;
|
||||
|
||||
class BlogPostAdapter
|
||||
extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
|
||||
|
||||
private final OnBlogPostClickListener listener;
|
||||
@Nullable
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
|
||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener,
|
||||
@Nullable FragmentManager fragmentManager) {
|
||||
super(ctx, BlogPostItem.class);
|
||||
this.listener = listener;
|
||||
this.fragmentManager = fragmentManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,8 +32,7 @@ class BlogPostAdapter
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v, false, listener);
|
||||
return ui;
|
||||
return new BlogPostViewHolder(v, false, listener, fragmentManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Spanned;
|
||||
@@ -52,12 +53,16 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@NonNull
|
||||
private final OnBlogPostClickListener listener;
|
||||
@Nullable
|
||||
private final FragmentManager fragmentManager;
|
||||
|
||||
BlogPostViewHolder(View v, boolean fullText,
|
||||
@NonNull OnBlogPostClickListener listener) {
|
||||
@NonNull OnBlogPostClickListener listener,
|
||||
@Nullable FragmentManager fragmentManager) {
|
||||
super(v);
|
||||
this.fullText = fullText;
|
||||
this.listener = listener;
|
||||
this.fragmentManager = fragmentManager;
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = v.findViewById(R.id.postLayout);
|
||||
@@ -117,7 +122,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
if (fullText) {
|
||||
body.setText(bodyText);
|
||||
body.setTextIsSelectable(true);
|
||||
makeLinksClickable(body);
|
||||
makeLinksClickable(body, fragmentManager);
|
||||
} else {
|
||||
body.setTextIsSelectable(false);
|
||||
if (bodyText.length() > TEASER_LENGTH)
|
||||
|
||||
@@ -74,7 +74,8 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter = new BlogPostAdapter(getActivity(), this);
|
||||
adapter =
|
||||
new BlogPostAdapter(getActivity(), this, getFragmentManager());
|
||||
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
list = v.findViewById(R.id.postList);
|
||||
|
||||
@@ -146,7 +146,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
ui.input.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private class ViewHolder {
|
||||
|
||||
private final ScrollView scrollView;
|
||||
private final ProgressBar progressBar;
|
||||
@@ -167,7 +167,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
public void onAuthorClick(BlogPostItem post) {
|
||||
// probably don't want to allow author clicks here
|
||||
}
|
||||
});
|
||||
}, getFragmentManager());
|
||||
input = v.findViewById(R.id.inputText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,7 @@ public interface ConfigController {
|
||||
void deleteAccount(Context ctx);
|
||||
|
||||
boolean accountExists();
|
||||
|
||||
boolean accountSignedIn();
|
||||
|
||||
}
|
||||
|
||||
@@ -52,4 +52,10 @@ public class ConfigControllerImpl implements ConfigController {
|
||||
String hex = getEncryptedDatabaseKey();
|
||||
return hex != null && databaseConfig.databaseExists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accountSignedIn() {
|
||||
return databaseConfig.getEncryptionKey() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ErrorFragment extends BaseFragment {
|
||||
|
||||
private static final String TAG = ErrorFragment.class.getSimpleName();
|
||||
|
||||
private static final String ERROR_MSG = "errorMessage";
|
||||
|
||||
public static ErrorFragment newInstance(String message) {
|
||||
ErrorFragment f = new ErrorFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ERROR_MSG, message);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args == null) throw new AssertionError();
|
||||
errorMessage = args.getString(ERROR_MSG);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater
|
||||
.inflate(R.layout.fragment_error, container, false);
|
||||
TextView msg = v.findViewById(R.id.errorMessage);
|
||||
msg.setText(errorMessage);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
// not necessary
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.hardware.Camera.AutoFocusCallback;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.hardware.Camera.Size;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.AttributeSet;
|
||||
@@ -22,6 +21,7 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
|
||||
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
import static android.hardware.Camera.Parameters.FLASH_MODE_OFF;
|
||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO;
|
||||
@@ -32,6 +32,7 @@ import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED;
|
||||
import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO;
|
||||
import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO;
|
||||
import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
@@ -41,7 +42,12 @@ import static java.util.logging.Level.WARNING;
|
||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
AutoFocusCallback, View.OnClickListener {
|
||||
|
||||
// Heuristic for the ideal preview size - small previews don't have enough
|
||||
// detail, large previews are slow to decode
|
||||
private static final int IDEAL_PIXELS = 500 * 1000;
|
||||
|
||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CameraView.class.getName());
|
||||
|
||||
@@ -49,6 +55,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
@Nullable
|
||||
private Camera camera = null;
|
||||
private int cameraIndex = 0;
|
||||
private PreviewConsumer previewConsumer = null;
|
||||
private Surface surface = null;
|
||||
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
||||
@@ -89,15 +96,32 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void start() throws CameraException {
|
||||
public void start(int rotationDegrees) throws CameraException {
|
||||
LOG.info("Opening camera");
|
||||
try {
|
||||
camera = Camera.open();
|
||||
int cameras = Camera.getNumberOfCameras();
|
||||
if (cameras == 0) throw new CameraException("No camera");
|
||||
// Try to find a back-facing camera
|
||||
for (int i = 0; i < cameras; i++) {
|
||||
CameraInfo info = new CameraInfo();
|
||||
Camera.getCameraInfo(i, info);
|
||||
if (info.facing == CAMERA_FACING_BACK) {
|
||||
LOG.info("Using back-facing camera");
|
||||
camera = Camera.open(i);
|
||||
cameraIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we can't find a back-facing camera, use a front-facing one
|
||||
if (camera == null) {
|
||||
LOG.info("Using front-facing camera");
|
||||
camera = Camera.open(0);
|
||||
cameraIndex = 0;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
if (camera == null) throw new CameraException("No back-facing camera");
|
||||
setDisplayOrientation(0);
|
||||
setDisplayOrientation(rotationDegrees);
|
||||
// Use barcode scene mode if it's available
|
||||
Parameters params = camera.getParameters();
|
||||
params = setSceneMode(camera, params);
|
||||
@@ -163,7 +187,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
private void startConsumer() throws CameraException {
|
||||
if (camera == null) throw new CameraException("Camera is null");
|
||||
startAutoFocus();
|
||||
previewConsumer.start(camera);
|
||||
previewConsumer.start(camera, cameraIndex);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -199,13 +223,17 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link Camera#setDisplayOrientation(int)}.
|
||||
*/
|
||||
@UiThread
|
||||
private void setDisplayOrientation(int rotationDegrees)
|
||||
throws CameraException {
|
||||
if (camera == null) throw new CameraException("Camera is null");
|
||||
int orientation;
|
||||
CameraInfo info = new CameraInfo();
|
||||
try {
|
||||
Camera.getCameraInfo(0, info);
|
||||
Camera.getCameraInfo(cameraIndex, info);
|
||||
} catch (RuntimeException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
@@ -215,9 +243,11 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
} else {
|
||||
orientation = (info.orientation - rotationDegrees + 360) % 360;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Display orientation " + orientation + " degrees");
|
||||
if (camera == null) throw new CameraException("Camera is null");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Screen rotation " + rotationDegrees
|
||||
+ " degrees, camera orientation " + orientation
|
||||
+ " degrees");
|
||||
}
|
||||
try {
|
||||
camera.setDisplayOrientation(orientation);
|
||||
} catch (RuntimeException e) {
|
||||
@@ -285,8 +315,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
@UiThread
|
||||
private void setVideoStabilisation(Parameters params) {
|
||||
if (Build.VERSION.SDK_INT >= 15 &&
|
||||
params.isVideoStabilizationSupported()) {
|
||||
if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) {
|
||||
params.setVideoStabilization(true);
|
||||
}
|
||||
}
|
||||
@@ -313,6 +342,8 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
@UiThread
|
||||
private void setPreviewSize(Parameters params) {
|
||||
if (surfaceWidth == 0 || surfaceHeight == 0) return;
|
||||
// Choose a preview size that's close to the aspect ratio of the
|
||||
// surface and close to the ideal size for decoding
|
||||
float idealRatio = (float) surfaceWidth / surfaceHeight;
|
||||
boolean rotatePreview = displayOrientation % 180 == 90;
|
||||
List<Size> sizes = params.getSupportedPreviewSizes();
|
||||
@@ -323,11 +354,12 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
int height = rotatePreview ? size.width : size.height;
|
||||
float ratio = (float) width / height;
|
||||
float stretch = Math.max(ratio / idealRatio, idealRatio / ratio);
|
||||
int pixels = width * height;
|
||||
float score = pixels / stretch;
|
||||
float pixels = width * height;
|
||||
float zoom = Math.max(pixels / IDEAL_PIXELS, IDEAL_PIXELS / pixels);
|
||||
float score = 1 / (stretch * zoom);
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Size " + size.width + "x" + size.height
|
||||
+ ", stretch " + stretch + ", pixels " + pixels
|
||||
+ ", stretch " + stretch + ", zoom " + zoom
|
||||
+ ", score " + score);
|
||||
}
|
||||
if (bestSize == null || score > bestScore) {
|
||||
@@ -358,7 +390,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
} catch (RuntimeException e) {
|
||||
throw new CameraException(e);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 15) {
|
||||
if (SDK_INT >= 15) {
|
||||
LOG.info("Video stabilisation enabled: "
|
||||
+ params.getVideoStabilization());
|
||||
}
|
||||
@@ -389,8 +421,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
surface.release();
|
||||
}
|
||||
surface = holder.getSurface();
|
||||
// Start the preview when the camera and the surface are both ready
|
||||
if (camera != null && !previewStarted) startPreview(holder);
|
||||
// We'll start the preview when surfaceChanged() is called
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -416,7 +447,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
surfaceWidth = w;
|
||||
surfaceHeight = h;
|
||||
if (camera == null) return; // We are stopped
|
||||
stopPreview();
|
||||
if (previewStarted) stopPreview();
|
||||
try {
|
||||
Parameters params = camera.getParameters();
|
||||
setPreviewSize(params);
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package org.briarproject.briar.android.keyagreement;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
@@ -23,6 +28,7 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.R.string;
|
||||
import org.briarproject.briar.R.style;
|
||||
@@ -39,9 +45,15 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -50,6 +62,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
BaseFragmentListener, IntroScreenSeenListener, EventListener,
|
||||
ContactExchangeListener {
|
||||
|
||||
private enum BluetoothState {
|
||||
UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementActivity.class.getName());
|
||||
|
||||
@@ -62,7 +78,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
@Inject
|
||||
volatile IdentityManager identityManager;
|
||||
|
||||
private boolean isResumed = false, enableWasRequested = false;
|
||||
private boolean continueClicked, gotCameraPermission;
|
||||
private BluetoothState bluetoothState = BluetoothState.UNKNOWN;
|
||||
private BroadcastReceiver bluetoothReceiver = null;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
@@ -84,6 +103,15 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
if (state == null) {
|
||||
showInitialFragment(IntroFragment.newInstance());
|
||||
}
|
||||
IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED);
|
||||
bluetoothReceiver = new BluetoothStateReceiver();
|
||||
registerReceiver(bluetoothReceiver, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,24 +140,72 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
isResumed = true;
|
||||
// Workaround for
|
||||
// https://code.google.com/p/android/issues/detail?id=190966
|
||||
if (continueClicked && gotCameraPermission) {
|
||||
showQrCodeFragment();
|
||||
}
|
||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
}
|
||||
|
||||
boolean canShowQrCodeFragment() {
|
||||
return isResumed && continueClicked
|
||||
&& (SDK_INT < 23 || gotCameraPermission)
|
||||
&& bluetoothState != BluetoothState.UNKNOWN
|
||||
&& bluetoothState != BluetoothState.WAITING;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
isResumed = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showNextScreen() {
|
||||
// FIXME #824
|
||||
// showNextFragment(ShowQrCodeFragment.newInstance());
|
||||
continueClicked = true;
|
||||
if (checkPermissions()) {
|
||||
showQrCodeFragment();
|
||||
if (shouldRequestEnableBluetooth()) requestEnableBluetooth();
|
||||
else if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRequestEnableBluetooth() {
|
||||
return bluetoothState == BluetoothState.UNKNOWN
|
||||
|| bluetoothState == BluetoothState.REFUSED;
|
||||
}
|
||||
|
||||
private void requestEnableBluetooth() {
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt == null) {
|
||||
setBluetoothState(BluetoothState.NO_ADAPTER);
|
||||
} else if (bt.isEnabled()) {
|
||||
setBluetoothState(BluetoothState.ENABLED);
|
||||
} else {
|
||||
enableWasRequested = true;
|
||||
setBluetoothState(BluetoothState.WAITING);
|
||||
Intent i = new Intent(ACTION_REQUEST_ENABLE);
|
||||
startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBluetoothState(BluetoothState bluetoothState) {
|
||||
LOG.info("Setting Bluetooth state to " + bluetoothState);
|
||||
this.bluetoothState = bluetoothState;
|
||||
if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) {
|
||||
eventBus.broadcast(new BluetoothEnabledEvent());
|
||||
enableWasRequested = false;
|
||||
}
|
||||
if (canShowQrCodeFragment()) showQrCodeFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
// If the request was granted we'll catch the state change event
|
||||
if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED)
|
||||
setBluetoothState(BluetoothState.REFUSED);
|
||||
}
|
||||
|
||||
private void showQrCodeFragment() {
|
||||
// FIXME #824
|
||||
BaseFragment f = ShowQrCodeFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||
@@ -154,8 +230,10 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
} else {
|
||||
requestPermission();
|
||||
}
|
||||
gotCameraPermission = false;
|
||||
return false;
|
||||
} else {
|
||||
gotCameraPermission = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -174,6 +252,7 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
if (grantResults.length > 0 &&
|
||||
grantResults[0] == PERMISSION_GRANTED) {
|
||||
gotCameraPermission = true;
|
||||
showNextScreen();
|
||||
} else {
|
||||
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
|
||||
CAMERA)) {
|
||||
@@ -258,4 +337,14 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED);
|
||||
else setBluetoothState(BluetoothState.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
interface PreviewConsumer {
|
||||
|
||||
@UiThread
|
||||
void start(Camera camera);
|
||||
void start(Camera camera, int cameraIndex);
|
||||
|
||||
@UiThread
|
||||
void stop();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.keyagreement;
|
||||
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.PreviewCallback;
|
||||
import android.hardware.Camera.Size;
|
||||
import android.os.AsyncTask;
|
||||
@@ -36,20 +37,23 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
private final ResultCallback callback;
|
||||
|
||||
private Camera camera = null;
|
||||
private int cameraIndex = 0;
|
||||
|
||||
QrCodeDecoder(ResultCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Camera camera) {
|
||||
public void start(Camera camera, int cameraIndex) {
|
||||
this.camera = camera;
|
||||
this.cameraIndex = cameraIndex;
|
||||
askForPreviewFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
camera = null;
|
||||
cameraIndex = 0;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@@ -61,45 +65,64 @@ 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();
|
||||
new DecoderTask(data, size.width, size.height).execute();
|
||||
// The preview should be in NV21 format: width * height bytes of
|
||||
// Y followed by width * height / 2 bytes of interleaved U and V
|
||||
if (data.length == size.width * size.height * 3 / 2) {
|
||||
CameraInfo info = new CameraInfo();
|
||||
Camera.getCameraInfo(cameraIndex, info);
|
||||
new DecoderTask(data, size.width, size.height,
|
||||
info.orientation).execute();
|
||||
} else {
|
||||
// Camera parameters have changed - ask for a new preview
|
||||
LOG.info("Preview size does not match camera parameters");
|
||||
askForPreviewFrame();
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
LOG.log(WARNING, "Error getting camera parameters.", e);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Camera has changed, ignoring preview frame");
|
||||
}
|
||||
}
|
||||
|
||||
private class DecoderTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private final byte[] data;
|
||||
private final int width, height;
|
||||
private final int width, height, orientation;
|
||||
|
||||
private DecoderTask(byte[] data, int width, int height) {
|
||||
private DecoderTask(byte[] data, int width, int height,
|
||||
int orientation) {
|
||||
this.data = data;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
long now = System.currentTimeMillis();
|
||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||
height, 0, 0, width, height, false);
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
||||
BinaryBitmap bitmap = binarize(data, width, height, orientation);
|
||||
Result result = null;
|
||||
try {
|
||||
result = reader.decode(bitmap);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Decoding barcode took " + duration + " ms");
|
||||
} catch (ReaderException e) {
|
||||
return null; // No barcode found
|
||||
// No barcode found
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("No barcode found after " + duration + " ms");
|
||||
return null;
|
||||
} catch (RuntimeException e) {
|
||||
return null; // Preview data did not match width and height
|
||||
LOG.warning("Invalid preview frame");
|
||||
return null;
|
||||
} finally {
|
||||
reader.reset();
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Decoding barcode took " + duration + " ms");
|
||||
callback.handleResult(result);
|
||||
return null;
|
||||
}
|
||||
@@ -110,6 +133,19 @@ class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
}
|
||||
}
|
||||
|
||||
private static BinaryBitmap binarize(byte[] data, int width, int height,
|
||||
int orientation) {
|
||||
// Crop to a square at the top (portrait) or left (landscape) of the
|
||||
// screen - this will be faster to decode and should include
|
||||
// everything visible in the viewfinder
|
||||
int crop = Math.min(width, height);
|
||||
int left = orientation >= 180 ? width - crop : 0;
|
||||
int top = orientation >= 180 ? height - crop : 0;
|
||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||
height, left, top, crop, crop, false);
|
||||
return new BinaryBitmap(new HybridBinarizer(src));
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
interface ResultCallback {
|
||||
|
||||
|
||||
@@ -32,21 +32,24 @@ class QrCodeUtils {
|
||||
// Generate QR code
|
||||
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
||||
smallestDimen, smallestDimen);
|
||||
// Convert QR code to Bitmap
|
||||
int width = encoded.getWidth();
|
||||
int height = encoded.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
pixels[y * width + x] = encoded.get(x, y) ? BLACK : WHITE;
|
||||
}
|
||||
}
|
||||
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
||||
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
return qr;
|
||||
return renderQrCode(encoded);
|
||||
} catch (WriterException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap renderQrCode(BitMatrix matrix) {
|
||||
int width = matrix.getWidth();
|
||||
int height = matrix.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
pixels[y * width + x] = matrix.get(x, y) ? BLACK : WHITE;
|
||||
}
|
||||
}
|
||||
Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888);
|
||||
qr.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||
return qr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package org.briarproject.briar.android.keyagreement;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Bitmap;
|
||||
import android.hardware.Camera;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.Base64;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
@@ -37,7 +36,6 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseEventFragment;
|
||||
@@ -49,9 +47,6 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
@@ -86,8 +81,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
private TextView mainProgressTitle;
|
||||
private ViewGroup mainProgressContainer;
|
||||
|
||||
private BluetoothStateReceiver receiver;
|
||||
private boolean gotRemotePayload, waitingForBluetooth;
|
||||
private boolean gotRemotePayload;
|
||||
private volatile boolean gotLocalPayload;
|
||||
private KeyAgreementTask task;
|
||||
|
||||
@@ -144,23 +138,29 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
try {
|
||||
cameraView.start();
|
||||
cameraView.start(getScreenRotationDegrees());
|
||||
} catch (CameraException e) {
|
||||
logCameraExceptionAndFinish(e);
|
||||
}
|
||||
// Listen for changes to the Bluetooth state
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_STATE_CHANGED);
|
||||
receiver = new BluetoothStateReceiver();
|
||||
getActivity().registerReceiver(receiver, filter);
|
||||
startListening();
|
||||
}
|
||||
|
||||
// Enable BT adapter if it is not already on.
|
||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||
if (adapter != null && !adapter.isEnabled()) {
|
||||
waitingForBluetooth = true;
|
||||
eventBus.broadcast(new EnableBluetoothEvent());
|
||||
} else {
|
||||
startListening();
|
||||
/**
|
||||
* See {@link Camera#setDisplayOrientation(int)}.
|
||||
*/
|
||||
private int getScreenRotationDegrees() {
|
||||
Display d = getActivity().getWindowManager().getDefaultDisplay();
|
||||
switch (d.getRotation()) {
|
||||
case Surface.ROTATION_0:
|
||||
return 0;
|
||||
case Surface.ROTATION_90:
|
||||
return 90;
|
||||
case Surface.ROTATION_180:
|
||||
return 180;
|
||||
case Surface.ROTATION_270:
|
||||
return 270;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
stopListening();
|
||||
if (receiver != null) getActivity().unregisterReceiver(receiver);
|
||||
try {
|
||||
cameraView.stop();
|
||||
} catch (CameraException e) {
|
||||
@@ -340,16 +339,4 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
protected void finish() {
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
@UiThread
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
if (state == STATE_ON && waitingForBluetooth) {
|
||||
waitingForBluetooth = false;
|
||||
startListening();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.briarproject.briar.android.login;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public class OpenDatabaseActivity extends BriarActivity
|
||||
implements EventListener {
|
||||
|
||||
@Inject
|
||||
LifecycleManager lifecycleManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
private TextView textView;
|
||||
private ImageView imageView;
|
||||
private boolean showingMigration = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.activity_open_database);
|
||||
textView = findViewById(R.id.textView);
|
||||
imageView = findViewById(R.id.imageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
LifecycleState state = lifecycleManager.getLifecycleState();
|
||||
if (state.isAfter(STARTING_SERVICES)) {
|
||||
finishAndStartApp();
|
||||
} else {
|
||||
if (state == MIGRATING_DATABASE) showMigration();
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof LifecycleEvent) {
|
||||
LifecycleState state = ((LifecycleEvent) e).getLifecycleState();
|
||||
if (state.isAfter(STARTING_SERVICES))
|
||||
runOnUiThreadUnlessDestroyed(this::finishAndStartApp);
|
||||
else if (state == MIGRATING_DATABASE)
|
||||
runOnUiThreadUnlessDestroyed(this::showMigration);
|
||||
}
|
||||
}
|
||||
|
||||
private void showMigration() {
|
||||
if (showingMigration) return;
|
||||
textView.setText(R.string.startup_migrate_database);
|
||||
imageView.setImageResource(R.drawable.startup_migration);
|
||||
showingMigration = true;
|
||||
}
|
||||
|
||||
private void finishAndStartApp() {
|
||||
startActivity(new Intent(this, NavDrawerActivity.class));
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -48,7 +47,7 @@ public class SetupActivity extends BaseActivity
|
||||
}
|
||||
|
||||
public void showApp() {
|
||||
Intent i = new Intent(this, NavDrawerActivity.class);
|
||||
Intent i = new Intent(this, OpenDatabaseActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
supportFinishAfterTransition();
|
||||
|
||||
@@ -46,8 +46,6 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.os.Build.MANUFACTURER;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||
import static android.support.v4.view.GravityCompat.START;
|
||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||
@@ -214,18 +212,6 @@ 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) {
|
||||
if (SDK_INT == 19 && MANUFACTURER.equalsIgnoreCase("Samsung")) {
|
||||
// workaround for #1116 causes splash screen to show again
|
||||
super.onBackPressed();
|
||||
} else {
|
||||
Intent i = new Intent(Intent.ACTION_MAIN);
|
||||
i.addCategory(Intent.CATEGORY_HOME);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
}
|
||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||
getSupportFragmentManager()
|
||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||
|
||||
@@ -22,8 +22,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||
import org.briarproject.bramble.api.settings.Settings;
|
||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
|
||||
@@ -262,7 +260,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
if (preference == enableBluetooth) {
|
||||
boolean btSetting = Boolean.valueOf((String) o);
|
||||
enableOrDisableBluetooth(btSetting);
|
||||
storeBluetoothSettings(btSetting);
|
||||
} else if (preference == torNetwork) {
|
||||
int torSetting = Integer.valueOf((String) o);
|
||||
@@ -295,11 +292,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
return true;
|
||||
}
|
||||
|
||||
private void enableOrDisableBluetooth(boolean enable) {
|
||||
if (enable) eventBus.broadcast(new EnableBluetoothEvent());
|
||||
else eventBus.broadcast(new DisableBluetoothEvent());
|
||||
}
|
||||
|
||||
private void storeTorSettings(int torSetting) {
|
||||
listener.runOnDbThread(() -> {
|
||||
try {
|
||||
|
||||
@@ -12,8 +12,8 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.controller.ConfigController;
|
||||
import org.briarproject.briar.android.login.OpenDatabaseActivity;
|
||||
import org.briarproject.briar.android.login.SetupActivity;
|
||||
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -43,10 +43,15 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
|
||||
setContentView(R.layout.splash);
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
startNextActivity();
|
||||
supportFinishAfterTransition();
|
||||
}, 500);
|
||||
if (configController.accountSignedIn()) {
|
||||
startActivity(new Intent(this, OpenDatabaseActivity.class));
|
||||
finish();
|
||||
} else {
|
||||
new Handler().postDelayed(() -> {
|
||||
startNextActivity();
|
||||
supportFinishAfterTransition();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +65,7 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
startActivity(new Intent(this, ExpiredActivity.class));
|
||||
} else {
|
||||
if (configController.accountExists()) {
|
||||
startActivity(new Intent(this, NavDrawerActivity.class));
|
||||
startActivity(new Intent(this, OpenDatabaseActivity.class));
|
||||
} else {
|
||||
configController.deleteAccount(this);
|
||||
startActivity(new Intent(this, SetupActivity.class));
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.support.annotation.ColorRes;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE;
|
||||
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
|
||||
|
||||
@@ -17,6 +19,9 @@ public class BriarNotificationBuilder extends NotificationCompat.Builder {
|
||||
// Auto-cancel does not fire the delete intent, see
|
||||
// https://issuetracker.google.com/issues/36961721
|
||||
setAutoCancel(true);
|
||||
|
||||
setLights(ContextCompat.getColor(context, R.color.briar_green_light),
|
||||
750, 500);
|
||||
}
|
||||
|
||||
public BriarNotificationBuilder setColorRes(@ColorRes int res) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.Html;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
@@ -25,6 +24,8 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.view.ArticleMovementMethod;
|
||||
import org.briarproject.briar.android.widget.LinkDialogFragment;
|
||||
@@ -47,6 +48,8 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class UiUtils {
|
||||
|
||||
public static final long MIN_DATE_RESOLUTION = MINUTE_IN_MILLIS;
|
||||
@@ -110,7 +113,9 @@ public class UiUtils {
|
||||
return Html.fromHtml(s);
|
||||
}
|
||||
|
||||
public static void makeLinksClickable(TextView v) {
|
||||
public static void makeLinksClickable(TextView v,
|
||||
@Nullable FragmentManager fm) {
|
||||
if (fm == null) return;
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder(v.getText());
|
||||
URLSpan[] spans = ssb.getSpans(0, ssb.length(), URLSpan.class);
|
||||
for (URLSpan span : spans) {
|
||||
@@ -122,8 +127,6 @@ public class UiUtils {
|
||||
@Override
|
||||
public void onClick(View v2) {
|
||||
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
|
||||
FragmentManager fm = ((AppCompatActivity) v2.getContext())
|
||||
.getSupportFragmentManager();
|
||||
f.show(fm, f.getUniqueTag());
|
||||
}
|
||||
};
|
||||
|
||||
9
briar-android/src/main/res/drawable/startup_lock.xml
Normal file
9
briar-android/src/main/res/drawable/startup_lock.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1 -2.73,2.71 -2.73,7.08 0,9.79 2.73,2.71 7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29 -3.51,3.48 -9.21,3.48 -12.72,0 -3.5,-3.47 -3.53,-9.11 -0.02,-12.58 3.51,-3.47 9.14,-3.47 12.65,0L21,3v7.12zM12.5,8v4.25l3.5,2.08 -0.72,1.21L11,13V8h1.5z"/>
|
||||
</vector>
|
||||
@@ -1,79 +1,89 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_intro"/>
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
<ImageView
|
||||
android:id="@+id/diagram"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:adjustViewBounds="true"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_intro"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/explanationText"
|
||||
app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
|
||||
|
||||
<LinearLayout
|
||||
<ImageView
|
||||
android:id="@+id/explanationImage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
android:paddingLeft="@dimen/margin_large"
|
||||
android:paddingRight="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_explanation"
|
||||
tools:ignore="ContentDescription"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationText"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/explanationText"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="@dimen/margin_large"
|
||||
android:text="@string/face_to_face"
|
||||
app:layout_constraintTop_toBottomOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/explanationBorder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/border_explanation"
|
||||
app:layout_constraintTop_toTopOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||
app:layout_constraintBottom_toBottomOf="@id/explanationText"/>
|
||||
|
||||
<android.support.constraint.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="diagram,explanationBorder"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/continue_button"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/border_explanation"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_large">
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:src="@drawable/qr_code_explanation"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/face_to_face"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/continue_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.briarproject.briar.android.keyagreement.CameraView
|
||||
android:id="@+id/camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/camera_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/status_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/background_light"
|
||||
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>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qr_code"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/container_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white"
|
||||
android:visibility="invisible">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/title_progress_bar"
|
||||
android:layout_centerHorizontal="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center"
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
tools:text="@string/waiting_for_contact_to_scan"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
46
briar-android/src/main/res/layout/activity_open_database.xml
Normal file
46
briar-android/src/main/res/layout/activity_open_database.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="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/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/startup_open_database"
|
||||
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"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/margin_activity_horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/BriarTextTitle"
|
||||
android:text="@string/startup_failed_notification_title"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="7dp"
|
||||
android:layout_marginBottom="7dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/startup_failed_notification_text"/>
|
||||
|
||||
</LinearLayout>
|
||||
51
briar-android/src/main/res/layout/fragment_error.xml
Normal file
51
briar-android/src/main/res/layout/fragment_error.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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/errorIcon"
|
||||
android:layout_width="128dp"
|
||||
android:layout_height="128dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:src="@drawable/alerts_and_states_error"
|
||||
android:tint="?colorControlNormal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/sorry"
|
||||
android:textSize="@dimen/text_size_xlarge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorIcon"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/errorMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/errorTitle"
|
||||
tools:text="@string/startup_failed_db_error"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
@@ -2,66 +2,80 @@
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/margin_activity_vertical"
|
||||
android:paddingEnd="@dimen/margin_activity_horizontal"
|
||||
android:paddingLeft="@dimen/margin_activity_horizontal"
|
||||
android:paddingRight="@dimen/margin_activity_horizontal"
|
||||
android:paddingStart="@dimen/margin_activity_horizontal"
|
||||
android:paddingTop="@dimen/margin_activity_vertical">
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/diagram"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingBottom="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_intro"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationImage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/explanationImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingLeft="@dimen/margin_large"
|
||||
android:paddingRight="@dimen/margin_large"
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_explanation"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toEndOf="@id/diagram"
|
||||
app:layout_constraintStart_toStartOf="@id/diagram"
|
||||
app:layout_constraintTop_toBottomOf="@id/diagram"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<TextView
|
||||
android:id="@+id/explanationText"
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_marginRight="@dimen/margin_small"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:padding="@dimen/margin_large"
|
||||
android:text="@string/face_to_face"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||
app:layout_constraintTop_toBottomOf="@id/explanationImage"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/explanationBorder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/border_explanation"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_large">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:src="@drawable/qr_code_explanation"
|
||||
android:contentDescription="@string/face_to_face"/>
|
||||
|
||||
<TextView
|
||||
style="@style/BriarTextBody"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/face_to_face"/>
|
||||
|
||||
</LinearLayout>
|
||||
app:layout_constraintBottom_toBottomOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||
app:layout_constraintTop_toTopOf="@id/explanationImage"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
style="@style/BriarButton.Default"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/continue_button"/>
|
||||
android:text="@string/continue_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||
app:layout_constraintTop_toBottomOf="@id/explanationText"/>
|
||||
|
||||
</LinearLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -2,9 +2,9 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:showIn="@layout/navigation_menu">
|
||||
|
||||
<View style="@style/Divider.Horizontal"/>
|
||||
@@ -13,8 +13,9 @@
|
||||
android:id="@+id/transportsView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:listSelector="@android:color/transparent"
|
||||
android:numColumns="3"
|
||||
android:padding="@dimen/margin_medium"
|
||||
tools:listitem="@layout/list_item_transport"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Добре дошли в Briar</string>
|
||||
<string name="setup_next">Следващ</string>
|
||||
<string name="choose_nickname">Изберете име</string>
|
||||
<string name="choose_password">Изберете парола</string>
|
||||
@@ -17,7 +18,6 @@
|
||||
<string name="dialog_title_lost_password">Забравена парола</string>
|
||||
<string name="dialog_message_lost_password">Briar профилът се съхранява криптиран във вашето устройство, не в облака, така че не можем да зададем нова парола. Искате ли да изтриете профила си и да започнете отначало?\n\nВнимание: Вашият профил, контакти и съобщения ще бъдат изтрити завинаги.</string>
|
||||
<string name="startup_failed_notification_title">Briar не можа да стартира</string>
|
||||
<string name="startup_failed_notification_text">Може да се наложи да преинсталирате Briar.</string>
|
||||
<string name="startup_failed_activity_title">Неуспешно стартиране</string>
|
||||
<string name="startup_failed_service_error">Briar не успя да стартира задължителен плъгин. Обикновено преинсталирането на Briar решава този проблем. Моля, имайте предвид, че ще изгубите профила си и всички данни, асоциирани с него, тъй като Briar не съхранява данните ви в централни сървъри.</string>
|
||||
<string name="expiry_date_reached">Софтуерът е невалиден.\nБлагодарим ви за тестването!</string>
|
||||
@@ -63,7 +63,8 @@
|
||||
<string name="delete">Изтрий</string>
|
||||
<string name="accept">Приеми</string>
|
||||
<string name="decline">Откажи</string>
|
||||
<string name="online">Онлайн</string>
|
||||
<string name="options">Опции</string>
|
||||
<string name="online">Онлайн</string>
|
||||
<string name="offline">Офлайн</string>
|
||||
<string name="send">Изпрати</string>
|
||||
<string name="allow">Позволи</string>
|
||||
@@ -344,6 +345,5 @@
|
||||
<string name="progress_title_logout">Отписване от Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Открит е овърлей на екрана</string>
|
||||
<string name="screen_filter_body">Друго приложение чертае върху Briar. За да защити вашата сигурност, Briar няма да реагира на докосване, докато друго приложение чертае отгоре.\n\nОпитайте да изключите следните приложения, когато използвате Briar:\n\n%1$s</string>
|
||||
<!--Permission Requests-->
|
||||
</resources>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<string name="forgotten_password">Ankoueet em eus ma ger-tremen</string>
|
||||
<string name="dialog_title_lost_password">Kollet em eus ma ger-tremen</string>
|
||||
<string name="startup_failed_notification_title">N\'hall ket loc\'hañ Briar</string>
|
||||
<string name="startup_failed_notification_text">Rankout a rit staliañ a nevez Briar.</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="contact_list_button">Darempredoù</string>
|
||||
<string name="groups_button">Strolladoù prevez</string>
|
||||
@@ -64,7 +63,8 @@
|
||||
<string name="delete">Dilemel</string>
|
||||
<string name="accept">Asantiñ</string>
|
||||
<string name="decline">Nac\'hañ</string>
|
||||
<string name="online">Enlinenn</string>
|
||||
<string name="options">Dibarzhioù</string>
|
||||
<string name="online">Enlinenn</string>
|
||||
<string name="offline">Ezlinenn</string>
|
||||
<string name="send">Kas</string>
|
||||
<string name="allow">Aotren</string>
|
||||
@@ -143,6 +143,7 @@
|
||||
<!--Settings Feedback-->
|
||||
<!--Link Warning-->
|
||||
<!--Crash Reporter-->
|
||||
<string name="close">Serriñ</string>
|
||||
<!--Sign Out-->
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<!--Permission Requests-->
|
||||
|
||||
@@ -31,10 +31,12 @@
|
||||
<string name="dialog_title_lost_password">Contrasenya perduda</string>
|
||||
<string name="dialog_message_lost_password">El vostre compte de Briar s\'emmagatzema xifrat al vostre dispositiu, no al núvol; per tant no podem restaurar-ne la contrasenya. Voleu esborrar el compte i tornar a començar?\n\nAlerta: les vostres identitats, contactes i missatges es perdran per sempre.</string>
|
||||
<string name="startup_failed_notification_title">Briar no s\'ha pogut iniciar</string>
|
||||
<string name="startup_failed_notification_text">Potser us caldrà reinstal·lar Briar.</string>
|
||||
<string name="startup_failed_notification_text">Toqueu per obtenir més informació.</string>
|
||||
<string name="startup_failed_activity_title">Error iniciant Briar</string>
|
||||
<string name="startup_failed_db_error">Per alguna raó, la vostra base de dades de Briar està malmesa més enllà de la reparació. El vostre compte, les vostres dades i tots els vostres contactes es perdran. Malauradament, necessiteu tornar a instal·lar Briar i configurar un compte nou.</string>
|
||||
<string name="startup_failed_service_error">Briar no ha pogut engegar un plugin necessari. La sol.lució sol ser reinstal.lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
|
||||
<string name="startup_failed_db_error">Per alguna raó, la vostra base de dades de Briar està malmesa més enllà de la reparació. El vostre compte, les vostres dades i tots els vostres contactes es perdran. Malauradament, necessiteu tornar a instal·lar Briar i configurar un nou compte escollint «He oblidat la meva contrasenya» quan se us demani la contrasenya.</string>
|
||||
<string name="startup_failed_data_too_old_error">El vostre compte s\'ha creat amb una versió anterior d\'aquesta aplicació i no es pot obrir amb aquesta versió. Heu de tornar a instal·lar la versió antiga o eliminar el vostre antic compte escollint «He oblidat la meva contrasenya» quan se us demani la contrasenya.</string>
|
||||
<string name="startup_failed_data_too_new_error">Aquesta versió de l\'aplicació és massa antiga. Actualitzeu a la versió més recent i torneu-ho a provar.</string>
|
||||
<string name="startup_failed_service_error">Briar no ha pogut engegar un connector necessari. La solució sol ser reinstal·lar Briar. De tota manera, tingueu en compte que si ho feu perdreu el vostre compte i totes les dades associades, doncs Briar no usa servidors centrals per desar les vostres dades.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dia i no es pot renovar.</item>
|
||||
<item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte expira en %d dies i no es pot renovar.</item>
|
||||
@@ -71,7 +73,7 @@
|
||||
</plurals>
|
||||
<plurals name="blog_post_notification_text">
|
||||
<item quantity="one">Una nova publicacions al bloc.</item>
|
||||
<item quantity="other">%d noves publicacions al bloc.</item>
|
||||
<item quantity="other">%d noves publicacions al blog.</item>
|
||||
</plurals>
|
||||
<!--Misc-->
|
||||
<string name="now">Ara</string>
|
||||
@@ -83,7 +85,8 @@
|
||||
<string name="delete">Suprimeix</string>
|
||||
<string name="accept">Accepta</string>
|
||||
<string name="decline">Rebutja</string>
|
||||
<string name="online">En línia</string>
|
||||
<string name="options">Opcions</string>
|
||||
<string name="online">En línia</string>
|
||||
<string name="offline">Fora de línia</string>
|
||||
<string name="send">Envia</string>
|
||||
<string name="allow">Permet</string>
|
||||
@@ -94,8 +97,9 @@
|
||||
<string name="show_onboarding">Mostra el diàleg d\'ajuda.</string>
|
||||
<string name="fix">Corregeix</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<string name="sorry">Ens sap greu</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Sembla que sou nouvingut i no teniu contactes encara.\n\nToqueu la icona + en la part superior i seguiu les instruccions per a afegir amics a la llista.\n\nRecordeu: afegiu només contactes cara a cara per a evitar que algú es faça passar per vós o pugui llegir els vostres missatges en el futur. </string>
|
||||
<string name="no_contacts">Sembla que sou nouvingut i no teniu contactes encara.\n\nToqueu la icona + en la part superior i seguiu les instruccions per a afegir amics a la llista.\n\nRecordeu: afegiu només contactes cara a cara per a evitar que algú es faci passar per vós o pugui llegir els vostres missatges en el futur. </string>
|
||||
<string name="date_no_private_messages">Sense missatges.</string>
|
||||
<string name="no_private_messages">Aquesta és la vista de conversa.\n\nSembla que hi manca una part de la conversa.\n\nNomés cal que toqueu el camp d\'entrada a la part inferior per iniciar una conversa.</string>
|
||||
<string name="message_hint">Escriu el missatge.</string>
|
||||
@@ -115,6 +119,7 @@
|
||||
<string name="contact_already_exists">El contacte %s ja existeix</string>
|
||||
<string name="contact_exchange_failed">L\'intercanvi de contactes ha fallat</string>
|
||||
<string name="qr_code_invalid">El codi QR és invàlid</string>
|
||||
<string name="qr_code_unsupported">El codi QR que intenteu escanejar pertany a una versió antiga de %s que ja no és compatible.\n\nAssegureu-vos que tots dos estiguin executant la versió més recent i torneu-ho a provar.</string>
|
||||
<string name="camera_error">Error de la càmera</string>
|
||||
<string name="connecting_to_device">Connectant al dispositiu\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticant-se amb el dispositiu\u2026</string>
|
||||
@@ -128,7 +133,7 @@
|
||||
<string name="introduction_message_hint">Afegir un missatge (opcional)</string>
|
||||
<string name="introduction_button">Presentar contactes entre si</string>
|
||||
<string name="introduction_sent">S\'ha enviat la teva presentació.</string>
|
||||
<string name="introduction_error">Hi ha hagut un error al fer la presentació de contactes.</string>
|
||||
<string name="introduction_error">Hi ha hagut un error en fer la presentació de contactes.</string>
|
||||
<string name="introduction_response_error">Error en respondre a la presentació</string>
|
||||
<string name="introduction_request_sent">Heu demanat que introduïu %1$s a %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s ha demanat que us presenteu a %2$s. Voleu afegir a%2$s a la vostra llista de contactes?</string>
|
||||
@@ -180,6 +185,10 @@
|
||||
<string name="groups_invitations_invitation_received">1%1$s us ha convidat a unir-vos al grup \" 2%2$s \".</string>
|
||||
<string name="groups_invitations_joined">Us heu unit al grup</string>
|
||||
<string name="groups_invitations_declined">S\'ha rebutjat la invitació al grup</string>
|
||||
<plurals name="groups_invitations_open">
|
||||
<item quantity="one">%d invitació a un grup obert</item>
|
||||
<item quantity="other">%d invitacions a grups oberts</item>
|
||||
</plurals>
|
||||
<string name="groups_invitations_response_accepted_sent">Heu acceptat la invitació del grup de %s.</string>
|
||||
<string name="groups_invitations_response_declined_sent">Heu rebutjat la invitació del grup de %s.</string>
|
||||
<string name="groups_invitations_response_accepted_received">%s va acceptar la invitació del grup.</string>
|
||||
@@ -210,7 +219,7 @@
|
||||
<string name="btn_reply">Respon</string>
|
||||
<string name="forum_leave">Abandona el fòrum</string>
|
||||
<string name="dialog_title_leave_forum">Confirmeu la sortida del Forum</string>
|
||||
<string name="dialog_message_leave_forum">Esteu segur que voleu sotir del fòrum? Els contactes amb qui heu compartit aquest fòrum podrien deixar de rebre\'n actualitzacions</string>
|
||||
<string name="dialog_message_leave_forum">Esteu segur que voleu sortir del fòrum? Els contactes amb qui heu compartit aquest fòrum podrien deixar de rebre\'n actualitzacions.</string>
|
||||
<string name="dialog_button_leave">Abandona</string>
|
||||
<string name="forum_left_toast">Abandona el fòrum</string>
|
||||
<!--Forum Sharing-->
|
||||
@@ -225,12 +234,15 @@
|
||||
<string name="forum_invitation_sent">Heu compartit el fòrum «%1$s» amb %2$s.</string>
|
||||
<string name="forum_invitations_title">Invitacions al fòrum</string>
|
||||
<string name="forum_invitation_exists">Ja heu acceptat una invitació a aquest fòrum. L\'acceptació de més invitacions augmentarà i enfortirà la comunicació al fòrum.</string>
|
||||
<string name="forum_joined_toast">Us hi heu unit al fòrum</string>
|
||||
<string name="forum_declined_toast">S\'ha rebutjat la invitació al fòrum</string>
|
||||
<string name="shared_by_format">Compartit per 1%s</string>
|
||||
<string name="forum_invitation_already_sharing">Ja esteu compartint</string>
|
||||
<string name="forum_invitation_response_accepted_sent">Heu acceptat la invitació del fòrum de %s.</string>
|
||||
<string name="forum_invitation_response_declined_sent">Heu rebutjat la invitació del fòrum de %s.</string>
|
||||
<string name="forum_invitation_response_accepted_received">%s va acceptar la invitació del fòrum.</string>
|
||||
<string name="forum_invitation_response_declined_received">%s va rebutjar la invitació del fòrum.</string>
|
||||
<string name="sharing_status">Estat de la compartició</string>
|
||||
<string name="sharing_status_forum">Qualsevol membre d\'un fòrum pot compartir-lo amb els seus contactes. Esteu compartint aquest fòrum amb els següents contactes. També hi pot haver altres membres que no pugueu veure.</string>
|
||||
<string name="shared_with">Compartit amb %1$d (%2$d en línia)</string>
|
||||
<plurals name="forums_shared">
|
||||
@@ -239,18 +251,60 @@
|
||||
</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="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>
|
||||
<string name="blogs_publish_blog_post">Publica</string>
|
||||
<string name="blogs_blog_post_created">S\'ha creat la publicació al blog</string>
|
||||
<string name="blogs_blog_post_received">S\'ha rebut una nova publicació al blog</string>
|
||||
<string name="blogs_blog_post_scroll_to">Desplaça</string>
|
||||
<string name="blogs_feed_empty_state">Aquestes són totes les entrades del blog.\n\nSembla que ningú ha publicat res encara.\n\nSigueu el primer i premeu la icona del llapis per escriure una publicació nova al blog.</string>
|
||||
<string name="blogs_remove_blog">Elimina el blog</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Esteu segur que voleu esborrar aquest blog i totes les seves publicacions?\nTingueu present que això no esborrarà el blog dels dispositius d\'altres persones.</string>
|
||||
<string name="blogs_remove_blog_ok">Elimina el blog</string>
|
||||
<string name="blogs_blog_removed">S\'ha eliminat el blog</string>
|
||||
<string name="blogs_reblog_comment_hint">Afegiu un comentari (opcional)</string>
|
||||
<string name="blogs_reblog_button">Rebloga</string>
|
||||
<!--Blog Sharing-->
|
||||
<string name="blogs_sharing_share">Compartiu el blog</string>
|
||||
<string name="blogs_sharing_error">S\'ha produït un error en compartir aquest blog.</string>
|
||||
<string name="blogs_sharing_button">Compartiu el blog</string>
|
||||
<string name="blogs_sharing_snackbar">S\'ha compartit el blog amb els contactes seleccionats</string>
|
||||
<string name="blogs_sharing_response_accepted_sent">Heu acceptat la invitació al blog de %s.</string>
|
||||
<string name="blogs_sharing_response_declined_sent">Heu refusat la invitació al blog de %s.</string>
|
||||
<string name="blogs_sharing_response_accepted_received">%s ha acceptat la invitació al blog.</string>
|
||||
<string name="blogs_sharing_response_declined_received">%s ha refusat la invitació al blog.</string>
|
||||
<string name="blogs_sharing_invitation_received">%1$s us ha compartit el blog \"%2$s\".</string>
|
||||
<string name="blogs_sharing_invitation_sent">Heu compartit el blog \"%1$s\" amb %2$s.</string>
|
||||
<string name="blogs_sharing_invitations_title">Invitacions al blog</string>
|
||||
<string name="blogs_sharing_joined_toast">Subscrit al blog</string>
|
||||
<string name="blogs_sharing_declined_toast">Invitació al blog refusada</string>
|
||||
<string name="sharing_status_blog">Qualsevol subscrit a un blog el pot compartir amb els seus contactes. Esteu compartint aquest blog amb els següents contactes. També hi pot haver altres subscrits que no veieu.</string>
|
||||
<!--RSS Feeds-->
|
||||
<string name="blogs_rss_feeds_import">Importa canal RSS</string>
|
||||
<string name="blogs_rss_feeds_import_button">Importa</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Introduïu l\'URL del canal RSS</string>
|
||||
<string name="blogs_rss_feeds_import_error">Ens sap greu! S\'ha produït un error en importar el vostre canal.</string>
|
||||
<string name="blogs_rss_feeds_manage">Gestiona els canals 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">Darrera actualització:</string>
|
||||
<string name="blogs_rss_remove_feed">Elimina el canal</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Esteu segurs que voleu eliminar aquest canal i totes les seves publicacions?\nLes publicacions que hàgiu compartit no se suprimiran dels dispositius d\'altres persones.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Elimina el canal</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">El canal no s\'ha pogut esborrar.</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">No heu importat cap canal RSS.\n\nPer què no feu clic al botó més de la part superior dreta per afegir el primer?</string>
|
||||
<string name="blogs_rss_feeds_manage_error">S\'ha produït un problema en carregar els vostres canals. Torneu-ho a provar més tard.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Xarxes</string>
|
||||
<string name="bluetooth_setting">Connecta via bluetooth</string>
|
||||
<string name="bluetooth_setting_enabled">Sempre que hi hagi contactes propers</string>
|
||||
<string name="bluetooth_setting_disabled">Només quan s\'afegeixen contactes</string>
|
||||
<string name="tor_network_setting">Connecta via Tor</string>
|
||||
<string name="tor_network_setting_never">Mai</string>
|
||||
<string name="tor_network_setting_wifi">Només amb WiFi</string>
|
||||
<string name="tor_network_setting_always">Quan s\'utilitzi la WiFi o les dades mòbils</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Seguretat</string>
|
||||
<string name="change_password">Canvia la contrasenya</string>
|
||||
@@ -261,28 +315,69 @@
|
||||
<string name="panic_setting">Configuració del botó del pànic</string>
|
||||
<string name="panic_setting_title">Botó de pànic</string>
|
||||
<string name="panic_setting_hint">Configureu com reaccionarà Briar quan feu servir una aplicació del pànic</string>
|
||||
<string name="panic_app_setting_title">Aplicació de botó de pànic</string>
|
||||
<string name="unknown_app">una aplicació desconeguda</string>
|
||||
<string name="panic_app_setting_summary">No s\'ha definit cap aplicació</string>
|
||||
<string name="panic_app_setting_none">Cap</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmeu l\'aplicació del pànic</string>
|
||||
<string name="dialog_message_connect_panic_app">Esteu segurs que voleu permetre que %1$s activi accions destructives del botó del pànic?</string>
|
||||
<string name="lock_setting_title">Tanca la sessió</string>
|
||||
<string name="lock_setting_summary">Tanca la sessió de Briar si es prem un botó del pànic</string>
|
||||
<string name="purge_setting_title">Esborra el compte</string>
|
||||
<string name="purge_setting_summary">Suprimeix el compte de Briar si es prem un botó del pànic. Precaució: s\'eliminarà permanentment les vostres identitats, contactes i missatges</string>
|
||||
<string name="uninstall_setting_title">Desinstal·la Briar</string>
|
||||
<string name="uninstall_setting_summary">Això requereix confirmació manual en una situació de pànic</string>
|
||||
<!--Settings Notifications-->
|
||||
<string name="notification_settings_title">Notificacions</string>
|
||||
<string name="notify_private_messages_setting_title">Missatges privats</string>
|
||||
<string name="notify_private_messages_setting_summary">Mostra els avisos per als missatges privats</string>
|
||||
<string name="notify_group_messages_setting_title">Missatges grupals</string>
|
||||
<string name="notify_group_messages_setting_summary">Mostra alertes per als missatges grupals</string>
|
||||
<string name="notify_forum_posts_setting_title">Publicacions del fòrum</string>
|
||||
<string name="notify_forum_posts_setting_summary">Mostra alertes per a les publicacions del fòrum</string>
|
||||
<string name="notify_blog_posts_setting_title">Publicacions al blog</string>
|
||||
<string name="notify_blog_posts_setting_summary">Mostra alertes per les publicacions al blog</string>
|
||||
<string name="notify_vibration_setting">Vibra</string>
|
||||
<string name="notify_lock_screen_setting_title">Bloca la pantalla</string>
|
||||
<string name="notify_lock_screen_setting_summary">Mostra notificacions a la pantalla de bloqueig</string>
|
||||
<string name="notify_sound_setting">So</string>
|
||||
<string name="notify_sound_setting_default">To de trucada predeterminat</string>
|
||||
<string name="notify_sound_setting_disabled">Cap</string>
|
||||
<string name="choose_ringtone_title">Trieu el to de trucada</string>
|
||||
<string name="cannot_load_ringtone">No s\'ha pogut carregar el to de trucada</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Comentaris</string>
|
||||
<string name="send_feedback">Envieu comentaris</string>
|
||||
<!--Link Warning-->
|
||||
<string name="link_warning_title">Avís d\'enllaç</string>
|
||||
<string name="link_warning_intro">L\'enllaç s\'obrirà amb una aplicació externa.</string>
|
||||
<string name="link_warning_text">Açò podria usar-se per a identificar-vos. Penseu si confieu prou en la persona que us ha enviat l\'enllaç i si convindria obrir-lo amb Orfox.</string>
|
||||
<string name="link_warning_open_link">Obri l\'enllaç</string>
|
||||
<!--Crash Reporter-->
|
||||
<string name="crash_report_title">Informe de bloqueig de Briar</string>
|
||||
<string name="briar_crashed">Ens sap greu, el Briar s\'ha tancat inesperadament.</string>
|
||||
<string name="not_your_fault">Això no és culpa vostra.</string>
|
||||
<string name="please_send_report">Ajuda\'ns a construir un Briar millor enviant-nos un informe de fallida.</string>
|
||||
<string name="report_is_encrypted">Ens comprometem a què l\'informe es xifra i s\'envia de manera segura.</string>
|
||||
<string name="feedback_title">Comentaris</string>
|
||||
<string name="describe_crash">Descriu el que hi ha succeït (opcional)</string>
|
||||
<string name="enter_feedback">Introduïu els vostres comentaris</string>
|
||||
<string name="optional_contact_email">La vostra adreça de correu (opcional)</string>
|
||||
<string name="include_debug_report_crash">Inclou dades anònimes sobre el bloqueig</string>
|
||||
<string name="include_debug_report_feedback">Inclou dades anònimes sobre el dispositiu</string>
|
||||
<string name="could_not_load_report_data">No s\'han pogut carregar les dades de l\'informe.</string>
|
||||
<string name="send_report">Envia l\'informe</string>
|
||||
<string name="close">Tanca</string>
|
||||
<string name="dev_report_saved">S\'ha desat l\'informe. Se us enviarà la propera vegada que inicieu sessió a Briar.</string>
|
||||
<!--Sign Out-->
|
||||
<string name="progress_title_logout">S\'està sortint del Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">S\'ha detectat superposició de la pantalla</string>
|
||||
<string name="screen_filter_body">Una altra aplicació es troba damunt de Briar. Per protegir la vostra seguretat, Briar no respondrà als tocs quan s\'aprovi una altra aplicació.\n\nLes següents aplicacions poden estar dibuixant a la part superior:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Permet que aquestes aplicacions dibuixin a la part superior</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Permís de la càmera</string>
|
||||
<string name="permission_camera_request_body">Per escanejar el codi QR, Briar necessita accés a la càmera.</string>
|
||||
<string name="permission_camera_denied_body">Heu denegat l\'accés a la càmera, però l\'addició de contactes requereix utilitzar la càmera.\n\nTingueu en compte permetre l\'accés.</string>
|
||||
<string name="permission_camera_denied_toast">No s\'ha concedit el permís de la càmera</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
<string name="dialog_title_lost_password">Ztracené heslo</string>
|
||||
<string name="dialog_message_lost_password">Váš Briar účet je šifrován a uložen ve vašem zařízení, nikoli v cloudu, z tohoto důvodu není možné obnovit Vaše heslo. Chcete odstranit svůj účet a začít znovu?\n\nUpozornění: Vaše identita, kontakty a zprávy budou permanentně ztraceny.</string>
|
||||
<string name="startup_failed_notification_title">Briar nemohl být spuštěn</string>
|
||||
<string name="startup_failed_notification_text">Můžete zkusit Briar přeinstalovat.</string>
|
||||
<string name="startup_failed_activity_title">Spuštění Briar selhalo.</string>
|
||||
<string name="startup_failed_db_error">Z nějakého důvodu je Briar databáze poškozena a není možná její obnova. Váš účet, data a všechny vaše kontakty jsou ztraceny. Bohužel musíte Briar přeinstalovat a nastavit nový účet.</string>
|
||||
<string name="startup_failed_service_error">Briar nemohl spustit vyžadovaný plugin. Tento problém vyřeší přeinstalování Briar. Mějte prosím na vědomí, že přeinstalováním ztratíte veškerá data pro váš účet, protože Briar nepoužívá centralizované ukládání vašich dat na serverech.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Toto je testovací verze Briar. Váš účet a jeho platnost vyprší po %d dnech a není možné ho obnovit.</item>
|
||||
@@ -88,7 +86,8 @@
|
||||
<string name="delete">Odstranit</string>
|
||||
<string name="accept">Přijmout</string>
|
||||
<string name="decline">Odmítnout</string>
|
||||
<string name="online">Online</string>
|
||||
<string name="options">Možnosti</string>
|
||||
<string name="online">Online</string>
|
||||
<string name="offline">Offline</string>
|
||||
<string name="send">Odeslat</string>
|
||||
<string name="allow">Povolit</string>
|
||||
@@ -378,7 +377,6 @@
|
||||
<string name="progress_title_logout">Odhlásit se z Briar...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Bylo zjištěno překrytí obrazovky</string>
|
||||
<string name="screen_filter_body">Jiná aplikace překrývá Briar. Pro vaši bezpečnost, Briar nebude odpovídat na kliknutí, pokud jej bude jiná aplikace překrývat.\n\nZkuste vypnout následující aplikace, když používáte Briar:\n\n%1$s</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Oprávnění pro přístup k fotoaparátu</string>
|
||||
<string name="permission_camera_request_body">Pro scan QR kódu, Briar vyžaduje přístup k fotoaparátu.</string>
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
<string name="dialog_title_lost_password">Passwort vergessen</string>
|
||||
<string name="dialog_message_lost_password">Dein Briar-Konto ist auf deinem Gerät verschlüsselt und nicht in der Cloud gespeichert, deshalb kannst du dein Passwort nicht zurücksetzen. Willst du dein Konto löschen und neu beginnen?\n\nAchtung: Deine bestehenden Identitäten, Kontakte und Nachrichten gehen dann für immer verloren.</string>
|
||||
<string name="startup_failed_notification_title">Briar konnte nicht gestartet werden</string>
|
||||
<string name="startup_failed_notification_text">Möglicherweise hilft eine Neuinstallation von Briar.</string>
|
||||
<string name="startup_failed_activity_title">Fehler beim Starten von Briar</string>
|
||||
<string name="startup_failed_db_error">Aus irgendeinem Grund ist deine Briar-Datenbank irreparabel beschädigt. Dein Konto, deine Daten und alle deinen Kontakte sind verloren. Leider musst du Briar neu installieren und ein neues Konto einrichten.</string>
|
||||
<string name="startup_failed_service_error">Briar konnte ein benötigtes Plugin nicht starten. Normalerweise kann das Problem durch eine Neuinstallation von Briar gelöst werden. Eine Neuinstallation führt jedoch zum Verlust deines Kontos und aller dazugehörigen Daten, da Briar deine Daten nicht auf zentralen Servern speichert.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Dies ist eine Testversion von Briar. Dein Konto läuft in %d Tag ab und kann nicht verlängert werden.</item>
|
||||
@@ -83,7 +81,8 @@
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="accept">Annehmen</string>
|
||||
<string name="decline">Ablehnen</string>
|
||||
<string name="online">Online</string>
|
||||
<string name="options">Optionen</string>
|
||||
<string name="online">Online</string>
|
||||
<string name="offline">Offline</string>
|
||||
<string name="send">Senden</string>
|
||||
<string name="allow">Erlauben</string>
|
||||
@@ -368,7 +367,8 @@
|
||||
<string name="progress_title_logout">Von Briar abmelden...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Bildschirmüberlagerung erkannt</string>
|
||||
<string name="screen_filter_body">Eine andere App überlagert Briar. Um deine Sicherheit zu gewährleisten, reagiert Briar in diesem Fall nicht auf deine Eingaben.\n\nBeende deswegen die folgenden Apps während der Verwendung von Briar:\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Eine andere App überlagert Briar. Um deine Sicherheit zu gewährleisten, reagiert Briar in diesem Fall nicht auf deine Eingaben.\n\nDie folgenden Apps könnten überlagern:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Erlauben diesen Apps die Bildschirmüberlagerung</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Berechtigung Kamera</string>
|
||||
<string name="permission_camera_request_body">Um den QR-Code zu scannen, benötigt Briar Zugriff auf die Kamera.</string>
|
||||
|
||||
@@ -31,9 +31,11 @@
|
||||
<string name="dialog_title_lost_password">Contraseña olvidada</string>
|
||||
<string name="dialog_message_lost_password">Tu cuenta de Briar se almacena de manera cifrada en tu dispositivo y no en ninguna nube, así que no podemos reiniciar tu contraseña. ¿Deseas eliminar tu cuenta y empezar de nuevo?\n\nAdvertencia: tus identidades, contactos y mensajes se perderán para siempre.</string>
|
||||
<string name="startup_failed_notification_title">Briar no pudo iniciarse</string>
|
||||
<string name="startup_failed_notification_text">Quizá tengas que reinstalar Briar.</string>
|
||||
<string name="startup_failed_notification_text">Toca para más información.</string>
|
||||
<string name="startup_failed_activity_title">Fallo al iniciar Briar</string>
|
||||
<string name="startup_failed_db_error">Por alguna razón, su base de datos Briar está dañada irreparablemente. Su cuenta, sus datos y todos sus contactos están perdidos. Desafortunadamente, necesita reinstalar Briar y configurar una nueva cuenta.</string>
|
||||
<string name="startup_failed_db_error">Por alguna razón, la base de datos de Briar se ha dañada irreparablemente. Tu cuenta, tus datos y tus contactos se han perdido. Lamentablemente, necesitas reinstalar Briar y configurar una nueva cuenta. Puedes hacerlo seleccionando «He olvidado mi contraseña» al arrancar Briar.</string>
|
||||
<string name="startup_failed_data_too_old_error">Tu cuenta se creó en una versión antigua de esta apli y no puede abrirse con la versión actual. Debes instalar la versión anterior o eliminar tu cuenta eligiendo «He olvidado mi contraseña» al arrancar Briar.</string>
|
||||
<string name="startup_failed_data_too_new_error">La versión de esta apli es demasiado antigua. Por favor, actualiza a la última versión y prueba de nuevo.</string>
|
||||
<string name="startup_failed_service_error">Briar no pudo iniciar un complemento necesario. Reinstalar Briar suele solucionar el problema. Sin embargo, ten en cuenta que perderás tu cuenta y todos los datos asociados ya que Briar no almacena esta información en ningún servidor central.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Esta es una versión de prueba de Briar. Su cuenta expirará en %d día y no podrá ser renovada.</item>
|
||||
@@ -83,7 +85,8 @@
|
||||
<string name="delete">Borrar</string>
|
||||
<string name="accept">Aceptar</string>
|
||||
<string name="decline">Rechazar</string>
|
||||
<string name="online">En línea</string>
|
||||
<string name="options">Configuración</string>
|
||||
<string name="online">En línea</string>
|
||||
<string name="offline">Desconectado</string>
|
||||
<string name="send">Enviar</string>
|
||||
<string name="allow">Permitir</string>
|
||||
@@ -94,6 +97,7 @@
|
||||
<string name="show_onboarding">Mostrar diálogo de ayuda</string>
|
||||
<string name="fix">Reparar</string>
|
||||
<string name="help">Ayuda</string>
|
||||
<string name="sorry">Disculpe</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Parece que eres nuevo por aquí y no tienes aún contactos.\n\nPulsa el signo + en la parte superior y sigue las instrucciones para añadir amigos a tu lista.\n\nPor favor, recuerda: sólo puedes añadir nuevos contactos cara a cara para evitar que nadie suplante tu identidad o lea tus mensajes en el futuro. </string>
|
||||
<string name="date_no_private_messages">Sin mensajes.</string>
|
||||
@@ -115,6 +119,7 @@
|
||||
<string name="contact_already_exists">El contacto %s ya existe</string>
|
||||
<string name="contact_exchange_failed">El intercambio del contacto falló</string>
|
||||
<string name="qr_code_invalid">El código QR no es válido</string>
|
||||
<string name="qr_code_unsupported">El código QR que intenta escanear pertenece a una versión anterior de %s que ya no es compatible.\n\nAsegúrese de que ambos estén ejecutando la última versión y luego inténtelo de nuevo.</string>
|
||||
<string name="camera_error">Error de cámara</string>
|
||||
<string name="connecting_to_device">Conectando al dispositivo\u2026</string>
|
||||
<string name="authenticating_with_device">Autentificándose con el dispositivo\u2026</string>
|
||||
@@ -368,7 +373,8 @@
|
||||
<string name="progress_title_logout">Saliendo de Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Superposición de pantalla detectada</string>
|
||||
<string name="screen_filter_body">Otra aplicación se está mostrando encima de Briar. Por seguridad, Briar no responderá a los toques mientras se muestre otra aplicación encima.\n\nIntenta apagar las siguientes aplicaciones cuando uses Briar:\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Otra aplicación se está mostrando por encima de Briar. Por seguridad, Briar no reaccionará a los toques mientras otras aplicaciones se muestren por encima.\n\nLas siguientes aplis pueden ser las causantes:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Permitir a estas aplicaciones a mostrarse por encima</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Permiso de cámara</string>
|
||||
<string name="permission_camera_request_body">Para escanear el código QR, Briar necesita acceso a la cámara.</string>
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
<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">Agian Briar berrinstalatu behar duzu.</string>
|
||||
<string name="startup_failed_activity_title">Briar abio-hutsegitea</string>
|
||||
<string name="startup_failed_db_error">Dena delakoagatik, zure Briar datu-basea hondatuta dago eta ezin da konpondu. Zure kontua, zure datuak eta zure kontaktuak galdu dira. Zoritxarrez Briar berrinstalatu behar duzu eta kontu berria sortu.</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>
|
||||
@@ -83,7 +81,8 @@
|
||||
<string name="delete">Ezabatu</string>
|
||||
<string name="accept">Onartu</string>
|
||||
<string name="decline">Ukatu</string>
|
||||
<string name="online">Konektatuta</string>
|
||||
<string name="options">Aukerak</string>
|
||||
<string name="online">Konektatuta</string>
|
||||
<string name="offline">Deskonektatuta</string>
|
||||
<string name="send">Bidali</string>
|
||||
<string name="allow">Baimendu</string>
|
||||
@@ -94,6 +93,7 @@
|
||||
<string name="show_onboarding">Erakutsi laguntza elkarrizketa-koadroa</string>
|
||||
<string name="fix">Konpondu</string>
|
||||
<string name="help">Laguntza</string>
|
||||
<string name="sorry">Sentitzen dugu</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Badirudi heldu berria zarela eta ez duzula kontakturik hemen oraindik.\n\nSakatu goiko + ikonoa eta jarraitu argibideak lagunak zure zerrendara gehitzeko.\nGogoratu: Kontaktu berriak aurrez aurre besterik ezin dira gehitu beste inork zure itxurak ez egiteko edo zure mezuak ez irakurtzeko.</string>
|
||||
<string name="date_no_private_messages">Mezurik ez.</string>
|
||||
@@ -115,6 +115,7 @@
|
||||
<string name="contact_already_exists">%s kontaktua badago aurretik</string>
|
||||
<string name="contact_exchange_failed">Kontaktuen trukeak huts egin du</string>
|
||||
<string name="qr_code_invalid">QR kodea baliogabea da</string>
|
||||
<string name="qr_code_unsupported">Eskaneatzen saiatu zaren QR kodea %s programaren bertsio zahar bati dagokio eta ez du euskarririk jada.\n\nZiurtatu biak azken bertsioa erabiltzen duzuela eta saiatu berriro.</string>
|
||||
<string name="camera_error">Kameraren errorea</string>
|
||||
<string name="connecting_to_device">Gailura konektatzen\u2026</string>
|
||||
<string name="authenticating_with_device">Gailuarekin autentifikatzen\u2026</string>
|
||||
@@ -293,7 +294,7 @@
|
||||
<string name="blogs_rss_feeds_manage_error">Arazo bat egon da zure jarioak kargatzean. Saiatu berriro geroago.</string>
|
||||
<!--Settings Network-->
|
||||
<string name="network_settings_title">Sareak</string>
|
||||
<string name="bluetooth_setting">Bluetooth bidez konektatuta</string>
|
||||
<string name="bluetooth_setting">Konektatu Bluetooth bidez</string>
|
||||
<string name="bluetooth_setting_enabled">Kontaktuak hurbil daudeneak</string>
|
||||
<string name="bluetooth_setting_disabled">Kontaktuak gehitzean besterik ez</string>
|
||||
<string name="tor_network_setting">Konektatu Tor bidez</string>
|
||||
@@ -333,7 +334,7 @@
|
||||
<string name="notify_blog_posts_setting_title">Blog sarrerak</string>
|
||||
<string name="notify_blog_posts_setting_summary">Erakutsi blog sarreren alertak</string>
|
||||
<string name="notify_vibration_setting">Bibratu</string>
|
||||
<string name="notify_lock_screen_setting_title">Blokeatu pantaila</string>
|
||||
<string name="notify_lock_screen_setting_title">Blokeo-pantaila</string>
|
||||
<string name="notify_lock_screen_setting_summary">Erakutsi jakinarazpenak blokeo-pantailan</string>
|
||||
<string name="notify_sound_setting">Soinua</string>
|
||||
<string name="notify_sound_setting_default">Lehenetsitako dei-doinua</string>
|
||||
@@ -368,7 +369,8 @@
|
||||
<string name="progress_title_logout">Briar saioa amaitzen...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Pantaila gainjartzea antzeman da</string>
|
||||
<string name="screen_filter_body">Briar aplikazioaren gainean marrazten ari den beste aplikazio bat dago. Zure segurtasuna babesteko Briar aplikazioak ez dio ukimenari erantzungo beste aplikazio bat gainean marrazten dagoen bitartean.\n\nSaiatu honako aplikazioak ixten Briar erabiltzean:\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Briar aplikazioaren gainean marrazten ari den beste aplikazio bat dago, Briar aplikazioak ez dio ukimenari erantzungo beste aplikazio bat gainean marrazten dagoen bitartean.\n\nHauek dira gainean marrazten egon daitezkeen aplikazioak:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Baimendu aplikazio hauei gainean idazten</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Kamera baimena</string>
|
||||
<string name="permission_camera_request_body">QR kodea eskaneatzeko Briar-ek kamera atzitu behar du.</string>
|
||||
|
||||
@@ -31,9 +31,8 @@
|
||||
<string name="dialog_title_lost_password">Unohtunut salasana</string>
|
||||
<string name="dialog_message_lost_password">Sinun Briar tili on tallennettu salattuna sinun laitteelle, ei pilvipalvelimelle, joten emme voi palauttaa salasanaasi. Haluatko poistaa tilisi ja aloittaa alusta?\n\nVaroitus: Tulet menettämään tunnuksesi, yhteystietosi ja viestisi lopullisesti.</string>
|
||||
<string name="startup_failed_notification_title">Briarin käynnistys epäonnistui</string>
|
||||
<string name="startup_failed_notification_text">Sinun täytyy ehkä uudelleenasentaa Briar.</string>
|
||||
<string name="startup_failed_notification_text">Napauta nähdäksesi lisätietoja.</string>
|
||||
<string name="startup_failed_activity_title">Briarin käynnistys epäonnistui</string>
|
||||
<string name="startup_failed_db_error">Jostain syystä sinun Briar tietokanta on korruptoitunut korjauskelvottomaksi. Tilisi, tietosi ja kaikki yhteystietosi on menetetty. Valitettavasti, sinun täytyy asentaa Briar uudelleen ja luoda uusi tili.</string>
|
||||
<string name="startup_failed_service_error">Briar ei kyennyt käynnistämään vaadittua liitännäistä. Briarin uudelleenasennus yleensä korjaa ongelman. Huomaa kuitenkin, että tulet menettämään tilisi ja kaikki siihen liittyvä data koska Briar ei käytä palvelimia sinun tietojesi säilyttämiseen.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Tämä on Briarin testiversio. Sinun tilisi tulee vanhentumaan %d päivän päästä eikä sitä voi uusia.</item>
|
||||
@@ -83,7 +82,8 @@
|
||||
<string name="delete">Poista</string>
|
||||
<string name="accept">Hyväksy</string>
|
||||
<string name="decline">Kieltäydy</string>
|
||||
<string name="online">Verkossa</string>
|
||||
<string name="options">Valitsimet</string>
|
||||
<string name="online">Verkossa</string>
|
||||
<string name="offline">Poissa verkosta</string>
|
||||
<string name="send">Lähetä</string>
|
||||
<string name="allow">Salli</string>
|
||||
@@ -94,6 +94,7 @@
|
||||
<string name="show_onboarding">Näytä apudialogi</string>
|
||||
<string name="fix">Korjaa</string>
|
||||
<string name="help">Ohje</string>
|
||||
<string name="sorry">Anteeksi</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>
|
||||
@@ -368,7 +369,8 @@
|
||||
<string name="progress_title_logout">Kirjaudutaan ulos Briarista...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Näyttökerros havaittu</string>
|
||||
<string name="screen_filter_body">Toinen sovellus on piirtämässä Briarin päälle. Suojellakseen sinun turvallisuutta, Briar ei tule vastaamaan kosketuksiin silloin kun toinen sovellus on piirtämässä Briarin päälle.\n\nYritä sulkea seuraavat sovellukset silloin kun käytät Briaria:\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Toinen sovellus on piirtämässä Briarin päälle. Suojellakseen sinun turvallisuutta, Briar ei tule vastaamaan kosketuksiin silloin kun toinen sovellus on piirtämässä Briarin päälle.\n\nOn mahdollista, että seuraavat sovellukset piirtävät päälle:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Salli näiden sovellusten piirtää päälle</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Kameralupa</string>
|
||||
<string name="permission_camera_request_body">Skannatakseen QR koodin, Briar tarvitsee luvan käyttää kameraa.</string>
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Configuration de Briar</string>
|
||||
<string name="choose_nickname">Choisir votre pseudonyme</string>
|
||||
<string name="setup_title">Bienvenue à Briar</string>
|
||||
<string name="setup_name_explanation">Votre pseudonyme sera affiché à côté de tout contenu que vous publierez. Vous pourrez le modifier après avoir créé votre compte.</string>
|
||||
<string name="setup_next">Suivant</string>
|
||||
<string name="setup_password_intro">Choisir un mot de passe</string>
|
||||
<string name="setup_password_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non dans le nuage. Si vous désinstallez Briar ou oubliez votre mot de passe, il n’existe aucune façon de récupérer votre compte.\n\nChoisissez un mot de passe long qui sera difficile à deviner, par exemple quatre mots au hasard ou dix lettres, chiffres et symboles au hasard.</string>
|
||||
<string name="setup_doze_title">Connexions d’arrière-plan</string>
|
||||
<string name="setup_doze_intro">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan.</string>
|
||||
<string name="setup_doze_explanation">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan. Veuillez désactiver les optimisations de la batterie afin que Briar puisse rester connectée.</string>
|
||||
<string name="setup_doze_button">Autoriser les connexions</string>
|
||||
<string name="choose_nickname">Choisir votre pseudonyme</string>
|
||||
<string name="choose_password">Choisir votre mot de passe</string>
|
||||
<string name="confirm_password">Confirmer votre mot de passe</string>
|
||||
<string name="name_too_long">Le nom est trop long</string>
|
||||
<string name="password_too_weak">Le mot de passe est trop faible</string>
|
||||
<string name="passwords_do_not_match">Les mots de passe ne correspondent pas</string>
|
||||
<string name="create_account_button">Créer un compte</string>
|
||||
<string name="more_info">Plus d’informations</string>
|
||||
<string name="don_t_ask_again">Ne plus demander</string>
|
||||
<string name="setup_huawei_text">Veuillez toucher le bouton ci-dessous et vous assurer que Briar est protégée dans l’écran « Applis protégées ».</string>
|
||||
<string name="setup_huawei_button">Protéger Briar</string>
|
||||
<string name="setup_huawei_help">Si Briar n’est pas ajoutée à la liste des applis protégées, elle ne pourra pas fonctionner en arrière-plan.</string>
|
||||
<string name="warning_dozed">%s n’a pas pu fonctionner en arrière-plan</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">Saisir votre mot de passe :</string>
|
||||
<string name="try_again">Le mot de passe est erroné, ressayez</string>
|
||||
@@ -17,14 +31,17 @@
|
||||
<string name="dialog_title_lost_password">Mot de passe oublié</string>
|
||||
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil, pas dans le nuage, et nous ne pouvons donc pas réinitialiser votre mot de passe. Voulez-vous supprimer votre compte et recommencer ?\n\nAttention : vos identités, contacts et messages seront perdus irrémédiablement.</string>
|
||||
<string name="startup_failed_notification_title">Impossible de démarrer Briar</string>
|
||||
<string name="startup_failed_notification_text">Il vous faudra peut-être réinstaller 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 une raison indéterminée, votre base de données Briar est corrompue sans espoir de récupération. Vos comptes, données et contacts sont perdus. Vous devez malheureusement réinstaller Briar et configurer un nouveau compte.</string>
|
||||
<string name="startup_failed_db_error">Pour 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_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 bêta de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
|
||||
<item quantity="other">Ceci est une version bêta de Briar. Votre compte arrivera à expiration dans %d jours et ne peut pas être renouvelé.</item>
|
||||
<item quantity="one">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
|
||||
<item quantity="other">Ceci est une version de test de Briar. Votre compte arrivera à expiration dans %d jours et ne peut pas être renouvelé.</item>
|
||||
</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>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">Ouvrir le tiroir de navigation</string>
|
||||
@@ -68,7 +85,8 @@
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="accept">Accepter</string>
|
||||
<string name="decline">Refuser</string>
|
||||
<string name="online">En ligne</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="online">En ligne</string>
|
||||
<string name="offline">Hors ligne</string>
|
||||
<string name="send">Envoyer</string>
|
||||
<string name="allow">Autoriser</string>
|
||||
@@ -77,6 +95,9 @@
|
||||
<string name="ellipsis">...</string>
|
||||
<string name="text_too_long">Le texte saisi est trop long</string>
|
||||
<string name="show_onboarding">Afficher la fenêtre d’aide</string>
|
||||
<string name="fix">Corriger</string>
|
||||
<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="date_no_private_messages">Aucun message.</string>
|
||||
@@ -88,19 +109,21 @@
|
||||
<string name="contact_deleted_toast">Le contact a été supprimé</string>
|
||||
<!--Adding Contacts-->
|
||||
<string name="add_contact_title">Ajouter un contact</string>
|
||||
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin d’éviter que quelqu’un se fasse passer pour vous et puisse lire vos messages à l’avenir.</string>
|
||||
<string name="face_to_face">Vous devez rencontrer la personne que vous voulez ajouter comme contact, afin d’éviter que quelqu’un se fasse passer pour vous et puisse lire vos messages à l’avenir.</string>
|
||||
<string name="continue_button">Continuer</string>
|
||||
<string name="connection_failed">Échec de connexion</string>
|
||||
<string name="try_again_button">Ressayer</string>
|
||||
<string name="waiting_for_contact_to_scan">En attente de lecture du code QR par le contact et de sa connexion\u2026</string>
|
||||
<string name="connection_failed">Échec de connexion</string>
|
||||
<string name="try_again_button">Ressayer</string>
|
||||
<string name="waiting_for_contact_to_scan">En attente de lecture du code QR par le contact et de sa connexion\u2026</string>
|
||||
<string name="exchanging_contact_details">Échange des renseignements de contact\u2026</string>
|
||||
<string name="contact_added_toast">Contact ajouté : %s</string>
|
||||
<string name="contact_added_toast">Contact ajouté : %s</string>
|
||||
<string name="contact_already_exists">Le contact %s existe déjà</string>
|
||||
<string name="contact_exchange_failed">Échec d’échange de contacts</string>
|
||||
<string name="qr_code_invalid">Le code QR est invalide</string>
|
||||
<string name="qr_code_unsupported">Le code QR que vous tentez de lire appartient à une ancienne version de %s qui n’est plus prise en charge.\n\nVeuillez vous assurer d’utiliser tous les deux la dernière version et ressayer.</string>
|
||||
<string name="camera_error">Erreur de la caméra</string>
|
||||
<string name="connecting_to_device">Connexion à l’appareil\u2026</string>
|
||||
<string name="authenticating_with_device">Autentification avec l’appareil\u2026</string>
|
||||
<string name="connection_aborted_local">Nous avons interrompu la connexion ! Cela pourrait signer que quelqu’un tente d’interférer avec votre connexion</string>
|
||||
<string name="connection_aborted_local">La connexion a été interrompue ! Cela pourrait signifier que quelqu’un tente d’interférer avec votre connexion.</string>
|
||||
<string name="connection_aborted_remote">Votre contact a interrompu la connexion ! Cela pourrait signer que quelqu’un tente d’interférer avec votre connexion</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">Présenter vos contacts</string>
|
||||
@@ -321,6 +344,7 @@
|
||||
<string name="notify_sound_setting_default">Sonnerie par défaut</string>
|
||||
<string name="notify_sound_setting_disabled">Aucun</string>
|
||||
<string name="choose_ringtone_title">Choisir une sonnerie</string>
|
||||
<string name="cannot_load_ringtone">Impossible de charger la sonnerie</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">Rétroaction</string>
|
||||
<string name="send_feedback">Envoyer une rétroaction</string>
|
||||
@@ -349,5 +373,11 @@
|
||||
<string name="progress_title_logout">Déconnexion de Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Une superposition d’écran a été détectée</string>
|
||||
<string name="screen_filter_body">Une autre appli s’affiche par dessus Briar. Pour protéger votre sécurité, Briar ne répondra pas au toucher si autre appli s’affiche par dessus.\n\nEssayez de fermer les applis suivantes lorsque vous utilisez Briar :\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Une autre appli s’affiche par-dessus Briar. Pour protéger votre sécurité, Briar ne répondra pas au toucher si une autre appli s’affiche par-dessus.\n\nLes applis suivantes pourraient s’afficher par-dessus Briar : \n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Permettre à ces applis de s’afficher par-dessus</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Accès à la caméra</string>
|
||||
<string name="permission_camera_request_body">Pour lire le code QR, Briar doit accéder à la caméra.</string>
|
||||
<string name="permission_camera_denied_body">Vous avez refusé 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>
|
||||
</resources>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<string name="dialog_title_lost_password">Clave perdida</string>
|
||||
<string name="dialog_message_lost_password">Briar almacena a súa configuración encriptada no dispositivo, non na nube, así que non podemos restabelecer a súa clave. Querrías borrar a túa conta e empezar de novo?\n\nPrecaución: As túas identidades, contactos e mensaxes serán eliminadas de forma permanente.</string>
|
||||
<string name="startup_failed_notification_title">Briar non puido iniciarse</string>
|
||||
<string name="startup_failed_notification_text">Pode que precises instalar Briar de novo</string>
|
||||
<string name="startup_failed_activity_title">Fallo de Inicio de Briar</string>
|
||||
<string name="startup_failed_service_error"> Briar non puido iniciar un complemento necesario. Xeralmente reinstalar Briar resolve este problema. Teña en conta que entón perderá a súa conta e todos os datos asociados a esta pois Briar non está a utilizar servidores centrais para almacenar os seus datos.</string>
|
||||
<string name="expiry_date_reached">Este software caducou.\nGrazas por probalo!</string>
|
||||
@@ -47,7 +46,8 @@
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="accept">Aceptar</string>
|
||||
<string name="decline">Rexeitar</string>
|
||||
<string name="online">Conectado</string>
|
||||
<string name="options">Opcións</string>
|
||||
<string name="online">Conectado</string>
|
||||
<string name="offline">Desconectado</string>
|
||||
<string name="send">Enviar</string>
|
||||
<string name="allow">Permitir</string>
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
זהירות: הזהויות שלכם, אנשי הקשר וההודעות יאבדו לצמיתות.</string>
|
||||
<string name="startup_failed_notification_title">אפליקציית בריאר נכשלה באיתחול</string>
|
||||
<string name="startup_failed_notification_text">יכול להיות שתאלצו להתקין מחדש את אפליקציית בריאר.</string>
|
||||
<string name="startup_failed_activity_title">איתחול בריאר נכשל</string>
|
||||
<string name="startup_failed_service_error">בריאר לא הצליח לאתחל תוסף הכרחי. התקנת בריאר מחדש לרוב פותרת בעייה זו. שימו לב שאז תאבדו את חשבונכם וכל המידע המשוייך אליו כיוון שבריאר לא משתמש בשרתים מרכזיים לשמירת המידע שלכם עליהם.</string>
|
||||
<string name="expiry_date_reached">פג תוקפה של תוכנה זו.
|
||||
@@ -66,7 +65,8 @@
|
||||
<string name="delete">מחק</string>
|
||||
<string name="accept">קבל</string>
|
||||
<string name="decline">סרב</string>
|
||||
<string name="online">מחובר</string>
|
||||
<string name="options">אפשרויות</string>
|
||||
<string name="online">מחובר</string>
|
||||
<string name="offline">לא מחובר</string>
|
||||
<string name="send">שלח</string>
|
||||
<string name="allow">להתיר</string>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<!--Setup-->
|
||||
<string name="setup_title">Briar में आपका स्वागत है</string>
|
||||
<string name="setup_name_explanation">आपके उपनाम आप पोस्ट किसी भी सामग्री के बगल में दिखाया जाएगा। आप अपना खाता बनाने के बाद इसे बदल नहीं सकते</string>
|
||||
<string name="setup_next">अगला</string>
|
||||
<string name="setup_password_intro">अपना पासवर्ड चुनें</string>
|
||||
<string name="setup_password_explanation">आपका ब्रियर खाता आपके डिवाइस पर एन्क्रिप्ट किया गया है, न कि क्लाउड में। यदि आप अपना पासवर्ड भूल जाते हैं या बियर को अनइंस्टॉल करते हैं, तो आपका खाता पुनर्प्राप्त करने का कोई तरीका नहीं है। \ N \ n एक लंबे पासवर्ड चुनें जो अनुमान लगाने में मुश्किल हो, जैसे चार यादृच्छिक शब्द, या दस यादृच्छिक वर्ण, संख्याएं और प्रतीकों।</string>
|
||||
<string name="setup_doze_title">पृष्ठभूमि कनेक्शन</string>
|
||||
<string name="setup_doze_intro">संदेश प्राप्त करने के लिए, बैर को पृष्ठभूमि में जुड़े रहने की आवश्यकता है।</string>
|
||||
<string name="setup_doze_explanation">संदेश प्राप्त करने के लिए, बैर को पृष्ठभूमि में जुड़े रहने की आवश्यकता है। कृपया बैटरी ऑप्टिमाइजेशन अक्षम करें ताकि ब्रियर कनेक्ट रह सकें।</string>
|
||||
<string name="setup_doze_button">कनेक्शन की अनुमति दें</string>
|
||||
<string name="choose_nickname">आपका मुंहबोला नाम चुनें</string>
|
||||
<string name="choose_password">अपना पासवर्ड चुनें</string>
|
||||
<string name="confirm_password">अपने पासवर्ड की पुष्टि करें</string>
|
||||
@@ -10,6 +18,11 @@
|
||||
<string name="passwords_do_not_match">पासवर्ड मेल नहीं खाते</string>
|
||||
<string name="create_account_button">खाता बनाएं</string>
|
||||
<string name="more_info">अधिक जानकारी</string>
|
||||
<string name="don_t_ask_again">फिर से मत पूछो</string>
|
||||
<string name="setup_huawei_text">कृपया नीचे दिए गए बटन को टैप करें और सुनिश्चित करें कि \"संरक्षित ऐप्स\" स्क्रीन पर बियर सुरक्षित है</string>
|
||||
<string name="setup_huawei_button">Briar की रक्षा करें</string>
|
||||
<string name="setup_huawei_help">अगर Briar संरक्षित ऐप्स सूची में नहीं जोड़ा गया है, तो यह पृष्ठभूमि में चलने में असमर्थ होगा।</string>
|
||||
<string name="warning_dozed">%sपृष्ठभूमि में चलाने में असमर्थ था</string>
|
||||
<!--Login-->
|
||||
<string name="enter_password">अपना पासवर्ड डालें:</string>
|
||||
<string name="try_again">गलत पासवर्ड, फिर से प्रयास करें</string>
|
||||
@@ -18,9 +31,13 @@
|
||||
<string name="dialog_title_lost_password">पासवर्ड खो गया</string>
|
||||
<string name="dialog_message_lost_password">आपका ब्रियर खाता आपके डिवाइस पर एन्क्रिप्ट किया गया है, बादल में नहीं, इसलिए हम आपका पासवर्ड रीसेट नहीं कर सकते। क्या आप अपना खाता हटाना चाहते हैं और फिर से शुरू करना चाहते हैं? \ N \ n सावधानी: आपकी पहचान, संपर्क और संदेश स्थायी रूप से खो जाएंगे</string>
|
||||
<string name="startup_failed_notification_title">बियर शुरू नहीं हो सका</string>
|
||||
<string name="startup_failed_notification_text">आपको ब्रियर को पुनर्स्थापित करने की आवश्यकता हो सकती है</string>
|
||||
<string name="startup_failed_activity_title">ब्रियर स्टार्टअप विफलता</string>
|
||||
<string name="startup_failed_service_error">ब्रियर एक आवश्यक प्लगइन प्रारंभ करने में असमर्थ था बरिअर को पुनः स्थापित करना आमतौर पर इस समस्या को हल करता है हालांकि, कृपया ध्यान दें कि बियर आपके डेटा को स्टोर करने के लिए केंद्रीय सर्वर का उपयोग नहीं कर रहा है, इसके बाद आप अपने खाता और उसके साथ जुड़े सभी डेटा खो देंगे।</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">यह Briar का एक परीक्षण संस्करण है आपका खाता %dदिनों में समाप्त हो जाएगा और नवीनीकरण नहीं किया जा सकता है</item>
|
||||
<item quantity="other">यह Briar का एक परीक्षण संस्करण है आपका खाता %dदिनों में समाप्त हो जाएगा और नवीनीकरण नहीं किया जा सकता है</item>
|
||||
</plurals>
|
||||
<string name="expiry_update">परीक्षण समाप्ति तिथि बढ़ा दी गई है। आपका खाता अब %d दिनों में समाप्त हो जाएगा</string>
|
||||
<string name="expiry_date_reached">यह सॉफ्टवेयर समाप्त हो गया है। \n परीक्षण के लिए धन्यवाद!</string>
|
||||
<!--Navigation Drawer-->
|
||||
<string name="nav_drawer_open_description">नेविगेशन ड्रॉवर खोलें</string>
|
||||
@@ -64,7 +81,8 @@
|
||||
<string name="delete">हटाना</string>
|
||||
<string name="accept">स्वीकारें</string>
|
||||
<string name="decline">पतन</string>
|
||||
<string name="online">ऑनलाइन</string>
|
||||
<string name="options">विकल्प</string>
|
||||
<string name="online">ऑनलाइन</string>
|
||||
<string name="offline">ऑफलाइन</string>
|
||||
<string name="send">भेजना</string>
|
||||
<string name="allow">अनुमति दें</string>
|
||||
@@ -73,6 +91,7 @@
|
||||
<string name="ellipsis">…</string>
|
||||
<string name="text_too_long">प्रवेश किया हुआ पाठ बहुत लंबा है</string>
|
||||
<string name="show_onboarding">सहायता संवाद दिखाएं</string>
|
||||
<string name="fix">ठीक कर</string>
|
||||
<string name="help">सहायता</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">ऐसा लगता है कि आप यहां नए हैं और अभी तक कोई संपर्क नहीं है। \ N \ n शीर्ष पर + आइकन टैप करें और कुछ मित्रों को अपनी सूची में जोड़ने के लिए निर्देशों का पालन करें। \ N \ n कृपया याद रखें: आप केवल नए संपर्कों को आमने-सामने जोड़ सकते हैं किसी भी व्यक्ति को भविष्य में आपके प्रतिरूपण या पढ़ने से रोकने के लिए -फाइल</string>
|
||||
@@ -95,8 +114,10 @@
|
||||
<string name="contact_already_exists">संपर्क%s पहले से मौजूद है</string>
|
||||
<string name="contact_exchange_failed">संपर्क विनिमय विफल</string>
|
||||
<string name="qr_code_invalid">QR कोड अमान्य है</string>
|
||||
<string name="camera_error">कैमरा त्रुटि</string>
|
||||
<string name="connecting_to_device">उपकरण \ u2026 से कनेक्ट हो रहा है</string>
|
||||
<string name="authenticating_with_device">डिवाइस के साथ प्रमाणीकरण \ u2026</string>
|
||||
<string name="connection_aborted_local">कनेक्शन निरस्त कर दिया गया! इसका मतलब यह हो सकता है कि कोई आपके कनेक्शन में हस्तक्षेप करने का प्रयास कर रहा है</string>
|
||||
<string name="connection_aborted_remote">आपके संपर्क से कनेक्शन निरस्त! इसका मतलब यह हो सकता है कि कोई आपके कनेक्शन में हस्तक्षेप करने का प्रयास कर रहा है</string>
|
||||
<!--Introductions-->
|
||||
<string name="introduction_onboarding_title">अपने संपर्कों का परिचय दें</string>
|
||||
@@ -317,6 +338,7 @@
|
||||
<string name="notify_sound_setting_default">बकाया घंटी</string>
|
||||
<string name="notify_sound_setting_disabled">कोई नहीं</string>
|
||||
<string name="choose_ringtone_title">रिंगटोन चुनें</string>
|
||||
<string name="cannot_load_ringtone">रिंगटोन लोड नहीं कर सकता</string>
|
||||
<!--Settings Feedback-->
|
||||
<string name="feedback_settings_title">प्रतिक्रिया</string>
|
||||
<string name="send_feedback">प्रतिक्रिया भेजें</string>
|
||||
@@ -345,6 +367,11 @@
|
||||
<string name="progress_title_logout">ब्रियर से साइन आउट हो रहा है ...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">स्क्रीन ओवरले का पता लगाया</string>
|
||||
<string name="screen_filter_body">एक और ऐप ब्रियर के शीर्ष पर है। आपकी सुरक्षा की सुरक्षा के लिए, ब्रियर किसी अन्य ऐप को शीर्ष पर खींचने पर स्पर्श का जवाब नहीं देगा। \ N \ n बरिआर का उपयोग करते समय निम्नलिखित ऐप्स बंद कर दें: \ n \ n%1$s</string>
|
||||
<string name="screen_filter_body">एक और ऐप ब्रियर के शीर्ष पर है। आपकी सुरक्षा की रक्षा के लिए, ब्रियर स्पर्श करने का जवाब नहीं देगा, जब किसी अन्य ऐप को शीर्ष पर आ रही है। \ N \ n निम्न एप्लिकेशन शीर्ष पर ड्राइंग कर सकते हैं: \ n \ n%1$s</string>
|
||||
<string name="screen_filter_allow">इन ऐप्स को शीर्ष पर आकर्षित करने की अनुमति दें</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">कैमरा अनुमति</string>
|
||||
<string name="permission_camera_request_body">QR कोड को स्कैन करने के लिए, Briar को कैमरे तक पहुंच की आवश्यकता है।</string>
|
||||
<string name="permission_camera_denied_body">आपने कैमरे तक पहुंच से वंचित किया है, लेकिन संपर्क जोड़ने के लिए कैमरे का उपयोग करने की आवश्यकता है। \ N \ n कृपया पहुंच प्रदान करने पर विचार करें।</string>
|
||||
<string name="permission_camera_denied_toast">कैमरा अनुमति नहीं दी गई थी</string>
|
||||
</resources>
|
||||
|
||||
@@ -31,9 +31,11 @@
|
||||
<string name="dialog_title_lost_password">Password persa</string>
|
||||
<string name="dialog_message_lost_password">Il tuo account Briar si trova cifrato sul tuo dispositivo e non nel cloud, quindi non possiamo resettarti la tua password. Vorresti cancellare il tuo account e partire di nuovo?\n\nAttenzione: Le tue identità, contatti e messaggi verranno persi permanentemente.</string>
|
||||
<string name="startup_failed_notification_title">Briar non è riuscito a partire</string>
|
||||
<string name="startup_failed_notification_text">Potresti dover reinstallare Briar</string>
|
||||
<string name="startup_failed_notification_text">Tocca per maggiori informazioni.</string>
|
||||
<string name="startup_failed_activity_title">Fallimento Avvio Briar</string>
|
||||
<string name="startup_failed_db_error">Per qualche motivo il tuo database di Briar è danneggiato in modo irreversibile. Il tuo account, i tuoi dati e tutti i contatti sono persi. Sfortunatamente devi reinstallare Briar e creare un nuovo account.</string>
|
||||
<string name="startup_failed_db_error">Per qualche motivo, il tuo database di Briar è danneggiato in modo irreparabile. Il tuo account, i tuoi dati e tutti i tuoi contatti sono perduti. Sfortunatamente devi reinstallare Briar e registrare un nuovo account scegliendo \'Ho dimenticato la password\'.</string>
|
||||
<string name="startup_failed_data_too_old_error">Il tuo account è stato creato con una vecchia versione dell\'app e non può essere aperto in questa versione. Devi reinstallare la vecchia versione oppure eliminare l\'account vecchio scegliendo \'Ho dimenticato la password\'.</string>
|
||||
<string name="startup_failed_data_too_new_error">Questa versione dell\'app è troppo vecchia. Aggiornala alla versione più recente e riprova.</string>
|
||||
<string name="startup_failed_service_error">Briar non è stato in grado di caricare un plugin richiesto. Reinstallare Briar di solito sistema questo problema. Però ricorda che perderai il tuo account e tutti i dati ad esso associati poichè Briar non usa server centralizzati per mantenere i tuoi dati.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Questa è una versione di prova di Briar. Il tuo account scadrà fra %d giorno e non può essere rinnovato.</item>
|
||||
@@ -83,7 +85,8 @@
|
||||
<string name="delete">Cancella</string>
|
||||
<string name="accept">Accetta</string>
|
||||
<string name="decline">Declina</string>
|
||||
<string name="online">Connesso</string>
|
||||
<string name="options">Opzioni</string>
|
||||
<string name="online">Connesso</string>
|
||||
<string name="offline">Disconnesso</string>
|
||||
<string name="send">Invia</string>
|
||||
<string name="allow">Abilita</string>
|
||||
@@ -94,6 +97,7 @@
|
||||
<string name="show_onboarding">Mostra l\'aiuto</string>
|
||||
<string name="fix">Correggi</string>
|
||||
<string name="help">Aiuto</string>
|
||||
<string name="sorry">Scusa</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">Sembra che tu sia nuovo qui e non hai ancora contatti.\n\nPremi l\'icona in alto e segui le istruzioni per aggiungere qualche amico alla tua lista.\n\nPer favore ricorda: puoi solo aggiungere nuovi contatti faccia-a-faccia per evitare che altri ti impersonino o leggano i tuoi messaggi in futuro.</string>
|
||||
<string name="date_no_private_messages">Nessun messaggio.</string>
|
||||
@@ -115,6 +119,7 @@
|
||||
<string name="contact_already_exists">Il contatto %s esiste già</string>
|
||||
<string name="contact_exchange_failed">Scambio di contatto fallito</string>
|
||||
<string name="qr_code_invalid">Il codice QR non è valido</string>
|
||||
<string name="qr_code_unsupported">Il codice QR che state tentando di scansionare appartiene ad una vecchia versione di %s che non è più supportata.\n\nAssicuratevi entrambi di utilizzare la versione più recente e poi riprovare.</string>
|
||||
<string name="camera_error">Errore fotocamera</string>
|
||||
<string name="connecting_to_device">Connessione al dispositivo\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticazione con il dispositivo\u2026</string>
|
||||
@@ -368,7 +373,8 @@
|
||||
<string name="progress_title_logout">Uscire da Briar ...</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">È stata rilevata un\'overlay sullo schermo</string>
|
||||
<string name="screen_filter_body">Un\'altra app sta disegnando sopra a Briar. Per proteggere la tua sicurezza, Briar non risponderà ai tocchi quando un\'altra app vi starà disegnando sopra.\n\nProva a spegnere le seguenti app mentre usi Briar:\n\n%1$s</string>
|
||||
<string name="screen_filter_body">Un\'altra app si sta sovrapponendo a Briar. Per proteggere la tua sicurezza, Briar non risponderà ai tocchi quando un\'app si sovrappone.\n\nLe seguenti app potrebbero sovrapporsi:\n\n%1$s</string>
|
||||
<string name="screen_filter_allow">Permetti a queste app di sovrapporsi</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Autorizzazione fotocamera</string>
|
||||
<string name="permission_camera_request_body">Per scansionare il codice QR, Briar deve accedere alla fotocamera.</string>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<string name="dialog_title_lost_password">パスワードを紛失</string>
|
||||
<string name="dialog_message_lost_password">あなたのBriarアカウントはクラウド上ではなく、暗号化さた上であなたのデバイスに保存さています。したがってBriarはパスワードをリセットできません。アカウントを削除しはじめからやりなおしますか?注意!あなたのID、連絡先、メッセージは永久に削除されます。</string>
|
||||
<string name="startup_failed_notification_title">Briarを起動できません。</string>
|
||||
<string name="startup_failed_notification_text">Briarを再インストールする必要があります。</string>
|
||||
<string name="startup_failed_activity_title">起動に失敗</string>
|
||||
<string name="startup_failed_service_error">プラグインの起動に失敗しました。Briarを再インストールすることで通常は直ります。Briarは中央サーバにデータを保存していないため、アカウントとそれに関連する情報は全て失われることに注意してください。</string>
|
||||
<string name="expiry_date_reached">このソフトの有効期限が切れました。テストに参加してくださりありがとうございます!</string>
|
||||
@@ -59,7 +58,8 @@
|
||||
<string name="delete">削除</string>
|
||||
<string name="accept">承認</string>
|
||||
<string name="decline">却下</string>
|
||||
<string name="online">オプション</string>
|
||||
<string name="options">オプション</string>
|
||||
<string name="online">オプション</string>
|
||||
<string name="offline">オフライン</string>
|
||||
<string name="send">送信</string>
|
||||
<string name="allow">許可</string>
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
<string name="dialog_title_lost_password">Tapt passord</string>
|
||||
<string name="dialog_message_lost_password">Din Briar-konto er lagret kryptert på din enhet, ikke i skyen, så du kan ikke tilbakestille passordet ditt. Ønsker du å slette kontoen og starte igjen?\n\nMerk: Identitetene dine, kontaktene og meldingene vil gå tapt for alltid.</string>
|
||||
<string name="startup_failed_notification_title">Briar kunne ikke starte</string>
|
||||
<string name="startup_failed_notification_text">Det kan hende du må reinstallere Briar.</string>
|
||||
<string name="startup_failed_activity_title">Oppstartsfeil med Briar</string>
|
||||
<string name="startup_failed_db_error">Hvis din Briar-database av noen grunn skulle bli skadet uten mulighet for reparasjon, vil din konto, din data, og alle dine kontakter gå tapt. Uheldigvis, må du da reinstallere Briar og sett opp en ny konto.</string>
|
||||
<string name="startup_failed_service_error">Briar kunne ikke starte det nødvendige programtillegget. Reinstallasjon av Briar fikser vanligvis dette problemet. Merk deg dog at kontoen og all data tilknyttet den vil gå tapt for godt siden Briar ikke bruker sentrale tjenere å lagre dataen din på.</string>
|
||||
<plurals name="expiry_warning">
|
||||
<item quantity="one">Dette er en test-versjon av Briar. Din konto vil utløpe om %d dag, og kan ikke fornyes.</item>
|
||||
@@ -83,7 +81,8 @@
|
||||
<string name="delete">Slett</string>
|
||||
<string name="accept">Godta</string>
|
||||
<string name="decline">Avslå</string>
|
||||
<string name="online">Pålogget</string>
|
||||
<string name="options">Valg</string>
|
||||
<string name="online">Pålogget</string>
|
||||
<string name="offline">Frakoblet</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="allow">Tillat</string>
|
||||
@@ -368,7 +367,6 @@
|
||||
<string name="progress_title_logout">Logger ut av Briar…</string>
|
||||
<!--Screen Filters & Tapjacking-->
|
||||
<string name="screen_filter_title">Skjermoverlag oppdaget</string>
|
||||
<string name="screen_filter_body">Et annet program tegner på toppen av Briar. For å beskytte din sikkerhet, vil Briar ikke reagere på trykk når et annet program tegner over det.\n\nPrøv å skru av følgende programmer når du bruker Briar.\n\n%1$s</string>
|
||||
<!--Permission Requests-->
|
||||
<string name="permission_camera_title">Kameratilgang</string>
|
||||
<string name="permission_camera_request_body">For å skanne QR-koden, trenger Briar tilgang til kameraet.</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user