Compare commits

..

47 Commits

Author SHA1 Message Date
Torsten Grote
af469d7f27 [android] add real Tor icon 2020-01-29 11:27:50 -03:00
Torsten Grote
0192b64dd3 [android] Scroll down when nav drawer chevron is pressed 2020-01-29 11:26:21 -03:00
Torsten Grote
aeb148c600 [android] remove unused strings 2020-01-29 11:26:20 -03:00
Torsten Grote
ac288b01d1 [android] make transport plugin toggles functional 2020-01-24 14:19:25 -03:00
Torsten Grote
197b40647c [android] Add transport plugin toggles to NavDrawer 2020-01-24 11:12:34 -03:00
akwizgran
9fc4bc838d Merge branch 'plugin-manager-plugin-state' into 'plugin-toggles'
Add method for enabling/disabling plugins to PluginManager

See merge request briar/briar!1214
2020-01-24 12:56:28 +00:00
Torsten Grote
89c227e2da [bramble] Add method for enabling/disabling plugins to PluginManager 2020-01-24 09:18:11 -03:00
Torsten Grote
4269bd4b74 Merge branch 'plugin-toggle-settings' into 'plugin-toggles'
Add toggle settings for transport plugins

See merge request briar/briar!1211
2020-01-23 15:09:19 +00:00
akwizgran
cc7a19402e Remove another redundant call to pluginStateChanged(). 2020-01-23 13:24:37 +00:00
akwizgran
dc64c4148d Enable LAN plugin before showing QR code. 2020-01-23 13:18:38 +00:00
akwizgran
e647ae7bb4 Remove redundant call to pluginStateChanged(). 2020-01-23 12:51:41 +00:00
Torsten Grote
42776f56d0 Merge branch 'skip-fetching-feeds-if-tor-is-not-active' into 'plugin-toggles'
Skip fetching RSS feeds if Tor is not active

See merge request briar/briar!1212
2020-01-20 18:23:30 +00:00
Torsten Grote
559776b0f5 Merge branch 'amber-transport-icons' into 'plugin-toggles'
Use amber icon when enabling transports

See merge request briar/briar!1210
2020-01-20 18:22:10 +00:00
Torsten Grote
642485d7bd Merge branch '581-plugin-states' into 'plugin-toggles'
Better methods for querying plugin states

See merge request briar/briar!1209
2020-01-20 18:19:10 +00:00
akwizgran
070be8621d Use XML to specify dependencies between settings. 2020-01-20 16:41:39 +00:00
akwizgran
2e42fb3c44 Only update bridge and padding settings if network is enabled. 2020-01-20 16:20:36 +00:00
akwizgran
3f0f3746d7 Skip fetching RSS feeds if Tor is not active. 2020-01-20 15:40:24 +00:00
akwizgran
c2dd61b006 Clean up logic for enabling/disabling settings. 2020-01-20 15:12:38 +00:00
akwizgran
5e37b3da22 Don't remove old settings yet.
This avoids an unlikely race condition at startup, where the user opens
the settings screen before the Tor plugin has migrated the settings.
2020-01-20 15:12:38 +00:00
akwizgran
8622f663f6 Enable LAN plugin in unit test. 2020-01-20 15:12:38 +00:00
akwizgran
628b69d4eb Enable BT plugin before showing QR code. 2020-01-20 15:12:38 +00:00
akwizgran
b53319a7b0 Small code cleanups in key agreement UI. 2020-01-20 15:12:38 +00:00
akwizgran
98d4a48855 Make REASON_USER into a generic reason code. 2020-01-20 15:12:38 +00:00
akwizgran
9184bf6afc Add toggle setting for LAN plugin. 2020-01-20 15:12:37 +00:00
akwizgran
4f2f145ab6 Update semantics of Bluetooth setting.
The setting now enables/disables the plugin, not just contact
connections. The key agreement UI will need to be updated to change the
setting if the user agrees to use Bluetooth.
2020-01-20 15:12:37 +00:00
akwizgran
c945b3f611 Convert Bluetooth setting to a switch. 2020-01-20 15:12:37 +00:00
akwizgran
0940b8d5b9 Add toggle setting for Tor plugin. 2020-01-20 15:12:37 +00:00
akwizgran
dac21cb3a0 Remove redundant casts. 2020-01-20 15:00:44 +00:00
akwizgran
9bfbb4d02d Notify callback of state changes while holding lock. 2020-01-20 15:00:16 +00:00
akwizgran
2689e5f361 Update javadocs for lock-safe methods. 2020-01-20 14:48:33 +00:00
akwizgran
d7d8af7e32 Remove redundant logging. 2020-01-20 14:03:12 +00:00
akwizgran
57a47463d6 Use amber icon when enabling transports. 2020-01-17 12:39:52 +00:00
akwizgran
8db481a17a Remove debug logging. 2020-01-17 12:38:43 +00:00
akwizgran
2b9ffc7fbe Close server socket when BT is disabled. 2020-01-17 12:38:03 +00:00
akwizgran
0a5f93edf9 Remove unnecessary inner class, state checks. 2020-01-16 13:08:16 +00:00
akwizgran
0aada89625 Reset backoff before notifying of new state.
The new state may cause the poller to poll the
plugin. Let's avoid a race between updating and
querying the polling interval.
2020-01-16 13:01:41 +00:00
akwizgran
549cf4e2be Move to enabling state earlier in Tor startup. 2020-01-16 12:38:34 +00:00
akwizgran
c6981fb243 Add TransportStateEvent, rename existing events. 2020-01-16 11:54:28 +00:00
akwizgran
10791aea49 Ensure server socket is closed. 2020-01-16 11:35:32 +00:00
akwizgran
1c98d8f12a Add method for getting reason why plugin is disabled. 2020-01-16 11:05:36 +00:00
akwizgran
6bce4b76d2 Fix test expectations. 2020-01-16 11:05:02 +00:00
akwizgran
c7565cb93e Rename available/unavailable states. 2020-01-16 09:58:12 +00:00
akwizgran
32288c376b Update tests. 2020-01-16 09:47:49 +00:00
akwizgran
1e7a1670dd If adapter is disabled, forget that we enabled it. 2020-01-15 17:51:18 +00:00
akwizgran
850ad18a36 Check that server sockets are closed as expected. 2020-01-15 17:51:18 +00:00
akwizgran
5d6ed1a724 Provide more information about plugin states. 2020-01-15 17:51:18 +00:00
akwizgran
ded1792213 Avoid NPE if there's no TelephonyManager. 2020-01-14 09:51:03 +00:00
73 changed files with 868 additions and 1710 deletions

View File

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 10207 versionCode 10205
versionName "1.2.7" versionName "1.2.5"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -21,7 +20,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir; import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir; import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
@@ -32,12 +30,6 @@ class AndroidAccountManager extends AccountManagerImpl
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidAccountManager.class.getName()); Logger.getLogger(AndroidAccountManager.class.getName());
/**
* Directories that shouldn't be deleted when deleting the user's account.
*/
private static final List<String> PROTECTED_DIR_NAMES =
asList("cache", "code_cache", "lib", "shared_prefs");
protected final Context appContext; protected final Context appContext;
private final SharedPreferences prefs; private final SharedPreferences prefs;
@@ -89,7 +81,7 @@ class AndroidAccountManager extends AccountManagerImpl
if (!prefs.edit().clear().commit()) if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences"); LOG.warning("Could not clear shared preferences");
} }
// Delete files, except protected directories // Delete files, except lib and shared_prefs directories
Set<File> files = new HashSet<>(); Set<File> files = new HashSet<>();
File dataDir = getDataDir(); File dataDir = getDataDir();
@Nullable @Nullable
@@ -98,12 +90,14 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not list files in app data dir"); LOG.warning("Could not list files in app data dir");
} else { } else {
for (File file : fileArray) { for (File file : fileArray) {
if (!PROTECTED_DIR_NAMES.contains(file.getName())) { String name = file.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
files.add(file); files.add(file);
} }
} }
} }
files.add(appContext.getFilesDir()); files.add(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir()); addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) { if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) { for (File file : appContext.getExternalCacheDirs()) {
@@ -115,16 +109,12 @@ class AndroidAccountManager extends AccountManagerImpl
addIfNotNull(files, file); addIfNotNull(files, file);
} }
} }
// Clear the cache directory but don't delete it
File cacheDir = appContext.getCacheDir();
File[] children = cacheDir.listFiles();
if (children != null) files.addAll(asList(children));
for (File file : files) { for (File file : files) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting " + file.getAbsolutePath());
}
deleteFileOrDir(file); deleteFileOrDir(file);
} }
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdirs())
LOG.warning("Could not recreate cache dir");
} }
private File getDataDir() { private File getDataDir() {

View File

@@ -32,7 +32,6 @@ import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
@@ -77,9 +76,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(ACTION_SCREEN_ON); filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF); filter.addAction(ACTION_SCREEN_OFF);
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
} }
@Override @Override
@@ -137,8 +136,7 @@ class AndroidNetworkManager implements NetworkManager, Service {
} }
private boolean isApEvent(@Nullable String action) { private boolean isApEvent(@Nullable String action) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(action) || return WIFI_AP_STATE_CHANGED_ACTION.equals(action);
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action);
} }
} }
} }

View File

@@ -8,34 +8,24 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.os.Parcelable;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -57,25 +47,12 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF; import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND; import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.ACTION_UUID;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.bluetooth.BluetoothDevice.EXTRA_UUID;
import static android.os.Build.VERSION.SDK_INT;
import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.shuffle;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
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.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -84,9 +61,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidBluetoothPlugin.class.getName()); getLogger(AndroidBluetoothPlugin.class.getName());
private static final int MIN_DEVICE_DISCOVERY_MS = 2_000; private static final int MAX_DISCOVERY_MS = 10_000;
private static final int MAX_DEVICE_DISCOVERY_MS = 30_000;
private static final int MAX_SERVICE_DISCOVERY_MS = 15_000;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
@@ -160,31 +135,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override @Override
void disableAdapterIfEnabledByUs() { void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) { if (isAdapterEnabled() && wasEnabledByUs) {
cancelDiscoverability();
if (adapter.disable()) LOG.info("Disabling Bluetooth"); if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth"); else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false; wasEnabledByUs = false;
} }
} }
private void cancelDiscoverability() {
if (adapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
try {
Method setDiscoverableTimeout = BluetoothAdapter.class
.getDeclaredMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, 1);
LOG.info("Cancelled discoverability");
} catch (NoSuchMethodException e) {
logException(LOG, WARNING, e);
} catch (IllegalAccessException e) {
logException(LOG, WARNING, e);
} catch (InvocationTargetException e) {
logException(LOG, WARNING, e);
}
}
}
@Override @Override
void setEnabledByUs() { void setEnabledByUs() {
wasEnabledByUs = true; wasEnabledByUs = true;
@@ -250,8 +206,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Nullable @Nullable
DuplexTransportConnection discoverAndConnect(String uuid) { DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null; if (adapter == null) return null;
for (BluetoothDevice d : discoverDevices()) { for (String address : discoverDevices()) {
String address = d.getAddress();
try { try {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address)); LOG.info("Connecting to " + scrubMacAddress(address));
@@ -267,184 +222,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return null; return null;
} }
@Override private Collection<String> discoverDevices() {
public boolean supportsDiscovery() { List<String> addresses = new ArrayList<>();
return true;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
// Discover all nearby devices
List<BluetoothDevice> devices = discoverDevices();
if (devices.isEmpty()) {
LOG.info("No devices discovered");
return;
}
List<Pair<TransportProperties, DiscoveryHandler>> discovered =
new ArrayList<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byUuid =
new HashMap<>();
Map<String, Pair<TransportProperties, DiscoveryHandler>> byAddress =
new HashMap<>();
for (Pair<TransportProperties, DiscoveryHandler> pair : properties) {
TransportProperties p = pair.getFirst();
String uuid = p.get(PROP_UUID);
if (!isNullOrEmpty(uuid)) {
byUuid.put(uuid, pair);
String address = p.get(PROP_ADDRESS);
if (!isNullOrEmpty(address)) byAddress.put(address, pair);
}
}
List<BluetoothDevice> unknown = new ArrayList<>(devices);
for (BluetoothDevice d : devices) {
Pair<TransportProperties, DiscoveryHandler> pair =
byAddress.remove(d.getAddress());
if (pair == null) {
// Try cached UUIDs
for (String uuid : getUuids(d)) {
pair = byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by cached UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
unknown.remove(d);
break;
}
}
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched " + scrubMacAddress(d.getAddress())
+ " by address");
}
discovered.add(pair);
unknown.remove(d);
}
}
if (unknown.isEmpty()) {
LOG.info("All discovered devices are known, not fetching UUIDs");
return;
}
// Fetch up-to-date UUIDs
if (LOG.isLoggable(INFO))
LOG.info("Fetching UUIDs for " + unknown.size() + " devices");
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>(); BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(intents); DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_UUID);
appContext.registerReceiver(receiver, filter);
try {
List<BluetoothDevice> pending = new ArrayList<>();
for (BluetoothDevice d : unknown) {
if (d.fetchUuidsWithSdp()) {
if (LOG.isLoggable(INFO)) {
LOG.info("Fetching UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
pending.add(d);
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Failed to fetch UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
}
}
long now = clock.currentTimeMillis();
long end = now + MAX_SERVICE_DISCOVERY_MS;
while (now < end && !pending.isEmpty()) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
if (LOG.isLoggable(INFO)) {
LOG.info("Fetched UUIDs for "
+ scrubMacAddress(d.getAddress()));
}
Set<String> uuids = getUuids(d);
Parcelable[] extra = i.getParcelableArrayExtra(EXTRA_UUID);
if (extra != null) {
for (Parcelable p : extra) {
uuids.addAll(getUuidStrings((ParcelUuid) p));
}
}
for (String uuid : uuids) {
Pair<TransportProperties, DiscoveryHandler> pair =
byUuid.remove(uuid);
if (pair != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Matched "
+ scrubMacAddress(d.getAddress())
+ " by fetched UUID");
}
TransportProperties p =
new TransportProperties(pair.getFirst());
p.put(PROP_ADDRESS, d.getAddress());
discovered.add(new Pair<>(p, pair.getSecond()));
break;
}
}
pending.remove(d);
now = clock.currentTimeMillis();
}
if (LOG.isLoggable(INFO)) {
if (pending.isEmpty()) {
LOG.info("Finished fetching UUIDs");
} else {
LOG.info("Failed to fetch UUIDs for " + pending.size()
+ " devices");
}
}
} catch (InterruptedException e) {
LOG.info("Interrupted while fetching UUIDs");
Thread.currentThread().interrupt();
} finally {
appContext.unregisterReceiver(receiver);
}
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered " + discovered.size() + " contacts");
}
for (Pair<TransportProperties, DiscoveryHandler> pair : discovered) {
pair.getSecond().handleDevice(pair.getFirst());
}
}
private Set<String> getUuids(BluetoothDevice d) {
Set<String> strings = new TreeSet<>();
ParcelUuid[] uuids = d.getUuids();
if (uuids == null) return strings;
for (ParcelUuid u : uuids) strings.addAll(getUuidStrings(u));
return strings;
}
// Workaround for https://code.google.com/p/android/issues/detail?id=197341
private List<String> getUuidStrings(ParcelUuid u) {
UUID forwards = u.getUuid();
ByteBuffer buf = ByteBuffer.allocate(16);
buf.putLong(forwards.getLeastSignificantBits());
buf.putLong(forwards.getMostSignificantBits());
buf.rewind();
buf.order(LITTLE_ENDIAN);
UUID backwards = new UUID(buf.getLong(), buf.getLong());
return asList(forwards.toString(), backwards.toString());
}
private List<BluetoothDevice> discoverDevices() {
if (adapter.isDiscovering()) {
LOG.info("Already discovering");
return emptyList();
}
List<BluetoothDevice> devices = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(intents);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCOVERY_STARTED); filter.addAction(ACTION_DISCOVERY_STARTED);
filter.addAction(ACTION_DISCOVERY_FINISHED); filter.addAction(ACTION_DISCOVERY_FINISHED);
@@ -452,9 +233,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.registerReceiver(receiver, filter); appContext.registerReceiver(receiver, filter);
try { try {
if (adapter.startDiscovery()) { if (adapter.startDiscovery()) {
long start = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long end = start + MAX_DEVICE_DISCOVERY_MS; long end = now + MAX_DISCOVERY_MS;
long now = start;
while (now < end) { while (now < end) {
Intent i = intents.poll(end - now, MILLISECONDS); Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break; if (i == null) break;
@@ -463,27 +243,14 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
LOG.info("Discovery started"); LOG.info("Discovery started");
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) { } else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
LOG.info("Discovery finished"); LOG.info("Discovery finished");
now = clock.currentTimeMillis(); break;
if (now - start < MIN_DEVICE_DISCOVERY_MS) {
LOG.info("Discovery finished quickly, retrying");
if (!adapter.startDiscovery()) {
LOG.info("Could not restart discovery");
break;
}
} else {
break;
}
} else if (ACTION_FOUND.equals(action)) { } else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = requireNonNull( BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
i.getParcelableExtra(EXTRA_DEVICE)); String address = d.getAddress();
// Ignore Bluetooth LE devices if (LOG.isLoggable(INFO))
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) { LOG.info("Discovered " + scrubMacAddress(address));
if (LOG.isLoggable(INFO)) { if (!addresses.contains(address))
LOG.info("Discovered " addresses.add(address);
+ scrubMacAddress(d.getAddress()));
}
if (!devices.contains(d)) devices.add(d);
}
} }
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
} }
@@ -498,9 +265,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
adapter.cancelDiscovery(); adapter.cancelDiscovery();
appContext.unregisterReceiver(receiver); appContext.unregisterReceiver(receiver);
} }
// Shuffle the devices so we don't always try the same one first // Shuffle the addresses so we don't always try the same one first
shuffle(devices); Collections.shuffle(addresses);
return devices; return addresses;
} }
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@@ -521,11 +288,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
} }
} }
private static class QueueingReceiver extends BroadcastReceiver { private static class DiscoveryReceiver extends BroadcastReceiver {
private final BlockingQueue<Intent> intents; private final BlockingQueue<Intent> intents;
private QueueingReceiver(BlockingQueue<Intent> intents) { private DiscoveryReceiver(BlockingQueue<Intent> intents) {
this.intents = intents; this.intents = intents;
} }

View File

@@ -19,7 +19,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -44,6 +44,19 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName()); getLogger(AndroidLanTcpPlugin.class.getName());
private static final byte[] WIFI_AP_ADDRESS_BYTES =
{(byte) 192, (byte) 168, 43, 1};
private static final InetAddress WIFI_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
private final ConnectivityManager connectivityManager; private final ConnectivityManager connectivityManager;
@Nullable @Nullable
@@ -53,9 +66,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext, AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
Backoff backoff, PluginCallback callback, int maxLatency, Backoff backoff, PluginCallback callback, int maxLatency,
int maxIdleTime, int connectionTimeout) { int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
connectionTimeout);
// Don't execute more than one connection status check at a time // Don't execute more than one connection status check at a time
connectionStatusExecutor = connectionStatusExecutor =
new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1); new PoliteExecutor("AndroidLanTcpPlugin", ioExecutor, 1);
@@ -71,7 +83,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@Override @Override
public void start() { public void start() {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
Settings settings = callback.getSettings(); Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false)); state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
updateConnectionStatus(); updateConnectionStatus();
@@ -83,19 +94,16 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
} }
@Override @Override
protected List<InetAddress> getUsableLocalInetAddresses() { protected Collection<InetAddress> getLocalIpAddresses() {
// If the device doesn't have wifi, don't open any sockets // If the device doesn't have wifi, don't open any sockets
if (wifiManager == null) return emptyList(); if (wifiManager == null) return emptyList();
// If we're connected to a wifi network, return its address // If we're connected to a wifi network, use that network
WifiInfo info = wifiManager.getConnectionInfo(); WifiInfo info = wifiManager.getConnectionInfo();
if (info != null && info.getIpAddress() != 0) { if (info != null && info.getIpAddress() != 0)
return singletonList(intToInetAddress(info.getIpAddress())); return singletonList(intToInetAddress(info.getIpAddress()));
}
// If we're running an access point, return its address // If we're running an access point, return its address
for (InetAddress addr : getLocalInetAddresses()) { if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
if (addr.equals(WIFI_AP_ADDRESS)) return singletonList(addr); return singletonList(WIFI_AP_ADDRESS);
if (addr.equals(WIFI_DIRECT_AP_ADDRESS)) return singletonList(addr);
}
// No suitable addresses // No suitable addresses
return emptyList(); return emptyList();
} }
@@ -137,9 +145,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
connectionStatusExecutor.execute(() -> { connectionStatusExecutor.execute(() -> {
State s = getState(); State s = getState();
if (s != ACTIVE && s != INACTIVE) return; if (s != ACTIVE && s != INACTIVE) return;
List<InetAddress> addrs = getLocalInetAddresses(); Collection<InetAddress> addrs = getLocalIpAddresses();
if (addrs.contains(WIFI_AP_ADDRESS) if (addrs.contains(WIFI_AP_ADDRESS)) {
|| addrs.contains(WIFI_DIRECT_AP_ADDRESS)) {
LOG.info("Providing wifi hotspot"); LOG.info("Providing wifi hotspot");
// There's no corresponding Network object and thus no way // There's no corresponding Network object and thus no way
// to get a suitable socket factory, so we won't be able to // to get a suitable socket factory, so we won't be able to

View File

@@ -21,11 +21,10 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -56,8 +55,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor, AndroidLanTcpPlugin plugin = new AndroidLanTcpPlugin(ioExecutor,
appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME, appContext, backoff, callback, MAX_LATENCY, MAX_IDLE_TIME);
CONNECTION_TIMEOUT);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -72,9 +72,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
@Test @Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles() public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception { throws Exception {
// Directories 'code_cache', 'lib' and 'shared_prefs' should be spared // Directories 'lib' and 'shared_prefs' should be spared
File codeCacheDir = new File(testDir, "code_cache");
File codeCacheFile = new File(codeCacheDir, "file");
File libDir = new File(testDir, "lib"); File libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file"); File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs"); File sharedPrefsDir = new File(testDir, "shared_prefs");
@@ -113,8 +111,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(dbDir.mkdirs()); assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs()); assertTrue(keyDir.mkdirs());
assertTrue(codeCacheDir.mkdirs());
assertTrue(codeCacheFile.createNewFile());
assertTrue(libDir.mkdirs()); assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile()); assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs()); assertTrue(sharedPrefsDir.mkdirs());
@@ -130,8 +126,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(dbDir.exists()); assertFalse(dbDir.exists());
assertFalse(keyDir.exists()); assertFalse(keyDir.exists());
assertTrue(codeCacheDir.exists());
assertTrue(codeCacheFile.exists());
assertTrue(libDir.exists()); assertTrue(libDir.exists());
assertTrue(libFile.exists()); assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists()); assertTrue(sharedPrefsDir.exists());

View File

@@ -8,4 +8,7 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address"; String PROP_ADDRESS = "address";
String PROP_UUID = "uuid"; String PROP_UUID = "uuid";
// Reason code returned by Plugin#getReasonDisabled()
int REASON_NO_BT_ADAPTER = 2;
} }

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.api.plugin;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties;
/**
* An interface for handling peers discovered by transport plugins.
*/
@NotNullByDefault
public interface DiscoveryHandler {
/**
* Handles a peer discovered by a transport plugin.
*
* @param p A set of properties describing the discovered peer.
*/
void handleDevice(TransportProperties p);
}

View File

@@ -4,10 +4,10 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan"); TransportId ID = new TransportId("org.briarproject.bramble.lan");
// Transport properties (shared with contacts) // a transport property (shared with contacts)
String PROP_IP_PORTS = "ipPorts"; String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
// A local setting // a local setting
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";
} }

View File

@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import java.util.List; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public interface Plugin { public interface Plugin {
@@ -13,13 +13,8 @@ public interface Plugin {
enum State { enum State {
/** /**
* The plugin has not finished starting or has been stopped. * The plugin has not been started, has been stopped, or is disabled by
*/ * settings.
STARTING_STOPPING,
/**
* The plugin is disabled by settings. Use {@link #getReasonsDisabled()}
* to find out which settings are responsible.
*/ */
DISABLED, DISABLED,
@@ -47,7 +42,14 @@ public interface Plugin {
String PREF_PLUGIN_ENABLE = "enable"; String PREF_PLUGIN_ENABLE = "enable";
/** /**
* Reason flag returned by {@link #getReasonsDisabled()} to indicate that * Reason code returned by {@link #getReasonDisabled()} to indicate that
* the plugin is disabled because it has not been started or has been
* stopped.
*/
int REASON_STARTING_STOPPING = 0;
/**
* Reason code returned by {@link #getReasonDisabled()} to indicate that
* the plugin has been disabled by the user. * the plugin has been disabled by the user.
*/ */
int REASON_USER = 1; int REASON_USER = 1;
@@ -83,13 +85,14 @@ public interface Plugin {
State getState(); State getState();
/** /**
* Returns a set of flags indicating why the plugin is * Returns an integer code indicating why the plugin is
* {@link State#DISABLED disabled}, or 0 if the plugin is not disabled. * {@link State#DISABLED disabled}, or -1 if the plugin is not disabled.
* <p> * <p>
* The flags used are plugin-specific, except the generic flag * The codes used are plugin-specific, except the generic codes
* {@link #REASON_USER}, which may be used by any plugin. * {@link #REASON_STARTING_STOPPING} and {@link #REASON_USER}, which may
* be used by any plugin.
*/ */
int getReasonsDisabled(); int getReasonDisabled();
/** /**
* Returns true if the plugin should be polled periodically to attempt to * Returns true if the plugin should be polled periodically to attempt to
@@ -106,5 +109,6 @@ public interface Plugin {
* Attempts to create connections using the given transport properties, * Attempts to create connections using the given transport properties,
* passing any created connections to the corresponding handlers. * passing any created connections to the corresponding handlers.
*/ */
void poll(List<Pair<TransportProperties, ConnectionHandler>> properties); void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties);
} }

View File

@@ -21,21 +21,10 @@ public interface TorConstants {
int PREF_TOR_NETWORK_AUTOMATIC = 0; int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1; int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3; int PREF_TOR_NETWORK_NEVER = 3;
/** // Reason codes returned by Plugin#getReasonDisabled()
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_BATTERY = 2; int REASON_BATTERY = 2;
int REASON_MOBILE_DATA = 3;
/** int REASON_COUNTRY_BLOCKED = 4;
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_MOBILE_DATA = 4;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_COUNTRY_BLOCKED = 8;
} }

View File

@@ -1,18 +1,14 @@
package org.briarproject.bramble.api.plugin.duplex; package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint; import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@@ -62,16 +58,4 @@ public interface DuplexPlugin extends Plugin {
@Nullable @Nullable
RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
boolean alice, ConnectionHandler incoming); boolean alice, ConnectionHandler incoming);
/**
* Returns true if the plugin supports peer discovery.
*/
boolean supportsDiscovery();
/**
* Attempts to discover peers using the given transport properties, passing
* any discovered peers to the corresponding handlers.
*/
void discoverPeers(List<Pair<TransportProperties, DiscoveryHandler>>
properties);
} }

View File

@@ -50,7 +50,7 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -277,7 +277,7 @@ class PluginManagerImpl implements PluginManager, Service {
private final TransportId id; private final TransportId id;
private final AtomicReference<State> state = private final AtomicReference<State> state =
new AtomicReference<>(STARTING_STOPPING); new AtomicReference<>(DISABLED);
private Callback(TransportId id) { private Callback(TransportId id) {
this.id = id; this.id = id;

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.ConnectionManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
@@ -33,7 +32,6 @@ import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -212,20 +210,18 @@ class PollerImpl implements Poller, EventListener {
@IoExecutor @IoExecutor
private void poll(Plugin p) { private void poll(Plugin p) {
TransportId t = p.getId(); TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling " + t); if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
try { try {
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t); transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected = Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t); connectionRegistry.getConnectedContacts(t);
List<Pair<TransportProperties, ConnectionHandler>> properties = Collection<Pair<TransportProperties, ConnectionHandler>>
new ArrayList<>(); properties = new ArrayList<>();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey(); ContactId c = e.getKey();
if (!connected.contains(c)) { if (!connected.contains(c))
ConnHandler handler = new ConnHandler(c, t); properties.add(new Pair<>(e.getValue(), new Handler(c, t)));
properties.add(new Pair<>(e.getValue(), handler));
}
} }
if (!properties.isEmpty()) p.poll(properties); if (!properties.isEmpty()) p.poll(properties);
} catch (DbException e) { } catch (DbException e) {
@@ -233,30 +229,6 @@ class PollerImpl implements Poller, EventListener {
} }
} }
@IoExecutor
private void discover(DuplexPlugin p) {
TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Discovering peers for " + t);
try {
Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t);
List<Pair<TransportProperties, DiscoveryHandler>> properties =
new ArrayList<>();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
if (!connected.contains(c)) {
DiscoHandler handler = new DiscoHandler(c, p);
properties.add(new Pair<>(e.getValue(), handler));
}
}
if (!properties.isEmpty()) p.discoverPeers(properties);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
private class ScheduledPollTask { private class ScheduledPollTask {
private final PollTask task; private final PollTask task;
@@ -296,23 +268,16 @@ class PollerImpl implements Poller, EventListener {
int delay = plugin.getPollingInterval(); int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble()); if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false); schedule(plugin, delay, false);
// FIXME: Revert poll(plugin);
if (plugin instanceof DuplexPlugin) {
DuplexPlugin d = (DuplexPlugin) plugin;
if (d.supportsDiscovery()) discover(d);
else poll(d);
} else {
poll(plugin);
}
} }
} }
private class ConnHandler implements ConnectionHandler { private class Handler implements ConnectionHandler {
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private ConnHandler(ContactId contactId, TransportId transportId) { private Handler(ContactId contactId, TransportId transportId) {
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
} }
@@ -335,27 +300,4 @@ class PollerImpl implements Poller, EventListener {
transportId, w); transportId, w);
} }
} }
private class DiscoHandler implements DiscoveryHandler {
private final ContactId contactId;
private final DuplexPlugin plugin;
private DiscoHandler(ContactId contactId, DuplexPlugin plugin) {
this.contactId = contactId;
this.plugin = plugin;
}
@Override
public void handleDevice(TransportProperties p) {
LOG.info("Discovered contact via " + plugin.getId());
ioExecutor.execute(() -> {
DuplexTransportConnection c = plugin.createConnection(p);
if (c != null) {
connectionManager.manageOutgoingConnection(contactId,
plugin.getId(), c);
}
});
}
}
} }

View File

@@ -15,7 +15,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -32,7 +31,7 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.List; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -49,11 +48,11 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TR
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.REASON_NO_BT_ADAPTER;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -160,15 +159,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
state.setStarted(enabledByUser);
try { try {
initialiseAdapter(); initialiseAdapter();
} catch (IOException e) { } catch (IOException e) {
state.setNoAdapter();
throw new PluginException(e); throw new PluginException(e);
} }
updateProperties(); updateProperties();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
state.setStarted(enabledByUser);
if (enabledByUser) { if (enabledByUser) {
if (isAdapterEnabled()) bind(); if (isAdapterEnabled()) bind();
else enableAdapter(); else enableAdapter();
@@ -252,8 +252,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
@Override @Override
public int getReasonsDisabled() { public int getReasonDisabled() {
return state.getReasonsDisabled(); return state.getReasonDisabled();
} }
@Override @Override
@@ -267,8 +267,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
@Override @Override
public void poll( public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
List<Pair<TransportProperties, ConnectionHandler>> properties) { properties) {
if (getState() != ACTIVE) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
@@ -412,17 +412,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof EnableBluetoothEvent) { if (e instanceof EnableBluetoothEvent) {
@@ -488,6 +477,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@GuardedBy("this") @GuardedBy("this")
private boolean started = false, private boolean started = false,
stopped = false, stopped = false,
noAdapter = false,
enabledByUser = false; enabledByUser = false;
@GuardedBy("this") @GuardedBy("this")
@@ -509,6 +499,11 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
return ss; return ss;
} }
synchronized void setNoAdapter() {
noAdapter = true;
callback.pluginStateChanged(getState());
}
@Nullable @Nullable
synchronized SS setEnabledByUser(boolean enabledByUser) { synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser; this.enabledByUser = enabledByUser;
@@ -537,13 +532,14 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
} }
synchronized State getState() { synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING; if (!started || stopped || !enabledByUser) return DISABLED;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE; return serverSocket == null ? INACTIVE : ACTIVE;
} }
synchronized int getReasonsDisabled() { synchronized int getReasonDisabled() {
return getState() == DISABLED ? REASON_USER : 0; if (noAdapter && !stopped) return REASON_NO_BT_ADAPTER;
if (!started || stopped) return REASON_STARTING_STOPPING;
return enabledByUser ? -1 : REASON_USER;
} }
} }
} }

View File

@@ -16,20 +16,17 @@ import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.lang.Integer.parseInt;
import static java.util.Collections.addAll; import static java.util.Collections.addAll;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort; import static java.util.Collections.sort;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -38,7 +35,6 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TR
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PREF_LAN_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS; import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_IP_PORTS;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.PROP_PORT;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED; import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
@@ -50,36 +46,15 @@ class LanTcpPlugin extends TcpPlugin {
private static final Logger LOG = getLogger(LanTcpPlugin.class.getName()); private static final Logger LOG = getLogger(LanTcpPlugin.class.getName());
private static final LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4; private static final int MAX_ADDRESSES = 4;
private static final String SEPARATOR = ","; private static final String SEPARATOR = ",";
/**
* The IP address of an Android device providing a wifi access point.
*/
protected static final InetAddress WIFI_AP_ADDRESS;
/**
* The IP address of an Android device providing a wifi direct
* legacy mode access point.
*/
protected static final InetAddress WIFI_DIRECT_AP_ADDRESS;
static {
try {
WIFI_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 43, 1});
WIFI_DIRECT_AP_ADDRESS = InetAddress.getByAddress(
new byte[] {(byte) 192, (byte) 168, 49, 1});
} catch (UnknownHostException e) {
// Should only be thrown if the address has an illegal length
throw new AssertionError(e);
}
}
LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, LanTcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime, int connectionTimeout) { int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime, super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
connectionTimeout);
} }
@Override @Override
@@ -87,83 +62,38 @@ class LanTcpPlugin extends TcpPlugin {
return ID; return ID;
} }
@Override
public void start() {
if (used.getAndSet(true)) throw new IllegalStateException();
initialisePortProperty();
Settings settings = callback.getSettings();
state.setStarted(settings.getBoolean(PREF_PLUGIN_ENABLE, false));
bind();
}
protected void initialisePortProperty() {
TransportProperties p = callback.getLocalProperties();
if (isNullOrEmpty(p.get(PROP_PORT))) {
int port = new Random().nextInt(32768) + 32768;
p.put(PROP_PORT, String.valueOf(port));
callback.mergeLocalProperties(p);
}
}
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS); String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts); List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>(); List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> fallbacks = new ArrayList<>(); for (InetAddress local : getLocalIpAddresses()) {
for (InetAddress local : getUsableLocalInetAddresses()) { if (isAcceptableAddress(local)) {
// If we've used this address before, try to use the same port // If this is the old address, try to use the same port
int port = preferredPort; for (InetSocketAddress old : olds) {
for (InetSocketAddress old : olds) { if (old.getAddress().equals(local))
if (old.getAddress().equals(local)) { locals.add(new InetSocketAddress(local, old.getPort()));
port = old.getPort();
break;
} }
locals.add(new InetSocketAddress(local, 0));
} }
locals.add(new InetSocketAddress(local, port));
// Fall back to any available port
fallbacks.add(new InetSocketAddress(local, 0));
} }
locals.addAll(fallbacks); sort(locals, ADDRESS_COMPARATOR);
return locals; return locals;
} }
private int parsePortProperty(@Nullable String portProperty) {
if (isNullOrEmpty(portProperty)) return 0;
try {
return parseInt(portProperty);
} catch (NumberFormatException e) {
return 0;
}
}
private List<InetSocketAddress> parseSocketAddresses(String ipPorts) { private List<InetSocketAddress> parseSocketAddresses(String ipPorts) {
if (isNullOrEmpty(ipPorts)) return emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<>(); List<InetSocketAddress> addresses = new ArrayList<>();
if (isNullOrEmpty(ipPorts)) return addresses; for (String ipPort : split) {
for (String ipPort : ipPorts.split(SEPARATOR)) {
InetSocketAddress a = parseSocketAddress(ipPort); InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a); if (a != null) addresses.add(a);
} }
return addresses; return addresses;
} }
protected List<InetAddress> getUsableLocalInetAddresses() {
List<InterfaceAddress> ifAddrs =
new ArrayList<>(getLocalInterfaceAddresses());
// Prefer longer network prefixes
sort(ifAddrs, (a, b) ->
b.getNetworkPrefixLength() - a.getNetworkPrefixLength());
List<InetAddress> addrs = new ArrayList<>();
for (InterfaceAddress ifAddr : ifAddrs) {
InetAddress addr = ifAddr.getAddress();
if (isAcceptableAddress(addr)) addrs.add(addr);
}
return addrs;
}
@Override @Override
protected void setLocalSocketAddress(InetSocketAddress a) { protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a); String ipPort = getIpPortString(a);
@@ -201,20 +131,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
protected List<InetSocketAddress> getRemoteSocketAddresses( protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) { TransportProperties p) {
String ipPorts = p.get(PROP_IP_PORTS); return parseSocketAddresses(p.get(PROP_IP_PORTS));
List<InetSocketAddress> remotes = parseSocketAddresses(ipPorts);
int port = parsePortProperty(p.get(PROP_PORT));
// If the contact has a preferred port, we can guess their IP:port when
// they're providing a wifi access point
if (port != 0) {
InetSocketAddress wifiAp =
new InetSocketAddress(WIFI_AP_ADDRESS, port);
if (!remotes.contains(wifiAp)) remotes.add(wifiAp);
InetSocketAddress wifiDirectAp =
new InetSocketAddress(WIFI_DIRECT_AP_ADDRESS, port);
if (!remotes.contains(wifiDirectAp)) remotes.add(wifiDirectAp);
}
return remotes;
} }
private boolean isAcceptableAddress(InetAddress a) { private boolean isAcceptableAddress(InetAddress a) {
@@ -227,33 +144,53 @@ class LanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InetSocketAddress remote) {
InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false; if (!isAcceptableAddress(remote.getAddress())) return false;
// Try to determine whether the address is on the same LAN as us // Try to determine whether the address is on the same LAN as us
byte[] localIp = local.getAddress().getAddress(); ServerSocket ss = state.getServerSocket();
if (ss == null) return false;
byte[] localIp = ss.getInetAddress().getAddress();
byte[] remoteIp = remote.getAddress().getAddress(); byte[] remoteIp = remote.getAddress().getAddress();
int prefixLength = local.getNetworkPrefixLength(); return addressesAreOnSameLan(localIp, remoteIp);
return areAddressesInSameNetwork(localIp, remoteIp, prefixLength);
} }
// Package access for testing // Package access for testing
static boolean areAddressesInSameNetwork(byte[] localIp, byte[] remoteIp, boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
int prefixLength) { // 10.0.0.0/8
if (localIp.length != remoteIp.length) return false; if (isPrefix10(localIp)) return isPrefix10(remoteIp);
// Compare the first prefixLength bits of the addresses // 172.16.0.0/12
for (int i = 0; i < prefixLength; i++) { if (isPrefix172(localIp)) return isPrefix172(remoteIp);
int byteIndex = i >> 3; // 192.168.0.0/16
int bitIndex = i & 7; // 0 to 7 if (isPrefix192(localIp)) return isPrefix192(remoteIp);
int mask = 128 >> bitIndex; // Select the bit at bitIndex // Unrecognised prefix - may be compatible
if ((localIp[byteIndex] & mask) != (remoteIp[byteIndex] & mask)) {
return false; // Addresses differ at bit i
}
}
return true; return true;
} }
private static boolean isPrefix10(byte[] ipv4) {
return ipv4[0] == 10;
}
private static boolean isPrefix172(byte[] ipv4) {
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
}
private static boolean isPrefix192(byte[] ipv4) {
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
}
// Returns the prefix length for an RFC 1918 address, or 0 for any other
// address
private static int getRfc1918PrefixLength(InetAddress addr) {
if (!(addr instanceof Inet4Address)) return 0;
if (!addr.isSiteLocalAddress()) return 0;
byte[] ipv4 = addr.getAddress();
if (isPrefix10(ipv4)) return 8;
if (isPrefix172(ipv4)) return 12;
if (isPrefix192(ipv4)) return 16;
return 0;
}
@Override @Override
public boolean supportsKeyAgreement() { public boolean supportsKeyAgreement() {
return true; return true;
@@ -293,11 +230,6 @@ class LanTcpPlugin extends TcpPlugin {
byte[] commitment, BdfList descriptor) { byte[] commitment, BdfList descriptor) {
ServerSocket ss = state.getServerSocket(); ServerSocket ss = state.getServerSocket();
if (ss == null) return null; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for key agreement server socket");
return null;
}
InetSocketAddress remote; InetSocketAddress remote;
try { try {
remote = parseSocketAddress(descriptor); remote = parseSocketAddress(descriptor);
@@ -305,7 +237,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Invalid IP/port in key agreement descriptor"); LOG.info("Invalid IP/port in key agreement descriptor");
return null; return null;
} }
if (!isConnectable(local, remote)) { if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) + LOG.info(scrubSocketAddress(remote) +
" is not connectable from " + " is not connectable from " +
@@ -318,7 +250,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket(); Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -367,4 +299,19 @@ class LanTcpPlugin extends TcpPlugin {
tryToClose(ss, LOG, WARNING); tryToClose(ss, LOG, WARNING);
} }
} }
static class LanAddressComparator implements Comparator<InetSocketAddress> {
@Override
public int compare(InetSocketAddress a, InetSocketAddress b) {
// Prefer addresses with non-zero ports
int aPort = a.getPort(), bPort = b.getPort();
if (aPort > 0 && bPort == 0) return -1;
if (aPort == 0 && bPort > 0) return 1;
// Prefer addresses with longer RFC 1918 prefixes
int aPrefix = getRfc1918PrefixLength(a.getAddress());
int bPrefix = getRfc1918PrefixLength(b.getAddress());
return bPrefix - aPrefix;
}
}
} }

View File

@@ -19,11 +19,10 @@ import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class LanTcpPluginFactory implements DuplexPluginFactory { public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -51,8 +50,8 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) { public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY, LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -2,15 +2,16 @@ package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor; import org.briarproject.bramble.PoliteExecutor;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@@ -23,7 +24,6 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
@@ -50,7 +51,6 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
@@ -68,8 +68,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
protected final Executor ioExecutor, bindExecutor; protected final Executor ioExecutor, bindExecutor;
protected final Backoff backoff; protected final Backoff backoff;
protected final PluginCallback callback; protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime; protected final int maxLatency, maxIdleTime, socketTimeout;
protected final int connectionTimeout, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false); protected final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState(); protected final PluginState state = new PluginState();
@@ -96,17 +95,15 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
* Returns true if connections to the given address can be attempted. * Returns true if connections to the given address can be attempted.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InterfaceAddress local, protected abstract boolean isConnectable(InetSocketAddress remote);
InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback, TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime, int connectionTimeout) { int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.backoff = backoff; this.backoff = backoff;
this.callback = callback; this.callback = callback;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime; this.maxIdleTime = maxIdleTime;
this.connectionTimeout = connectionTimeout;
if (maxIdleTime > Integer.MAX_VALUE / 2) if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE; socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2; else socketTimeout = maxIdleTime * 2;
@@ -205,8 +202,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} }
@Override @Override
public int getReasonsDisabled() { public int getReasonDisabled() {
return state.getReasonsDisabled(); return state.getReasonDisabled();
} }
@Override @Override
@@ -220,8 +217,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} }
@Override @Override
public void poll( public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
List<Pair<TransportProperties, ConnectionHandler>> properties) { properties) {
if (getState() != ACTIVE) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
@@ -243,18 +240,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
public DuplexTransportConnection createConnection(TransportProperties p) { public DuplexTransportConnection createConnection(TransportProperties p) {
ServerSocket ss = state.getServerSocket(); ServerSocket ss = state.getServerSocket();
if (ss == null) return null; if (ss == null) return null;
InterfaceAddress local = getLocalInterfaceAddress(ss.getInetAddress());
if (local == null) {
LOG.warning("No interface for server socket");
return null;
}
for (InetSocketAddress remote : getRemoteSocketAddresses(p)) { for (InetSocketAddress remote : getRemoteSocketAddresses(p)) {
// Don't try to connect to our own address if (!isConnectable(remote)) {
if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) {
continue;
}
if (!isConnectable(local, remote)) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) + LOG.info(scrubSocketAddress(remote) +
" is not connectable from " + " is not connectable from " +
@@ -267,7 +254,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
LOG.info("Connecting to " + scrubSocketAddress(remote)); LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket(); Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0)); s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout); s.connect(remote);
s.setSoTimeout(socketTimeout); s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote)); LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -281,19 +268,6 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
return null; return null;
} }
@Nullable
InterfaceAddress getLocalInterfaceAddress(InetAddress a) {
for (InterfaceAddress ifAddr : getLocalInterfaceAddresses()) {
if (ifAddr.getAddress().equals(a)) return ifAddr;
}
return null;
}
// Override for testing
protected boolean canConnectToOwnAddress() {
return false;
}
protected Socket createSocket() throws IOException { protected Socket createSocket() throws IOException {
return new Socket(); return new Socket();
} }
@@ -322,6 +296,22 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} }
} }
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean supportsRendezvous() { public boolean supportsRendezvous() {
return false; return false;
@@ -333,38 +323,14 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override Collection<InetAddress> getLocalIpAddresses() {
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
List<InterfaceAddress> getLocalInterfaceAddresses() {
List<InterfaceAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(iface.getInterfaceAddresses());
}
return addrs;
}
List<InetAddress> getLocalInetAddresses() {
List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : getNetworkInterfaces()) {
addrs.addAll(list(iface.getInetAddresses()));
}
return addrs;
}
private List<NetworkInterface> getNetworkInterfaces() {
try { try {
Enumeration<NetworkInterface> ifaces = Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
NetworkInterface.getNetworkInterfaces(); if (ifaces == null) return emptyList();
return ifaces == null ? emptyList() : list(ifaces); List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : list(ifaces))
addrs.addAll(list(iface.getInetAddresses()));
return addrs;
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
return emptyList(); return emptyList();
@@ -450,13 +416,13 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
} }
synchronized State getState() { synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING; if (!started || stopped || !enabledByUser) return DISABLED;
if (!enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE; return serverSocket == null ? INACTIVE : ACTIVE;
} }
synchronized int getReasonsDisabled() { synchronized int getReasonDisabled() {
return getState() == DISABLED ? REASON_USER : 0; if (!started || stopped) return REASON_STARTING_STOPPING;
return enabledByUser ? -1 : REASON_USER;
} }
} }
} }

View File

@@ -1,19 +1,15 @@
package org.briarproject.bramble.plugin.tcp; package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -33,10 +29,8 @@ class WanTcpPlugin extends TcpPlugin {
private volatile MappingResult mappingResult; private volatile MappingResult mappingResult;
WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper, WanTcpPlugin(Executor ioExecutor, Backoff backoff, PortMapper portMapper,
PluginCallback callback, int maxLatency, int maxIdleTime, PluginCallback callback, int maxLatency, int maxIdleTime) {
int connectionTimeout) { super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
this.portMapper = portMapper; this.portMapper = portMapper;
} }
@@ -45,29 +39,13 @@ class WanTcpPlugin extends TcpPlugin {
return ID; return ID;
} }
@Override
public boolean supportsKeyAgreement() {
return false;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException();
}
@Override @Override
protected List<InetSocketAddress> getLocalSocketAddresses() { protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available // Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties(); TransportProperties p = callback.getLocalProperties();
InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT)); InetSocketAddress old = parseSocketAddress(p.get(PROP_IP_PORT));
List<InetSocketAddress> addrs = new LinkedList<>(); List<InetSocketAddress> addrs = new LinkedList<>();
for (InetAddress a : getLocalInetAddresses()) { for (InetAddress a : getLocalIpAddresses()) {
if (isAcceptableAddress(a)) { if (isAcceptableAddress(a)) {
// If this is the old address, try to use the same port // If this is the old address, try to use the same port
if (old != null && old.getAddress().equals(a)) if (old != null && old.getAddress().equals(a))
@@ -108,8 +86,7 @@ class WanTcpPlugin extends TcpPlugin {
} }
@Override @Override
protected boolean isConnectable(InterfaceAddress local, protected boolean isConnectable(InetSocketAddress remote) {
InetSocketAddress remote) {
if (remote.getPort() == 0) return false; if (remote.getPort() == 0) return false;
return isAcceptableAddress(remote.getAddress()); return isAcceptableAddress(remote.getAddress());
} }

View File

@@ -20,11 +20,10 @@ import static org.briarproject.bramble.api.plugin.WanTcpConstants.ID;
@NotNullByDefault @NotNullByDefault
public class WanTcpPluginFactory implements DuplexPluginFactory { public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 30_000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -56,7 +55,7 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff, WanTcpPlugin plugin = new WanTcpPlugin(ioExecutor, backoff,
new PortMapperImpl(shutdownManager), callback, MAX_LATENCY, new PortMapperImpl(shutdownManager), callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT); MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -17,7 +17,7 @@ public interface CircumventionProvider {
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"}; String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
/** /**
* Countries where obfs4 or meek bridge connections are likely to work. * Countries where obfs4 bridge connection are likely to work.
* Should be a subset of {@link #BLOCKED}. * Should be a subset of {@link #BLOCKED}.
*/ */
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" }; String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };

View File

@@ -19,7 +19,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
@@ -74,7 +73,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.CONTROL_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.ID; import static org.briarproject.bramble.api.plugin.TorConstants.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
@@ -193,6 +191,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
if (!torDirectory.exists()) { if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) { if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory."); LOG.warning("Could not create Tor directory.");
@@ -280,7 +279,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} catch (IOException e) { } catch (IOException e) {
throw new PluginException(e); throw new PluginException(e);
} }
state.setStarted(); state.setTorStarted();
// Check whether we're online // Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
@@ -535,8 +534,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public int getReasonsDisabled() { public int getReasonDisabled() {
return state.getReasonsDisabled(); return state.getReasonDisabled();
} }
@Override @Override
@@ -550,8 +549,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public void poll( public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
List<Pair<TransportProperties, ConnectionHandler>> properties) { properties) {
if (getState() != ACTIVE) return; if (getState() != ACTIVE) return;
backoff.increment(); backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) { for (Pair<TransportProperties, ConnectionHandler> p : properties) {
@@ -685,17 +684,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void circuitStatus(String status, String id, String path) { public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") && if (status.equals("BUILT") &&
@@ -786,8 +774,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
boolean enabledByUser = boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, true);
settings.getBoolean(PREF_PLUGIN_ENABLE, true);
int network = settings.getInt(PREF_TOR_NETWORK, int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
@@ -803,58 +790,55 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Charging: " + charging); LOG.info("Charging: " + charging);
} }
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false; boolean enableNetwork = false, enableBridges = false;
boolean useMeek = false, enableConnectionPadding = false; boolean useMeek = false, enableConnectionPadding = false;
boolean disabledBySettings = false;
int reasonDisabled = REASON_STARTING_STOPPING;
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
} else if (!enabledByUser) {
LOG.info("Disabling network, user has disabled Tor");
disabledBySettings = true;
reasonDisabled = REASON_USER;
} else if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, device is on battery");
disabledBySettings = true;
reasonDisabled = REASON_BATTERY;
} else if (!useMobile && !wifi) {
LOG.info("Disabling network, device is using mobile data");
disabledBySettings = true;
reasonDisabled = REASON_MOBILE_DATA;
} else if (automatic && blocked && !bridgesWork) {
LOG.info("Disabling network, country is blocked");
disabledBySettings = true;
reasonDisabled = REASON_COUNTRY_BLOCKED;
} else { } else {
if (!enabledByUser) { LOG.info("Enabling network");
LOG.info("User has disabled Tor"); enableNetwork = true;
reasonsDisabled |= REASON_USER; if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
} (automatic && bridgesWork)) {
if (!charging && onlyWhenCharging) { if (circumventionProvider.needsMeek(country)) {
LOG.info("Configured not to use battery"); LOG.info("Using meek bridges");
reasonsDisabled |= REASON_BATTERY; enableBridges = true;
} useMeek = true;
if (!useMobile && !wifi) { } else {
LOG.info("Configured not to use mobile data"); LOG.info("Using obfs4 bridges");
reasonsDisabled |= REASON_MOBILE_DATA; enableBridges = true;
} }
if (automatic && blocked && !bridgesWork) {
LOG.info("Country is blocked");
reasonsDisabled |= REASON_COUNTRY_BLOCKED;
}
if (reasonsDisabled != 0) {
LOG.info("Disabling network due to settings");
} else { } else {
LOG.info("Enabling network"); LOG.info("Not using bridges");
enableNetwork = true; }
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (wifi && charging) {
(automatic && bridgesWork)) { LOG.info("Enabling connection padding");
if (circumventionProvider.needsMeek(country)) { enableConnectionPadding = true;
LOG.info("Using meek bridges"); } else {
enableBridges = true; LOG.info("Disabling connection padding");
useMeek = true;
} else {
LOG.info("Using obfs4 bridges");
enableBridges = true;
}
} else {
LOG.info("Not using bridges");
}
if (wifi && charging) {
LOG.info("Enabling connection padding");
enableConnectionPadding = true;
} else {
LOG.info("Disabling connection padding");
}
} }
} }
state.setReasonsDisabled(reasonsDisabled);
state.setDisabledBySettings(disabledBySettings, reasonDisabled);
try { try {
if (enableNetwork) { if (enableNetwork) {
@@ -879,14 +863,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this") @GuardedBy("this")
private boolean started = false, private boolean started = false,
stopped = false, stopped = false,
torStarted = false,
networkInitialised = false, networkInitialised = false,
networkEnabled = false, networkEnabled = false,
bootstrapped = false, bootstrapped = false,
circuitBuilt = false, circuitBuilt = false,
settingsChecked = false; disabledBySettings = false;
@GuardedBy("this") @GuardedBy("this")
private int reasonsDisabled = 0; private int reasonDisabled = REASON_STARTING_STOPPING;
@GuardedBy("this") @GuardedBy("this")
@Nullable @Nullable
@@ -897,8 +882,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
// Doesn't affect getState()
synchronized void setTorStarted() {
torStarted = true;
}
synchronized boolean isTorRunning() { synchronized boolean isTorRunning() {
return started && !stopped; return torStarted && !stopped;
} }
@Nullable @Nullable
@@ -929,9 +919,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
synchronized void setReasonsDisabled(int reasonsDisabled) { synchronized void setDisabledBySettings(boolean disabledBySettings,
settingsChecked = true; int reasonDisabled) {
this.reasonsDisabled = reasonsDisabled; this.disabledBySettings = disabledBySettings;
this.reasonDisabled = reasonDisabled;
callback.pluginStateChanged(getState()); callback.pluginStateChanged(getState());
} }
@@ -948,17 +939,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
synchronized State getState() { synchronized State getState() {
if (!started || stopped || !settingsChecked) { if (!started || stopped || disabledBySettings) return DISABLED;
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
if (!networkInitialised) return ENABLING; if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE; if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING; return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
} }
synchronized int getReasonsDisabled() { synchronized int getReasonDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0; return getState() == DISABLED ? reasonDisabled : -1;
} }
} }
} }

View File

@@ -38,7 +38,7 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.ListMatcher.listOf; import static org.briarproject.bramble.test.CollectionMatcher.collectionOf;
import static org.briarproject.bramble.test.PairMatcher.pairOf; import static org.briarproject.bramble.test.PairMatcher.pairOf;
import static org.briarproject.bramble.test.TestUtils.getContactId; import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -351,17 +351,14 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts // Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId); oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties))); will(returnValue(singletonMap(contactId, properties)));
oneOf(connectionRegistry).getConnectedContacts(transportId); oneOf(connectionRegistry).getConnectedContacts(transportId);
will(returnValue(emptyList())); will(returnValue(emptyList()));
// Poll the plugin // Poll the plugin
oneOf(plugin).poll(with(listOf(pairOf( oneOf(plugin).poll(with(collectionOf(
equal(properties), any(ConnectionHandler.class))))); pairOf(equal(properties), any(ConnectionHandler.class)))));
}}); }});
poller.eventOccurred(new TransportActiveEvent(transportId)); poller.eventOccurred(new TransportActiveEvent(transportId));
@@ -397,9 +394,6 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future)); will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts // Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId); oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties))); will(returnValue(singletonMap(contactId, properties)));

View File

@@ -8,11 +8,12 @@ import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportConnectionReader; import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter; import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.io.IOException;
@@ -22,6 +23,7 @@ import java.net.InetSocketAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -33,89 +35,56 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.plugin.tcp.LanTcpPlugin.areAddressesInSameNetwork;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class LanTcpPluginTest extends BrambleTestCase { public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff(); private final Backoff backoff = new TestBackoff();
private final ExecutorService ioExecutor = newCachedThreadPool(); private final ExecutorService ioExecutor = newCachedThreadPool();
private Callback callback = null;
private LanTcpPlugin plugin = null;
@Before
public void setUp() {
callback = new Callback();
plugin = new LanTcpPlugin(ioExecutor, backoff, callback, 0, 0, 1000) {
@Override
protected boolean canConnectToOwnAddress() {
return true;
}
};
}
@Test @Test
public void testAreAddressesInSameNetwork() { public void testAddressesAreOnSameLan() {
// Local and remote in 10.0.0.0/8 Callback callback = new Callback();
assertTrue(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
makeAddress(10, 255, 255, 255), 8)); 0, 0);
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), // Local and remote in 10.0.0.0/8 should return true
makeAddress(10, 255, 255, 255), 9)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255)));
// Local and remote in 172.16.0.0/12 // Local and remote in 172.16.0.0/12 should return true
assertTrue(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), assertTrue(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255), 12)); makeAddress(172, 31, 255, 255)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), // Local and remote in 192.168.0.0/16 should return true
makeAddress(172, 31, 255, 255), 13)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local and remote in 192.168.0.0/16 // Local and remote in 169.254.0.0/16 (link-local) should return true
assertTrue(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0), assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
makeAddress(192, 168, 255, 255), 16)); makeAddress(169, 254, 255, 255)));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0), // Local and remote in different recognised prefixes should return false
makeAddress(192, 168, 255, 255), 17)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(172, 31, 255, 255)));
// Local and remote in 169.254.0.0/16 assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
assertTrue(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0), makeAddress(192, 168, 255, 255)));
makeAddress(169, 254, 255, 255), 16)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0), makeAddress(10, 255, 255, 255)));
makeAddress(169, 254, 255, 255), 17)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local in 10.0.0.0/8, remote in a different network assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), makeAddress(10, 255, 255, 255)));
makeAddress(172, 31, 255, 255), 8)); assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), makeAddress(172, 31, 255, 255)));
makeAddress(192, 168, 255, 255), 8)); // Remote prefix unrecognised should return false
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0), assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(169, 254, 255, 255), 8)); makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
// Local in 172.16.0.0/12, remote in a different network makeAddress(1, 2, 3, 4)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255), 12)); makeAddress(1, 2, 3, 4)));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), // Both prefixes unrecognised should return true (could be link-local)
makeAddress(192, 168, 255, 255), 12)); assertTrue(plugin.addressesAreOnSameLan(makeAddress(1, 2, 3, 4),
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0), makeAddress(5, 6, 7, 8)));
makeAddress(169, 254, 255, 255), 12));
// Local in 192.168.0.0/16, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(172, 31, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(169, 254, 255, 255), 16));
// Local in 169.254.0.0/16, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(10, 255, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(172, 31, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(192, 168, 255, 255), 16));
} }
private byte[] makeAddress(int... parts) { private byte[] makeAddress(int... parts) {
@@ -126,7 +95,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testIncomingConnection() throws Exception { public void testIncomingConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -155,7 +130,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testOutgoingConnection() throws Exception { public void testOutgoingConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -198,7 +179,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testIncomingKeyAgreementConnection() throws Exception { public void testIncomingKeyAgreementConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
KeyAgreementListener kal = KeyAgreementListener kal =
@@ -240,7 +227,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test @Test
public void testOutgoingKeyAgreementConnection() throws Exception { public void testOutgoingKeyAgreementConnection() throws Exception {
assumeTrue(systemHasLocalIpv4Address()); if (!systemHasLocalIpv4Address()) {
System.err.println("WARNING: Skipping test, no local IPv4 address");
return;
}
Callback callback = new Callback();
DuplexPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
plugin.start(); plugin.start();
// The plugin should have bound a socket and stored the port number // The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS)); assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -285,12 +278,63 @@ public class LanTcpPluginTest extends BrambleTestCase {
plugin.stop(); plugin.stop();
} }
@Test
public void testComparatorPrefersNonZeroPorts() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
assertEquals(0, comparator.compare(nonZero, nonZero));
assertTrue(comparator.compare(nonZero, zero) < 0);
assertTrue(comparator.compare(zero, nonZero) > 0);
assertEquals(0, comparator.compare(zero, zero));
}
@Test
public void testComparatorPrefersLongerPrefixes() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
assertEquals(0, comparator.compare(prefix192, prefix192));
assertTrue(comparator.compare(prefix192, prefix172) < 0);
assertTrue(comparator.compare(prefix192, prefix10) < 0);
assertTrue(comparator.compare(prefix172, prefix192) > 0);
assertEquals(0, comparator.compare(prefix172, prefix172));
assertTrue(comparator.compare(prefix172, prefix10) < 0);
assertTrue(comparator.compare(prefix10, prefix192) > 0);
assertTrue(comparator.compare(prefix10, prefix172) > 0);
assertEquals(0, comparator.compare(prefix10, prefix10));
}
@Test
public void testComparatorPrefersSiteLocalToLinkLocal() {
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0);
assertTrue(comparator.compare(prefix192, linkLocal) < 0);
assertTrue(comparator.compare(prefix172, linkLocal) < 0);
assertTrue(comparator.compare(prefix10, linkLocal) < 0);
assertTrue(comparator.compare(linkLocal, prefix192) > 0);
assertTrue(comparator.compare(linkLocal, prefix172) > 0);
assertTrue(comparator.compare(linkLocal, prefix10) > 0);
assertEquals(0, comparator.compare(linkLocal, linkLocal));
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean systemHasLocalIpv4Address() throws Exception { private boolean systemHasLocalIpv4Address() throws Exception {
for (NetworkInterface i : list(getNetworkInterfaces())) { for (NetworkInterface i : list(getNetworkInterfaces())) {
for (InetAddress a : list(i.getInetAddresses())) { for (InetAddress a : list(i.getInetAddresses())) {
if (a instanceof Inet4Address) { if (a instanceof Inet4Address)
return a.isLinkLocalAddress() || a.isSiteLocalAddress(); return a.isLinkLocalAddress() || a.isSiteLocalAddress();
}
} }
} }
return false; return false;
@@ -299,9 +343,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
@NotNullByDefault @NotNullByDefault
private static class Callback implements PluginCallback { private static class Callback implements PluginCallback {
// Properties will be stored twice: the preferred port at startup, private final CountDownLatch propertiesLatch = new CountDownLatch(1);
// and the IP:port when the server socket is bound
private final CountDownLatch propertiesLatch = new CountDownLatch(2);
private final CountDownLatch connectionsLatch = new CountDownLatch(1); private final CountDownLatch connectionsLatch = new CountDownLatch(1);
private final TransportProperties local = new TransportProperties(); private final TransportProperties local = new TransportProperties();
private final Settings settings = new Settings(); private final Settings settings = new Settings();

View File

@@ -49,7 +49,7 @@ import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS; import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
import static org.briarproject.bramble.test.ListMatcher.listOf; import static org.briarproject.bramble.test.CollectionMatcher.collectionOf;
import static org.briarproject.bramble.test.PairMatcher.pairOf; import static org.briarproject.bramble.test.PairMatcher.pairOf;
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
@@ -196,7 +196,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry)); will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class))); oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(listOf(pairOf( oneOf(plugin).poll(with(collectionOf(pairOf(
equal(transportProperties), equal(transportProperties),
any(ConnectionHandler.class))))); any(ConnectionHandler.class)))));
}}); }});
@@ -248,7 +248,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis(); oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry)); will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class))); oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(listOf(pairOf( oneOf(plugin).poll(with(collectionOf(pairOf(
equal(transportProperties), equal(transportProperties),
any(ConnectionHandler.class))))); any(ConnectionHandler.class)))));
}}); }});

View File

@@ -5,24 +5,24 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import java.util.List; import java.util.Collection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public class ListMatcher<T> extends BaseMatcher<List<T>> { public class CollectionMatcher<T> extends BaseMatcher<Collection<T>> {
private final Matcher<T> elementMatcher; private final Matcher<T> elementMatcher;
public ListMatcher(Matcher<T> elementMatcher) { public CollectionMatcher(Matcher<T> elementMatcher) {
this.elementMatcher = elementMatcher; this.elementMatcher = elementMatcher;
} }
@Override @Override
public boolean matches(@Nullable Object item) { public boolean matches(@Nullable Object item) {
if (!(item instanceof List)) return false; if (!(item instanceof Collection)) return false;
List list = (List) item; Collection collection = (Collection) item;
for (Object element : list) { for (Object element : collection) {
if (!elementMatcher.matches(element)) return false; if (!elementMatcher.matches(element)) return false;
} }
return true; return true;
@@ -33,7 +33,7 @@ public class ListMatcher<T> extends BaseMatcher<List<T>> {
description.appendText("matches a collection"); description.appendText("matches a collection");
} }
public static <T> ListMatcher<T> listOf(Matcher<T> t) { public static <T> CollectionMatcher<T> collectionOf(Matcher<T> t) {
return new ListMatcher<>(t); return new CollectionMatcher<>(t);
} }
} }

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionHandler; import org.briarproject.bramble.api.plugin.ConnectionHandler;
import org.briarproject.bramble.api.plugin.DiscoveryHandler;
import org.briarproject.bramble.api.plugin.PluginCallback; import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException; import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
@@ -21,7 +20,7 @@ import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -32,9 +31,9 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -122,8 +121,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
@Override @Override
public int getReasonsDisabled() { public int getReasonDisabled() {
return 0; return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
} }
@Override @Override
@@ -137,8 +136,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
@Override @Override
public void poll( public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
List<Pair<TransportProperties, ConnectionHandler>> properties) { properties) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -214,17 +213,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override @Override
public void incomingCallConnected() { public void incomingCallConnected() {
LOG.info("Incoming call connected"); LOG.info("Incoming call connected");
@@ -292,7 +280,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
} }
private State getState() { private State getState() {
if (!started || stopped) return STARTING_STOPPING; if (!started || stopped) return DISABLED;
if (failed) return INACTIVE; if (failed) return INACTIVE;
return initialised ? ACTIVE : ENABLING; return initialised ? ACTIVE : ENABLING;
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="@+id/end"
app:constraintSetStart="@id/start"
app:duration="1000">
<OnClick app:targetId="@+id/chevronView" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/longRangeLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0">
<CustomAttribute
app:attributeName="crossfade"
app:customFloatValue="0" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@+id/connectionsLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_bias="1.0">
<CustomAttribute
app:attributeName="crossfade"
app:customFloatValue="1" />
</Constraint>
<Constraint
android:id="@+id/backgroundView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/torSwitch"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/nearbyLabel"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/wifiSwitch"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/btSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/btSwitch"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/longRangeLabel"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/torSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/nearbyLabel"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/wifiSwitch"
app:layout_constraintStart_toEndOf="parent" />
<Constraint
android:id="@+id/torIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/wifiIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/btIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<Constraint
android:id="@+id/connectionsLabel"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/torIcon"
app:layout_constraintEnd_toStartOf="@+id/torIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/torIcon" />
</ConstraintSet>
</MotionScene>

View File

@@ -22,8 +22,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 10207 versionCode 10205
versionName "1.2.7" versionName "1.2.5"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -98,7 +98,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-beta01' implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01' implementation 'androidx.recyclerview:recyclerview-selection:1.0.0'
implementation 'ch.acra:acra:4.11' implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'

View File

@@ -9,6 +9,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import java.util.Collection; import java.util.Collection;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
@@ -17,10 +18,7 @@ public interface BlogController extends BaseController {
void setGroupId(GroupId g); void setGroupId(GroupId g);
@UiThread @UiThread
void setBlogSharingListener(BlogSharingListener listener); void setBlogSharingListener(@Nullable BlogSharingListener listener);
@UiThread
void unsetBlogSharingListener(BlogSharingListener listener);
void loadBlogPosts( void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);

View File

@@ -96,15 +96,10 @@ class BlogControllerImpl extends BaseControllerImpl
} }
@Override @Override
public void setBlogSharingListener(BlogSharingListener listener) { public void setBlogSharingListener(@Nullable BlogSharingListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetBlogSharingListener(BlogSharingListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (groupId == null || listener == null) if (groupId == null || listener == null)

View File

@@ -141,8 +141,7 @@ public class BlogFragment extends BaseFragment
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
blogController.unsetBlogSharingListener(this); blogController.setBlogSharingListener(null);
sharingController.unsetSharingListener(this);
} }
@Override @Override

View File

@@ -7,6 +7,7 @@ import org.briarproject.briar.api.blog.Blog;
import java.util.Collection; import java.util.Collection;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
@@ -18,10 +19,7 @@ public interface FeedController extends BaseController {
void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler); void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler);
@UiThread @UiThread
void setFeedListener(FeedListener listener); void setFeedListener(@Nullable FeedListener listener);
@UiThread
void unsetFeedListener(FeedListener listener);
@NotNullByDefault @NotNullByDefault
interface FeedListener extends BlogListener { interface FeedListener extends BlogListener {

View File

@@ -69,15 +69,10 @@ class FeedControllerImpl extends BaseControllerImpl implements FeedController {
} }
@Override @Override
public void setFeedListener(FeedListener listener) { public void setFeedListener(@Nullable FeedListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetFeedListener(FeedListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (listener == null) throw new IllegalStateException(); if (listener == null) throw new IllegalStateException();

View File

@@ -134,7 +134,7 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
feedController.unsetFeedListener(this); feedController.setFeedListener(null);
} }
@Override @Override

View File

@@ -61,7 +61,7 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListen
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName; import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -87,12 +87,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
private ContactListAdapter adapter; private ContactListAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
/** private Snackbar snackbar;
* The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
*/
@Nullable
private Snackbar snackbar = null;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
@@ -168,6 +163,13 @@ public class ContactListFragment extends BaseFragment implements EventListener,
list.setEmptyText(getString(R.string.no_contacts)); list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action)); list.setEmptyAction(getString(R.string.no_contacts_action));
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, v ->
startActivity(new Intent(getContext(),
PendingContactListActivity.class)))
.make(contentView, R.string.pending_contact_requests_snackbar,
LENGTH_INDEFINITE);
return contentView; return contentView;
} }
@@ -201,9 +203,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
listener.runOnDbThread(() -> { listener.runOnDbThread(() -> {
try { try {
if (contactManager.getPendingContacts().isEmpty()) { if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar); runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else { } else {
runOnUiThreadUnlessDestroyed(this::showSnackBar); runOnUiThreadUnlessDestroyed(() -> snackbar.show());
} }
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
@@ -218,7 +220,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
adapter.clear(); adapter.clear();
list.showProgressBar(); list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
dismissSnackBar();
} }
private void loadContacts() { private void loadContacts() {
@@ -314,27 +315,4 @@ public class ContactListFragment extends BaseFragment implements EventListener,
} }
} }
@UiThread
private void showSnackBar() {
if (snackbar != null) return;
View v = requireNonNull(getView());
int stringRes = R.string.pending_contact_requests_snackbar;
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, view -> showPendingContactList())
.make(v, stringRes, LENGTH_INDEFINITE);
snackbar.show();
}
@UiThread
private void dismissSnackBar() {
if (snackbar == null) return;
snackbar.dismiss();
snackbar = null;
}
private void showPendingContactList() {
Intent i = new Intent(getContext(), PendingContactListActivity.class);
startActivity(i);
}
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.contact.add.remote;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -37,10 +38,12 @@ import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong; import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -196,7 +199,9 @@ public class NicknameFragment extends BaseFragment {
private void showWarningDialog(String name1, String name2) { private void showWarningDialog(String name1, String name2) {
Context ctx = requireContext(); Context ctx = requireContext();
Builder b = new Builder(ctx, R.style.BriarDialogTheme); Builder b = new Builder(ctx, R.style.BriarDialogTheme);
b.setIcon(getDialogIcon(ctx, R.drawable.alerts_and_states_error)); Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
b.setIcon(icon);
b.setTitle(getString(R.string.duplicate_link_dialog_title)); b.setTitle(getString(R.string.duplicate_link_dialog_title));
b.setMessage( b.setMessage(
getString(R.string.duplicate_link_dialog_text_3, name1, name2)); getString(R.string.duplicate_link_dialog_text_3, name1, name2));

View File

@@ -92,7 +92,7 @@ public class PendingContactListViewModel extends AndroidViewModel
Collection<Pair<PendingContact, PendingContactState>> pairs = Collection<Pair<PendingContact, PendingContactState>> pairs =
contactManager.getPendingContacts(); contactManager.getPendingContacts();
List<PendingContactItem> items = new ArrayList<>(pairs.size()); List<PendingContactItem> items = new ArrayList<>(pairs.size());
boolean online = pairs.isEmpty(); boolean online = items.isEmpty();
for (Pair<PendingContact, PendingContactState> pair : pairs) { for (Pair<PendingContact, PendingContactState> pair : pairs) {
PendingContact p = pair.getFirst(); PendingContact p = pair.getFirst();
PendingContactState state = pair.getSecond(); PendingContactState state = pair.getSecond();

View File

@@ -54,6 +54,8 @@ class PendingContactViewHolder extends ViewHolder {
status.setText(R.string.waiting_for_contact_to_come_online); status.setText(R.string.waiting_for_contact_to_come_online);
break; break;
case OFFLINE: case OFFLINE:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText(""); status.setText("");
break; break;
case CONNECTING: case CONNECTING:

View File

@@ -16,12 +16,6 @@ public interface SharingController {
@UiThread @UiThread
void setSharingListener(SharingListener listener); void setSharingListener(SharingListener listener);
/**
* Unsets the listener.
*/
@UiThread
void unsetSharingListener(SharingListener listener);
/** /**
* Call this when your lifecycle starts, * Call this when your lifecycle starts,
* so the listener will be called when information changes. * so the listener will be called when information changes.

View File

@@ -43,11 +43,6 @@ public class SharingControllerImpl implements SharingController, EventListener {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetSharingListener(SharingListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
public void onStart() { public void onStart() {
eventBus.addListener(this); eventBus.addListener(this);

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.conversation;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.transition.Fade; import android.transition.Fade;
import android.transition.Transition; import android.transition.Transition;
@@ -34,6 +35,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
@@ -56,7 +59,6 @@ import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -276,7 +278,10 @@ public class ImageActivity extends BriarActivity
Builder builder = new Builder(this, R.style.BriarDialogTheme); Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_save_image)); builder.setTitle(getString(R.string.dialog_title_save_image));
builder.setMessage(getString(R.string.dialog_message_save_image)); builder.setMessage(getString(R.string.dialog_message_save_image));
builder.setIcon(getDialogIcon(this, R.drawable.ic_security)); Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_security);
DrawableCompat.setTint(requireNonNull(icon),
ContextCompat.getColor(this, R.color.color_primary));
builder.setIcon(icon);
builder.setPositiveButton(R.string.save_image, okListener); builder.setPositiveButton(R.string.save_image, okListener);
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
builder.show(); builder.show();

View File

@@ -53,7 +53,6 @@ import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
@@ -259,9 +258,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private boolean shouldEnableWifi() { private boolean shouldEnableWifi() {
if (hasEnabledWifi) return false; if (hasEnabledWifi) return false;
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID); Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return false; return p != null && p.getState() == DISABLED;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
} }
private void requestBluetoothDiscoverable() { private void requestBluetoothDiscoverable() {
@@ -287,9 +284,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false; if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
if (hasEnabledBluetooth) return false; if (hasEnabledBluetooth) return false;
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID); Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
if (p == null) return false; return p != null && p.getState() == DISABLED;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
} }
@Override @Override

View File

@@ -7,7 +7,9 @@ import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
@@ -45,7 +47,9 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START; import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
@@ -84,11 +88,11 @@ public class NavDrawerActivity extends BriarActivity implements
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@Inject @Inject
LifecycleManager lifecycleManager; LifecycleManager lifecycleManager;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private ScrollView drawerScrollView;
private NavigationView navigation; private NavigationView navigation;
@Override @Override
@@ -110,8 +114,26 @@ public class NavDrawerActivity extends BriarActivity implements
if (ask) showDozeDialog(getString(R.string.setup_doze_intro)); if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
}); });
View drawerScrollView = findViewById(R.id.drawerScrollView); drawerScrollView = findViewById(R.id.drawerScrollView);
View chevronView = drawerScrollView.findViewById(R.id.chevronView);
drawerScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// hide/show chevron depending on whether we can scroll
if (drawerScrollView.canScrollVertically(1)) {
chevronView.setVisibility(VISIBLE);
} else {
chevronView.setVisibility(INVISIBLE);
}
drawerScrollView.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
});
new PluginViewController(drawerScrollView, this, viewModel); new PluginViewController(drawerScrollView, this, viewModel);
chevronView.setOnClickListener(v ->
drawerScrollView.fullScroll(FOCUS_DOWN)
);
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
drawerLayout = findViewById(R.id.drawer_layout); drawerLayout = findViewById(R.id.drawer_layout);
@@ -350,4 +372,5 @@ public class NavDrawerActivity extends BriarActivity implements
expiryWarning.setVisibility(GONE); expiryWarning.setVisibility(GONE);
} }
} }
} }

View File

@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.LocationUtils;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -35,18 +34,12 @@ import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@NotNullByDefault @NotNullByDefault
@@ -64,7 +57,6 @@ public class NavDrawerViewModel extends AndroidViewModel
private final Executor dbExecutor; private final Executor dbExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final LocationUtils locationUtils;
private final EventBus eventBus; private final EventBus eventBus;
private final MutableLiveData<Boolean> showExpiryWarning = private final MutableLiveData<Boolean> showExpiryWarning =
@@ -82,12 +74,11 @@ public class NavDrawerViewModel extends AndroidViewModel
@Inject @Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor, NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, PluginManager pluginManager, SettingsManager settingsManager, PluginManager pluginManager,
LocationUtils locationUtils, EventBus eventBus) { EventBus eventBus) {
super(app); super(app);
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus; this.eventBus = eventBus;
eventBus.addListener(this); eventBus.addListener(this);
updatePluginStates(); updatePluginStates();
@@ -203,7 +194,7 @@ public class NavDrawerViewModel extends AndroidViewModel
private State getTransportState(TransportId id) { private State getTransportState(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id); Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? STARTING_STOPPING : plugin.getState(); return plugin == null ? DISABLED : plugin.getState();
} }
@Nullable @Nullable
@@ -225,31 +216,8 @@ public class NavDrawerViewModel extends AndroidViewModel
return liveData; return liveData;
} }
int getReasonsDisabled(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? 0 : plugin.getReasonsDisabled();
}
void setPluginEnabled(TransportId t, boolean enabled) { void setPluginEnabled(TransportId t, boolean enabled) {
pluginManager.setPluginEnabled(t, enabled); pluginManager.setPluginEnabled(t, enabled);
} }
void setTorEnabled(boolean battery, boolean mobileData, boolean location) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, true);
if (battery) s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
if (mobileData) s.putBoolean(PREF_TOR_MOBILE, true);
if (location) s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_WITH_BRIDGES);
dbExecutor.execute(() -> {
try {
settingsManager.mergeSettings(s, TorConstants.ID.getString());
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
String getCurrentCountryName() {
return getCountryDisplayName(locationUtils.getCurrentCountry());
}
} }

View File

@@ -1,10 +1,7 @@
package org.briarproject.briar.android.navdrawer; package org.briarproject.briar.android.navdrawer;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ScrollView;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
@@ -13,70 +10,22 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.lifecycle.LifecycleOwner;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.app.ActivityCompat;
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.transition.TransitionManager.beginDelayedTransition;
import static android.view.View.FOCUS_DOWN;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static org.briarproject.bramble.api.plugin.Plugin.REASON_USER;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.briar.android.navdrawer.NavDrawerViewModel.TRANSPORT_IDS; import static org.briarproject.briar.android.navdrawer.NavDrawerViewModel.TRANSPORT_IDS;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
class PluginViewController { class PluginViewController {
private final AppCompatActivity activity;
private final NavDrawerViewModel viewModel;
private final ConstraintLayout drawerContent;
private final ConstraintSet collapsedConstraints, expandedConstraints;
private final AppCompatImageButton chevronView;
private final ImageView torIcon, wifiIcon, btIcon; private final ImageView torIcon, wifiIcon, btIcon;
private final SwitchCompat torSwitch, wifiSwitch, btSwitch; private final SwitchCompat torSwitch, wifiSwitch, btSwitch;
private boolean expanded = false; PluginViewController(View v, LifecycleOwner owner,
PluginViewController(View v, AppCompatActivity activity,
NavDrawerViewModel viewModel) { NavDrawerViewModel viewModel) {
this.activity = activity;
this.viewModel = viewModel;
drawerContent = v.findViewById(R.id.drawerContent);
collapsedConstraints = new ConstraintSet();
collapsedConstraints.clone(v.getContext(),
R.layout.navigation_menu_collapsed);
expandedConstraints = new ConstraintSet();
expandedConstraints.clone(v.getContext(),
R.layout.navigation_menu_expanded);
// Scroll the drawer to the bottom when the view is expanded/collapsed
ScrollView scrollView = v.findViewById(R.id.drawerScrollView);
drawerContent.addOnLayoutChangeListener((view, left, top, right,
bottom, oldLeft, oldTop, oldRight, oldBottom) ->
scrollView.fullScroll(FOCUS_DOWN));
// Clicking the chevron expands or collapses the view
chevronView = v.findViewById(R.id.chevronView);
chevronView.setOnClickListener(view -> expandOrCollapseView());
// The whole view is clickable when collapsed
v.findViewById(R.id.connectionsBackground).setOnClickListener(view ->
expandOrCollapseView());
torIcon = v.findViewById(R.id.torIcon); torIcon = v.findViewById(R.id.torIcon);
wifiIcon = v.findViewById(R.id.wifiIcon); wifiIcon = v.findViewById(R.id.wifiIcon);
@@ -89,48 +38,12 @@ class PluginViewController {
for (TransportId t : TRANSPORT_IDS) { for (TransportId t : TRANSPORT_IDS) {
// a OnCheckedChangeListener would get triggered on programmatic updates // a OnCheckedChangeListener would get triggered on programmatic updates
SwitchCompat switchCompat = getSwitch(t); SwitchCompat switchCompat = getSwitch(t);
switchCompat.setOnClickListener(buttonView -> { switchCompat.setOnClickListener(buttonView ->
if (switchCompat.isChecked()) tryToEnablePlugin(t); // TODO check reason first and change settings if needed
else viewModel.setPluginEnabled(t, false); viewModel.setPluginEnabled(t, switchCompat.isChecked())
// Revert the switch to its previous state until the plugin );
// changes its state viewModel.getPluginState(t)
switchCompat.toggle(); .observe(owner, state -> stateUpdate(t, state));
});
viewModel.getPluginState(t).observe(activity, state ->
stateUpdate(t, state));
}
}
private void expandOrCollapseView() {
if (SDK_INT >= 19) beginDelayedTransition(drawerContent);
if (expanded) {
collapsedConstraints.applyTo(drawerContent);
chevronView.setImageResource(R.drawable.chevron_up_white);
} else {
expandedConstraints.applyTo(drawerContent);
chevronView.setImageResource(R.drawable.chevron_down_white);
}
expanded = !expanded;
}
private void tryToEnablePlugin(TransportId id) {
if (id.equals(TorConstants.ID)) {
int reasons = viewModel.getReasonsDisabled(id);
if (reasons == 0 || reasons == REASON_USER) {
viewModel.setPluginEnabled(id, true);
} else {
showTorSettingsDialog(reasons);
}
} else if (id.equals(BluetoothConstants.ID)) {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
i.putExtra(EXTRA_DISCOVERABLE_DURATION, 0);
PackageManager pm = activity.getPackageManager();
if (i.resolveActivity(pm) != null) {
ActivityCompat.startActivity(activity, i, null);
}
viewModel.setPluginEnabled(id, true);
} else {
viewModel.setPluginEnabled(id, true);
} }
} }
@@ -147,9 +60,7 @@ class PluginViewController {
} }
private void updateSwitch(SwitchCompat switchCompat, State state) { private void updateSwitch(SwitchCompat switchCompat, State state) {
boolean checked = state != STARTING_STOPPING && state != DISABLED; switchCompat.setChecked(state != DISABLED);
switchCompat.setChecked(checked);
switchCompat.setEnabled(state != STARTING_STOPPING);
} }
private ImageView getIcon(TransportId id) { private ImageView getIcon(TransportId id) {
@@ -172,42 +83,4 @@ class PluginViewController {
icon.setColorFilter(color); icon.setColorFilter(color);
} }
private void showTorSettingsDialog(int reasonsDisabled) {
boolean battery = (reasonsDisabled & REASON_BATTERY) != 0;
boolean mobileData = (reasonsDisabled & REASON_MOBILE_DATA) != 0;
boolean location = (reasonsDisabled & REASON_COUNTRY_BLOCKED) != 0;
StringBuilder s = new StringBuilder();
if (location) {
s.append("\t\u2022 ");
s.append(activity.getString(R.string.tor_override_network_setting,
viewModel.getCurrentCountryName()));
s.append('\n');
}
if (mobileData) {
s.append("\t\u2022 ");
s.append(activity.getString(
R.string.tor_override_mobile_data_setting));
s.append('\n');
}
if (battery) {
s.append("\t\u2022 ");
s.append(activity.getString(R.string.tor_only_when_charging_title));
s.append('\n');
}
String message = activity.getString(
R.string.tor_override_settings_body, s.toString());
AlertDialog.Builder b =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
b.setTitle(R.string.tor_override_settings_title);
b.setIcon(getDialogIcon(activity, R.drawable.ic_settings_black_24dp));
b.setMessage(message);
b.setPositiveButton(R.string.tor_override_settings_confirm,
(dialog, which) ->
viewModel.setTorEnabled(battery, mobileData, location));
b.setNegativeButton(R.string.cancel, (dialog, which) ->
dialog.dismiss());
b.show();
}
} }

View File

@@ -10,6 +10,8 @@ import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import java.util.Collection; import java.util.Collection;
import javax.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
@@ -19,10 +21,7 @@ interface GroupListController extends DbController {
* The listener must be set right after the controller was injected * The listener must be set right after the controller was injected
*/ */
@UiThread @UiThread
void setGroupListListener(GroupListListener listener); void setGroupListListener(@Nullable GroupListListener listener);
@UiThread
void unsetGroupListListener(GroupListListener listener);
@UiThread @UiThread
void onStart(); void onStart();

View File

@@ -80,15 +80,10 @@ class GroupListControllerImpl extends DbControllerImpl
} }
@Override @Override
public void setGroupListListener(GroupListListener listener) { public void setGroupListListener(@Nullable GroupListListener listener) {
this.listener = listener; this.listener = listener;
} }
@Override
public void unsetGroupListListener(GroupListListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override @Override
@CallSuper @CallSuper
public void onStart() { public void onStart() {

View File

@@ -112,7 +112,7 @@ public class GroupListFragment extends BaseFragment implements
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
controller.unsetGroupListListener(this); controller.setGroupListListener(null);
} }
@Override @Override

View File

@@ -72,11 +72,9 @@ import static android.widget.Toast.LENGTH_SHORT;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -85,7 +83,6 @@ import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback;
import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
@@ -317,8 +314,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
// Look up country name in the user's chosen language if available // Look up country name in the user's chosen language if available
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
String countryName = getCountryDisplayName(country); String countryName = country;
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getCountry().equalsIgnoreCase(country)) {
countryName = locale.getDisplayCountry();
break;
}
}
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
boolean useBridges = circumventionProvider.doBridgesWork(country); boolean useBridges = circumventionProvider.doBridgesWork(country);
@@ -339,8 +341,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
try { try {
long start = now(); long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE); settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
torSettings = migrateTorSettings( torSettings = settingsManager.getSettings(TOR_NAMESPACE);
settingsManager.getSettings(TOR_NAMESPACE));
settingsLoaded = true; settingsLoaded = true;
logDuration(LOG, "Loading settings", start); logDuration(LOG, "Loading settings", start);
displaySettings(); displaySettings();
@@ -350,19 +351,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
}); });
} }
// TODO: Remove after a reasonable migration period (added 2020-01-29)
private Settings migrateTorSettings(Settings s) {
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
if (network == PREF_TOR_NETWORK_NEVER) {
s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC);
s.putBoolean(PREF_PLUGIN_ENABLE, false);
// We don't need to save the migrated settings - the Tor plugin is
// responsible for that. This code just handles the case where the
// settings are loaded before the plugin migrates them.
}
return s;
}
private void displaySettings() { private void displaySettings() {
listener.runOnUiThreadUnlessDestroyed(() -> { listener.runOnUiThreadUnlessDestroyed(() -> {
// due to events, we might try to display before a load completed // due to events, we might try to display before a load completed
@@ -681,7 +669,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
displaySettings(); displaySettings();
} else if (namespace.equals(TOR_NAMESPACE)) { } else if (namespace.equals(TOR_NAMESPACE)) {
LOG.info("Tor settings updated"); LOG.info("Tor settings updated");
torSettings = migrateTorSettings(s.getSettings()); torSettings = s.getSettings();
displaySettings(); displaySettings();
} }
} }

View File

@@ -6,7 +6,6 @@ import android.app.KeyguardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.Html; import android.text.Html;
@@ -39,12 +38,9 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.view.ArticleMovementMethod; import org.briarproject.briar.android.view.ArticleMovementMethod;
import org.briarproject.briar.android.widget.LinkDialogFragment; import org.briarproject.briar.android.widget.LinkDialogFragment;
import java.util.Locale;
import androidx.annotation.AttrRes; import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes; import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@@ -83,10 +79,7 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode; import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.content.ContextCompat.getSystemService; import static androidx.core.content.ContextCompat.getSystemService;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
@@ -388,7 +381,7 @@ public class UiUtils {
/** /**
* Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)}, * Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)},
* but without a {@link LifecycleOwner}. * but without a {@link LifecycleOwner}.
* <p> *
* Warning: Do NOT call from objects that have a lifecycle. * Warning: Do NOT call from objects that have a lifecycle.
*/ */
@UiThread @UiThread
@@ -409,19 +402,4 @@ public class UiUtils {
LAYOUT_DIRECTION_RTL; LAYOUT_DIRECTION_RTL;
} }
public static String getCountryDisplayName(String isoCode) {
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getCountry().equalsIgnoreCase(isoCode)) {
return locale.getDisplayCountry();
}
}
// Name is unknown
return isoCode;
}
public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
Drawable icon = getDrawable(ctx, resId);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
return icon;
}
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.widget; package org.briarproject.briar.android.widget;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
@@ -11,7 +10,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -24,7 +22,6 @@ import androidx.fragment.app.DialogFragment;
import static android.content.Intent.ACTION_VIEW; import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -67,23 +64,18 @@ public class LinkDialogFragment extends DialogFragment {
urlView.setText(url); urlView.setText(url);
// prepare normal intent or intent chooser // prepare normal intent or intent chooser
Context ctx = requireContext();
Intent i = new Intent(ACTION_VIEW, Uri.parse(url)); Intent i = new Intent(ACTION_VIEW, Uri.parse(url));
PackageManager packageManager = ctx.getPackageManager(); PackageManager packageManager =
List activities = requireNonNull(getContext()).getPackageManager();
packageManager.queryIntentActivities(i, MATCH_DEFAULT_ONLY); List activities = packageManager.queryIntentActivities(i,
MATCH_DEFAULT_ONLY);
boolean choice = activities.size() > 1; boolean choice = activities.size() > 1;
Intent intent = choice ? Intent.createChooser(i, Intent intent = choice ? Intent.createChooser(i,
getString(R.string.link_warning_open_link)) : i; getString(R.string.link_warning_open_link)) : i;
Button openButton = v.findViewById(R.id.openButton); Button openButton = v.findViewById(R.id.openButton);
openButton.setOnClickListener(v1 -> { openButton.setOnClickListener(v1 -> {
if (intent.resolveActivity(packageManager) != null) { startActivity(intent);
startActivity(intent);
} else {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT)
.show();
}
getDialog().dismiss(); getDialog().dismiss();
}); });

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawerScrollView" android:id="@+id/drawerScrollView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -7,6 +8,36 @@
android:fillViewport="true" android:fillViewport="true"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/navigation_menu_collapsed" /> <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintBottom_toTopOf="@+id/chevronView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="spread_inside"
app:menu="@menu/navigation_drawer" />
<include
layout="@layout/transports_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/drawerContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:menu="@menu/navigation_drawer" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/chevronView"
android:layout_width="0dp"
android:layout_height="24dp"
android:layout_marginBottom="8dp"
android:background="@color/divider"
android:foreground="?attr/selectableItemBackground"
android:src="@drawable/chevron_up_white"
app:layout_constraintBottom_toTopOf="@+id/connectionsLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed"
app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription,UnusedAttribute" />
<View
android:id="@+id/connectionsBackground"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<TextView
android:id="@+id/connectionsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:text="@string/transport_connection"
android:textSize="12sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/torIcon"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<!-- Hidden -->
<View
android:id="@+id/longRangeBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/item_background_highlight"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/longRangeLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_internet"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/torIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_tor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/torSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_tor"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/nearbyLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_nearby"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/wifiIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_lan"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/wifiSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_lan"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<ImageView
android:id="@+id/btIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:src="@drawable/transport_bt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/chevronView"
tools:checked="true"
tools:ignore="ContentDescription"
tools:tint="@color/briar_green" />
<!-- Hidden -->
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/btSwitch"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_bt"
android:visibility="gone"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,26 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerContent"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/window_background"> tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"
tools:showIn="@layout/navigation_menu">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/window_background"
app:elevation="0dp"
app:headerLayout="@layout/navigation_header"
app:itemBackground="@drawable/navigation_item_background"
app:itemIconTint="?attr/colorControlNormal"
app:itemTextColor="?android:textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:menu="@menu/navigation_drawer" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/chevronView" android:id="@+id/chevronView"
@@ -35,30 +20,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/navigation" app:layout_constraintTop_toBottomOf="@+id/navigation"
app:layout_constraintVertical_bias="1.0" app:layout_constraintVertical_bias="1.0"
app:layout_constraintVertical_chainStyle="packed"
app:tint="?attr/colorControlNormal" app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription,UnusedAttribute" /> tools:ignore="ContentDescription"
tools:visibility="visible" />
<!-- Hidden -->
<View
android:id="@+id/connectionsBackground"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<!-- Hidden -->
<TextView
android:id="@+id/connectionsLabel"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="@string/transport_connection"
android:textSize="12sp"
android:visibility="gone"
tools:ignore="MissingConstraints" />
<View <View
android:id="@+id/longRangeBackground" android:id="@+id/backgroundView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
@@ -76,8 +43,7 @@
android:text="@string/transport_internet" android:text="@string/transport_internet"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/torSwitch" app:layout_constraintBottom_toTopOf="@+id/torSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon" app:layout_constraintStart_toStartOf="@+id/torIcon" />
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
<ImageView <ImageView
android:id="@+id/torIcon" android:id="@+id/torIcon"
@@ -102,23 +68,22 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:text="@string/transport_tor" android:text="@string/transport_tor"
app:layout_constraintBottom_toTopOf="@+id/nearbyLabel" app:layout_constraintBottom_toTopOf="@id/nearbyLabel"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/torIcon" app:layout_constraintStart_toEndOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/longRangeLabel"
tools:checked="true" /> tools:checked="true" />
<TextView <TextView
android:id="@+id/nearbyLabel" android:id="@+id/nearbyLabel"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/transport_nearby" android:text="@string/transport_nearby"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/wifiSwitch" app:layout_constraintBottom_toTopOf="@+id/wifiSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon" app:layout_constraintStart_toStartOf="@+id/torIcon" />
app:layout_constraintTop_toBottomOf="@+id/torSwitch" />
<ImageView <ImageView
android:id="@+id/wifiIcon" android:id="@+id/wifiIcon"
@@ -147,7 +112,6 @@
app:layout_constraintBottom_toTopOf="@+id/btSwitch" app:layout_constraintBottom_toTopOf="@+id/btSwitch"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/wifiIcon" app:layout_constraintStart_toEndOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/nearbyLabel"
tools:checked="true" /> tools:checked="true" />
<ImageView <ImageView
@@ -177,7 +141,23 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btIcon" app:layout_constraintStart_toEndOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/wifiSwitch" app:layout_constraintVertical_bias="1.0"
tools:checked="true" /> tools:checked="true" />
</androidx.constraintlayout.widget.ConstraintLayout> <TextView
android:id="@+id/connectionsLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/transport_connection"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/torIcon"
app:layout_constraintEnd_toStartOf="@+id/torIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/torIcon" />
</merge>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<!--Setup--> <!--Setup-->
<string name="setup_title">Willkommen bei Briar</string> <string name="setup_title">Willkommen bei Briar</string>
<string name="setup_name_explanation">Dein Benutzername wird neben deinem geposteten Inhalt angezeigt. Du kannst diesen nicht mehr ändern, nachdem du dein Konto erstellt hast.</string> <string name="setup_name_explanation">Dein Benutzername wird neben deinem geposteten Inhalt angezeigt. Du kannst diesen nicht mehr ändern, nachdem du dein Konto erstellt hast.</string>

View File

@@ -125,16 +125,9 @@
<string name="set_contact_alias_hint">Nombre del contacto</string> <string name="set_contact_alias_hint">Nombre del contacto</string>
<string name="set_alias_button">Cambiar</string> <string name="set_alias_button">Cambiar</string>
<string name="delete_all_messages">Eliminar todos los mensajes</string> <string name="delete_all_messages">Eliminar todos los mensajes</string>
<string name="dialog_title_delete_all_messages">Confirmar eliminación de mensajes</string> <string name="dialog_title_delete_all_messages">Confirmar la eliminación del mensaje</string>
<string name="dialog_message_delete_all_messages">¿Estás seguro de que deseas eliminar todos los mensajes?</string> <string name="dialog_message_delete_all_messages">¿Estás seguro de que deseas eliminar todos los mensajes?</string>
<string name="dialog_title_not_all_messages_deleted">No se pudieron eliminar todos los mensajes.</string> <string name="dialog_title_not_all_messages_deleted">No se pudieron eliminar todos los mensajes.</string>
<string name="dialog_message_not_deleted_ongoing_both">Los mensajes relacionados con presentaciones o invitaciones en curso no se pueden eliminar hasta que finalicen.</string>
<string name="dialog_message_not_deleted_ongoing_introductions">Los mensajes relacionados con presentaciones o invitaciones en curso no se pueden eliminar hasta que finalicen.</string>
<string name="dialog_message_not_deleted_ongoing_invitations">Los mensajes relacionados a invitaciones en curso no pueden ser borrados hasta su conclusión.</string>
<string name="dialog_message_not_deleted_partly_downloaded">Los mensajes parcialmente descargados no se pueden eliminar hasta que haya finalizado la descarga.</string>
<string name="dialog_message_not_deleted_not_all_selected_both">Para borrar una invitación o presentación, debes seleccionar la petición y la respuesta.</string>
<string name="dialog_message_not_deleted_not_all_selected_introductions">Para eliminar una introducción, debe seleccionar la solicitud y la respuesta.</string>
<string name="dialog_message_not_deleted_not_all_selected_invitations">Para eliminar una invitación, debe seleccionar la solicitud y la respuesta.</string>
<string name="delete_contact">Eliminar contacto</string> <string name="delete_contact">Eliminar contacto</string>
<string name="dialog_title_delete_contact">Confirmar eliminación de contacto</string> <string name="dialog_title_delete_contact">Confirmar eliminación de contacto</string>
<string name="dialog_message_delete_contact">¿Seguro que quieres eliminar este contacto y todos los mensajes intercambiados entre vosotros?</string> <string name="dialog_message_delete_contact">¿Seguro que quieres eliminar este contacto y todos los mensajes intercambiados entre vosotros?</string>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<!--Setup--> <!--Setup-->
<string name="setup_title">ברוך הבא אל Briar</string> <string name="setup_title">ברוך הבא אל Briar</string>
<string name="setup_name_explanation">כינויך יוראה ליד תוכן כלשהו שתכתוב. אינך יכול לשנות אותו לאחר יצירת חשבונך.</string> <string name="setup_name_explanation">כינויך יוראה ליד תוכן כלשהו שתכתוב. אינך יכול לשנות אותו לאחר יצירת חשבונך.</string>

View File

@@ -128,13 +128,6 @@
<string name="dialog_title_delete_all_messages">Staðfesta eyðingu skilaboða</string> <string name="dialog_title_delete_all_messages">Staðfesta eyðingu skilaboða</string>
<string name="dialog_message_delete_all_messages">Ertu viss um að þú viljir eyða öllum skilaboðum?</string> <string name="dialog_message_delete_all_messages">Ertu viss um að þú viljir eyða öllum skilaboðum?</string>
<string name="dialog_title_not_all_messages_deleted">Gat ekki eytt öllum skilaboðum</string> <string name="dialog_title_not_all_messages_deleted">Gat ekki eytt öllum skilaboðum</string>
<string name="dialog_message_not_deleted_ongoing_both">Skilaboð sem tengjast fyrirliggjandi kynningum fyrirliggjandi boðum og kynningum er ekki hægt að eyða fyrr en viðkomandi ferli er lokið.</string>
<string name="dialog_message_not_deleted_ongoing_introductions">Skilaboð sem tengjast fyrirliggjandi kynningum fyrirliggjandi kynningum er ekki hægt að eyða fyrr en viðkomandi ferli er lokið.</string>
<string name="dialog_message_not_deleted_ongoing_invitations">Skilaboð sem tengjast fyrirliggjandi kynningum fyrirliggjandi boðum er ekki hægt að eyða fyrr en viðkomandi ferli er lokið.</string>
<string name="dialog_message_not_deleted_partly_downloaded">Skilaboðum sem sótt hafa verið að hluta er ekki hægt að eyða fyrr en niðurhali þeirra er lokið.</string>
<string name="dialog_message_not_deleted_not_all_selected_both">Til að eyða boði eða kynningu, þarftu að velja beiðnina og svarið.</string>
<string name="dialog_message_not_deleted_not_all_selected_introductions">Til að eyða kynningu, þarftu að velja beiðnina og svarið.</string>
<string name="dialog_message_not_deleted_not_all_selected_invitations">Til að eyða boði, þarftu að velja beiðnina og svarið.</string>
<string name="delete_contact">Eyða tengilið</string> <string name="delete_contact">Eyða tengilið</string>
<string name="dialog_title_delete_contact">Staðfesta eyðingu tengiliðar</string> <string name="dialog_title_delete_contact">Staðfesta eyðingu tengiliðar</string>
<string name="dialog_message_delete_contact">Ertu viss að þú viljir fjarlægja þennan tengilið ásamt öllum þeim skilaboðum sem ykkur hafa farið á milli?</string> <string name="dialog_message_delete_contact">Ertu viss að þú viljir fjarlægja þennan tengilið ásamt öllum þeim skilaboðum sem ykkur hafa farið á milli?</string>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<!--Setup--> <!--Setup-->
<string name="setup_title">Sveiki atvykę į Briar</string> <string name="setup_title">Sveiki atvykę į Briar</string>
<string name="setup_name_explanation">Jūsų slapyvardis bus rodomas šalia bet kokio jūsų skelbiamo turinio. Sukūrę paskyrą, slapyvardžio pakeisti nebegalėsite.</string> <string name="setup_name_explanation">Jūsų slapyvardis bus rodomas šalia bet kokio jūsų skelbiamo turinio. Sukūrę paskyrą, slapyvardžio pakeisti nebegalėsite.</string>

View File

@@ -128,13 +128,6 @@
<string name="dialog_title_delete_all_messages">Bevestig verwijderen berichten</string> <string name="dialog_title_delete_all_messages">Bevestig verwijderen berichten</string>
<string name="dialog_message_delete_all_messages">Weet je zeker dat je alle berichten wil verwijderen?</string> <string name="dialog_message_delete_all_messages">Weet je zeker dat je alle berichten wil verwijderen?</string>
<string name="dialog_title_not_all_messages_deleted">Kon niet alle berichten verwijderen</string> <string name="dialog_title_not_all_messages_deleted">Kon niet alle berichten verwijderen</string>
<string name="dialog_message_not_deleted_ongoing_both">Berichten gerelateerd aan uitgaande uitnodigingen of introducties kunnen niet worden verwijderd totdat ze zijn afgerond.</string>
<string name="dialog_message_not_deleted_ongoing_introductions">Berichten gerelateerd aan uitgaande introducties kunnen niet worden verwijderd totdat ze zijn afgerond.</string>
<string name="dialog_message_not_deleted_ongoing_invitations">Berichten gerelateerd aan uitgaande uitnodigingen kunnen niet worden verwijderd totdat ze zijn afgerond.</string>
<string name="dialog_message_not_deleted_partly_downloaded">Gedeeltelijk gedownloade berichten kunnen niet worden verwijderd totdat ze volledig zijn gedownload.</string>
<string name="dialog_message_not_deleted_not_all_selected_both">Om een uitnodiging of introductie te verwijderen met je het verzoek en het antwoord selecteren.</string>
<string name="dialog_message_not_deleted_not_all_selected_introductions">Om een introductie te verwijderen met je het verzoek en het antwoord selecteren.</string>
<string name="dialog_message_not_deleted_not_all_selected_invitations">Om een uitnodiging te verwijderen met je het verzoek en het antwoord selecteren.</string>
<string name="delete_contact">Verwijder bericht</string> <string name="delete_contact">Verwijder bericht</string>
<string name="dialog_title_delete_contact">Bevestig verwijderen contact</string> <string name="dialog_title_delete_contact">Bevestig verwijderen contact</string>
<string name="dialog_message_delete_contact">Weet je zeker dat je dit contact en alle berichten die met dit contact zijn uitgewisseld wil verwijderen?</string> <string name="dialog_message_delete_contact">Weet je zeker dat je dit contact en alle berichten die met dit contact zijn uitgewisseld wil verwijderen?</string>

View File

@@ -258,11 +258,11 @@
<string name="introduction_error">Произошла ошибка во время представления.</string> <string name="introduction_error">Произошла ошибка во время представления.</string>
<string name="introduction_response_error">Ошибка при ответе на представление</string> <string name="introduction_response_error">Ошибка при ответе на представление</string>
<string name="introduction_request_sent">Вы сделали представление %1$s %2$s.</string> <string name="introduction_request_sent">Вы сделали представление %1$s %2$s.</string>
<string name="introduction_request_received">%1$s попросил(-а) вас представить %2$s. Вы хотите добавить %2$s в ваш список контактов?</string> <string name="introduction_request_received">%1$s попросил вас представить %2$s. Вы хотите добавить %2$s в ваш список контактов?</string>
<string name="introduction_request_exists_received">%1$s попросил(-а) вас представить %2$s, но %2$s уже находится в вашем списке контактов. Поскольку %1$s может не знать об этом, вы все равно можете ответить:</string> <string name="introduction_request_exists_received">%1$s попросил вас представить %2$s, но %2$s уже находится в вашем списке контактов. Поскольку %1$s может не знать об этом, вы все равно можете ответить:</string>
<string name="introduction_request_answered_received">%1$s попросил(-а) вас представить %2$s.</string> <string name="introduction_request_answered_received">%1$s попросил вас представить %2$s.</string>
<string name="introduction_response_accepted_sent">Вы приняли представление %1$s.</string> <string name="introduction_response_accepted_sent">Вы приняли представление %1$s.</string>
<string name="introduction_response_accepted_sent_info">%1$s будет добавлен(-а) в контакты после принятия представления. Это может занять некоторое время.</string> <string name="introduction_response_accepted_sent_info">Прежде чем добавить %1$s в свои контакты, необходимо чтобы он принял представление. Это может занять некоторое время.</string>
<string name="introduction_response_declined_sent">Вы отказались от представления %1$s.</string> <string name="introduction_response_declined_sent">Вы отказались от представления %1$s.</string>
<string name="introduction_response_accepted_received">%1$s принял(-а) представление %2$s.</string> <string name="introduction_response_accepted_received">%1$s принял(-а) представление %2$s.</string>
<string name="introduction_response_declined_received">%1$s отказался от представления %2$s.</string> <string name="introduction_response_declined_received">%1$s отказался от представления %2$s.</string>
@@ -303,8 +303,8 @@
<!--Private Group Invitations--> <!--Private Group Invitations-->
<string name="groups_invitations_title">Приглашения в группу</string> <string name="groups_invitations_title">Приглашения в группу</string>
<string name="groups_invitations_invitation_sent">Вы пригласили %1$s присоединиться к группе \"%2$s\".</string> <string name="groups_invitations_invitation_sent">Вы пригласили %1$s присоединиться к группе \"%2$s\".</string>
<string name="groups_invitations_invitation_received">%1$s пригласил(-а) вас присоединиться к группе \"%2$s\".</string> <string name="groups_invitations_invitation_received">%1$s пригласил вас присоединиться к группе \"%2$s\".</string>
<string name="groups_invitations_joined">Присоединился(-лась) к группе</string> <string name="groups_invitations_joined">Присоединился к группе</string>
<string name="groups_invitations_declined">Приглашение в группу отклонено</string> <string name="groups_invitations_declined">Приглашение в группу отклонено</string>
<plurals name="groups_invitations_open"> <plurals name="groups_invitations_open">
<item quantity="one">%d открытое приглашение в группу</item> <item quantity="one">%d открытое приглашение в группу</item>
@@ -360,10 +360,10 @@
<string name="forum_invitation_sent">Вы поделились форумом \"%1$s\" с %2$s.</string> <string name="forum_invitation_sent">Вы поделились форумом \"%1$s\" с %2$s.</string>
<string name="forum_invitations_title">Приглашения на форум</string> <string name="forum_invitations_title">Приглашения на форум</string>
<string name="forum_invitation_exists">Вы уже приняли приглашение на этот форум.\n\nПринятие большего количества приглашений сделает вашу связь с форумом более быстрой и надежной.</string> <string name="forum_invitation_exists">Вы уже приняли приглашение на этот форум.\n\nПринятие большего количества приглашений сделает вашу связь с форумом более быстрой и надежной.</string>
<string name="forum_joined_toast">Присоединился(-лась) к форуму</string> <string name="forum_joined_toast">Присоединился к форуму</string>
<string name="forum_declined_toast">Приглашение отклонено</string> <string name="forum_declined_toast">Приглашение отклонено</string>
<string name="shared_by_format">Совместно %s</string> <string name="shared_by_format">Совместно %s</string>
<string name="forum_invitation_already_sharing">Уже поделился(-лась)</string> <string name="forum_invitation_already_sharing">Уже поделился</string>
<string name="forum_invitation_response_accepted_sent">Вы приняли приглашение на форум от %s.</string> <string name="forum_invitation_response_accepted_sent">Вы приняли приглашение на форум от %s.</string>
<string name="forum_invitation_response_declined_sent">Вы отклонили приглашение на форум от %s.</string> <string name="forum_invitation_response_declined_sent">Вы отклонили приглашение на форум от %s.</string>
<string name="forum_invitation_response_accepted_received">%s принял(-а) приглашение на форум.</string> <string name="forum_invitation_response_accepted_received">%s принял(-а) приглашение на форум.</string>
@@ -399,12 +399,12 @@
<string name="blogs_sharing_share">Поделиться блогом</string> <string name="blogs_sharing_share">Поделиться блогом</string>
<string name="blogs_sharing_error">Произошла ошибка при при попытке поделиться этим блогом.</string> <string name="blogs_sharing_error">Произошла ошибка при при попытке поделиться этим блогом.</string>
<string name="blogs_sharing_button">Поделиться блогом</string> <string name="blogs_sharing_button">Поделиться блогом</string>
<string name="blogs_sharing_snackbar">Доступ к блогу предоставлен выбранным контактам</string> <string name="blogs_sharing_snackbar">Поделиться блогом совместно с выбранными контактами</string>
<string name="blogs_sharing_response_accepted_sent">Вы приняли приглашение в блог от %s.</string> <string name="blogs_sharing_response_accepted_sent">Вы приняли приглашение в блог от %s.</string>
<string name="blogs_sharing_response_declined_sent">Вы отклонили приглашение в блог от %s.</string> <string name="blogs_sharing_response_declined_sent">Вы отклонили приглашение в блог от %s.</string>
<string name="blogs_sharing_response_accepted_received">%s принял(-а) приглашение в блог.</string> <string name="blogs_sharing_response_accepted_received">%s принял(-а) приглашение в блог.</string>
<string name="blogs_sharing_response_declined_received">%s отклонил(-а) приглашение в блог.</string> <string name="blogs_sharing_response_declined_received">%s отклонил(-а) приглашение в блог.</string>
<string name="blogs_sharing_invitation_received">%1$s поделился(-лась) блогом \"%2$s\" с вами.</string> <string name="blogs_sharing_invitation_received">%1$s поделился блогом \"%2$s\" с вами.</string>
<string name="blogs_sharing_invitation_sent">Вы поделились блогом \"%1$s\" с %2$s.</string> <string name="blogs_sharing_invitation_sent">Вы поделились блогом \"%1$s\" с %2$s.</string>
<string name="blogs_sharing_invitations_title">Приглашения в блог</string> <string name="blogs_sharing_invitations_title">Приглашения в блог</string>
<string name="blogs_sharing_joined_toast">Подписка на блог</string> <string name="blogs_sharing_joined_toast">Подписка на блог</string>

View File

@@ -168,7 +168,7 @@
<string name="authenticating_with_device">Autentiserar med enhet\u2026</string> <string name="authenticating_with_device">Autentiserar med enhet\u2026</string>
<string name="connection_error_title">Kunde ej ansluta till din kontakt</string> <string name="connection_error_title">Kunde ej ansluta till din kontakt</string>
<string name="connection_error_explanation">Kontrollera att ni båda är anslutna på samma Wi-Fi-nätverk.</string> <string name="connection_error_explanation">Kontrollera att ni båda är anslutna på samma Wi-Fi-nätverk.</string>
<string name="connection_error_feedback">Om det här problemet kvarstår, vänligen lämna <a href="feedback">synpunkter</a> så vi kan förbättra appen.</string> <string name="connection_error_feedback">Om det här problemet kvarstår, vänligen<a href="feedback">ge återkoppling</a> så vi kan förbättra appan.</string>
<!--Adding Contacts Remotely--> <!--Adding Contacts Remotely-->
<string name="add_contact_remotely_title_case">Lägg till en kontakt på avstånd</string> <string name="add_contact_remotely_title_case">Lägg till en kontakt på avstånd</string>
<string name="add_contact_nearby_title">Lägg till en närvarande kontakt</string> <string name="add_contact_nearby_title">Lägg till en närvarande kontakt</string>
@@ -492,8 +492,8 @@
<string name="choose_ringtone_title">Välj ringsignal</string> <string name="choose_ringtone_title">Välj ringsignal</string>
<string name="cannot_load_ringtone">Kan ej ladda ringsignal</string> <string name="cannot_load_ringtone">Kan ej ladda ringsignal</string>
<!--Settings Feedback--> <!--Settings Feedback-->
<string name="feedback_settings_title">Synpunkter</string> <string name="feedback_settings_title">Återkoppling</string>
<string name="send_feedback">Lämna synpunkter</string> <string name="send_feedback">Ge återkoppling</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Länkvarning</string> <string name="link_warning_title">Länkvarning</string>
<string name="link_warning_intro">Följande länk är på väg att öppnas i en extern app.</string> <string name="link_warning_intro">Följande länk är på väg att öppnas i en extern app.</string>
@@ -505,9 +505,9 @@
<string name="not_your_fault">Detta är inte ditt fel.</string> <string name="not_your_fault">Detta är inte ditt fel.</string>
<string name="please_send_report">Hjälp oss att förbättra Briar genom att skicka en felrapport.</string> <string name="please_send_report">Hjälp oss att förbättra Briar genom att skicka en felrapport.</string>
<string name="report_is_encrypted">Vi lovar att felrapporten är krypterad och skickas säkert.</string> <string name="report_is_encrypted">Vi lovar att felrapporten är krypterad och skickas säkert.</string>
<string name="feedback_title">Synpunkter</string> <string name="feedback_title">Återkoppling</string>
<string name="describe_crash">Beskriv vad som hänt (valfritt)</string> <string name="describe_crash">Beskriv vad som hänt (valfritt)</string>
<string name="enter_feedback">Skriv ner dina synpunkter</string> <string name="enter_feedback">Ge din återkoppling</string>
<string name="optional_contact_email">Din e-postadress (valfritt)</string> <string name="optional_contact_email">Din e-postadress (valfritt)</string>
<string name="include_debug_report_crash">Inkludera anonym data om krashen.</string> <string name="include_debug_report_crash">Inkludera anonym data om krashen.</string>
<string name="include_debug_report_feedback">Inkludera anonym data om den här enheten</string> <string name="include_debug_report_feedback">Inkludera anonym data om den här enheten</string>

View File

@@ -1,41 +1,24 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<!--Setup--> <!--Setup-->
<string name="setup_title">Briar\'a Hoşgeldiniz</string>
<string name="setup_next">Sonraki</string> <string name="setup_next">Sonraki</string>
<string name="setup_password_intro">Bir Parola Seçin</string>
<string name="setup_doze_title">Arkaplan Bağlantıları</string>
<string name="setup_doze_button">Bağlantılara İzin Ver</string>
<string name="choose_nickname">Kullanıcı adınızı belirleyin</string> <string name="choose_nickname">Kullanıcı adınızı belirleyin</string>
<string name="choose_password">Parolanızı belirleyin</string> <string name="choose_password">Parolanızı belirleyin</string>
<string name="confirm_password">Parolanızı doğrulayın</string> <string name="confirm_password">Parolanızı doğrulayın</string>
<string name="name_too_long">İsim çok uzun</string> <string name="name_too_long">İsim çok uzun</string>
<string name="password_too_weak">Parola çok zayıf</string> <string name="password_too_weak">Parola çok zayıf</string>
<string name="passwords_do_not_match">Parolalar uyuşmuyor</string> <string name="passwords_do_not_match">Girdiğiniz iki parola uyuşmuyor</string>
<string name="create_account_button">Hesap Oluştur</string> <string name="create_account_button">Hesabı Oluştur</string>
<string name="more_info">Daha Fazla Bilgi</string>
<string name="don_t_ask_again">Tekrar sorma</string>
<string name="setup_huawei_text">Lütfen aşağıdaki düğmeye dokunun ve Briar\'ın \"Korunan Uygulamalar\" ekranında korunduğundan emin olun.</string>
<string name="setup_huawei_button">Briar\'ı Koru</string>
<string name="setup_huawei_help">Briar korunan uygulamalar listesine eklenmezse, arka planda çalışamaz.</string>
<string name="warning_dozed">%s arka planda çalışamadı</string>
<!--Login--> <!--Login-->
<string name="enter_password">Parola</string> <string name="enter_password">Parola</string>
<string name="try_again">Parola yanlış, tekrar deneyin</string> <string name="try_again">Parola yanlış, tekrar deneyin</string>
<string name="sign_in_button">Oturum Aç</string> <string name="sign_in_button">Giriş Yap</string>
<string name="forgotten_password">Parolamı unuttum</string> <string name="forgotten_password">Parolamı unuttum</string>
<string name="dialog_title_lost_password">Kayıp Parola</string> <string name="dialog_title_lost_password">Kayıp Parola</string>
<string name="dialog_message_lost_password">Briar hesabınız, bulutta değil şifreli olarak cihazınızda saklanır, bu nedenle şifrenizi sıfırlayamıyoruz. Hesabınızı silmek ve tekrar başlamak ister misiniz? \n\nUyarı: Kimlikleriniz, kişileriniz ve iletileriniz kaybolur.</string> <string name="dialog_message_lost_password">Briar hesabınız, bulutta değil şifreli olarak cihazınızda saklanır, bu nedenle şifrenizi sıfırlayamıyoruz. Hesabınızı silmek ve tekrar başlamak ister misiniz? \n\nUyarı: Kimlikleriniz, kişileriniz ve iletileriniz kaybolur.</string>
<string name="startup_failed_notification_title">Briar başlayamadı</string> <string name="startup_failed_notification_title">Briar başlayamadı</string>
<string name="startup_failed_notification_text">Daha fazla bilgi için dokunun.</string>
<string name="startup_failed_activity_title">Briar Başlangıç Hatası</string> <string name="startup_failed_activity_title">Briar Başlangıç Hatası</string>
<string name="startup_failed_service_error">Briar gerekli bir eklentiyi başlatamadı. Briar\'ı yeniden yüklemek genellikle bu sorunu çözer. Bununla birlikte, lütfen Briar\'ın verilerinizi depolamak için merkezi sunucuları kullanmadığından hesabınızı ve onunla ilişkili tüm verileri kaybedeceğinizi unutmayın.</string> <string name="startup_failed_service_error">Briar gerekli bir eklentiyi başlatamadı. Briar\'ı yeniden yüklemek genellikle bu sorunu çözer. Bununla birlikte, lütfen Briar\'ın verilerinizi depolamak için merkezi sunucuları kullanmadığından hesabınızı ve onunla ilişkili tüm verileri kaybedeceğinizi unutmayın.</string>
<string name="download_briar">Briar\'ı kullanmaya devam etmek için lütfen en son dağıtımı indirin.</string>
<string name="create_new_account">Yeni bir hesap oluşturmanız gerekecek, fakat aynı takma adı kullanabilirsiniz.</string>
<string name="download_briar_button">En Son Sürümü İndir</string>
<string name="startup_open_database">Veritabanı Şifresi Çözülüyor...</string>
<string name="startup_migrate_database">Veritabanı Yükseltiliyor...</string>
<string name="startup_compact_database">Veritabanı Derli Toplu Hale Getiriliyor...</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Gezinme çekmecesini aç</string> <string name="nav_drawer_open_description">Gezinme çekmecesini aç</string>
<string name="nav_drawer_close_description">Gezinme çekmecesini kapat</string> <string name="nav_drawer_close_description">Gezinme çekmecesini kapat</string>
@@ -52,10 +35,6 @@
<string name="transport_bt">Bluetooth</string> <string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string> <string name="transport_lan">Wi-Fi</string>
<!--Notifications--> <!--Notifications-->
<string name="reminder_notification_title">Briar oturumunu kapatma</string>
<string name="reminder_notification_text">Tekrar oturum açmak için dokunun</string>
<string name="reminder_notification_channel_title">Briar Oturum Açma Anımsatıcı</string>
<string name="reminder_notification_dismiss">Vazgeç</string>
<string name="ongoing_notification_title">Briar\'a giriş yapıldı</string> <string name="ongoing_notification_title">Briar\'a giriş yapıldı</string>
<string name="ongoing_notification_text">Briar\'ı açmak için dokunun</string> <string name="ongoing_notification_text">Briar\'ı açmak için dokunun</string>
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
@@ -94,43 +73,16 @@
<string name="ellipsis"></string> <string name="ellipsis"></string>
<string name="text_too_long">Girilen metin çok uzun</string> <string name="text_too_long">Girilen metin çok uzun</string>
<string name="show_onboarding">Yardım Penceresini Göster</string> <string name="show_onboarding">Yardım Penceresini Göster</string>
<string name="fix">Düzeltme</string>
<string name="help">Yardım</string> <string name="help">Yardım</string>
<string name="sorry">Üzgünüm</string>
<string name="error_start_activity">Sisteminizde mevcut değil</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Gösterilecek kişi yok</string>
<string name="no_contacts_action">kişi eklemek için + simgesine dokunun</string>
<string name="date_no_private_messages">Hiç mesaj yok.</string> <string name="date_no_private_messages">Hiç mesaj yok.</string>
<string name="no_private_messages">Gösterilecek ileti yok</string>
<string name="message_hint">Mesaj yazın</string> <string name="message_hint">Mesaj yazın</string>
<string name="image_attach">Resim ekle</string>
<string name="image_attach_error">Resim(ler) eklenemedi</string>
<string name="image_attach_error_too_big">Resim çok büyük. Sınır %d MB.</string>
<string name="image_attach_error_invalid_mime_type">Resim biçimi desteklenmedi: %s</string>
<string name="set_contact_alias">Kişi adını değiştir</string>
<string name="set_contact_alias_hint">Ad</string>
<string name="set_alias_button">Değiştirme</string>
<string name="delete_all_messages">Tüm iletileri sil</string>
<string name="dialog_title_delete_all_messages">İleti Silmeyi Onayla</string>
<string name="dialog_message_delete_all_messages">Tüm iletileri silmek istediğinizden emin misiniz?</string>
<string name="dialog_title_not_all_messages_deleted">Tüm iletiler silinemedi</string>
<string name="delete_contact">Kişiyi sil</string> <string name="delete_contact">Kişiyi sil</string>
<string name="dialog_title_delete_contact">Kişi Silmeyi Onayla</string> <string name="dialog_title_delete_contact">Kişi Silmeyi Onayla</string>
<string name="dialog_message_delete_contact">Bu kişiyi ve bu kişiyle ilgili tüm iletileri kaldırmak istediğinize emin misiniz?</string> <string name="dialog_message_delete_contact">Bu kişiyi ve bu kişiyle ilgili tüm iletileri kaldırmak istediğinize emin misiniz?</string>
<string name="contact_deleted_toast">Kişi silindi</string> <string name="contact_deleted_toast">Kişi silindi</string>
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
<string name="you">Siz</string>
<string name="save_image">Resmi Kaydet</string>
<string name="dialog_title_save_image">Resim Kaydedilsin mi?</string>
<string name="save_image_success">Resim kaydedildi</string>
<string name="save_image_error">Resim kaydedilemedi</string>
<string name="dialog_title_no_image_support">Resimler mevcut değil</string>
<string name="dialog_title_image_support">Artık bu kişiye resim gönderebilirsiniz</string>
<string name="dialog_message_image_support">Resim eklemek için bu simgeye dokunun.</string>
<string name="messaging_too_many_attachments_toast">Yalnızca ilk %d resim gönderilecek</string>
<!--Adding Contacts--> <!--Adding Contacts-->
<string name="add_contact_title">Yakındaki Kişiyi Ekle</string> <string name="add_contact_title">Kişi ekle</string>
<string name="face_to_face">Kişi olarak eklemek istediğiniz kişiyle buluşmanız gerekir.\n\nBu, gelecekte başkalarının sizin kimliğinize bürünmesini veya mesajlarınızı okumasını engelleyecektir.</string> <string name="face_to_face">Kişi olarak eklemek istediğiniz kişiyle buluşmanız gerekir.\n\nBu, gelecekte başkalarının sizin kimliğinize bürünmesini veya mesajlarınızı okumasını engelleyecektir.</string>
<string name="continue_button">Devam et</string> <string name="continue_button">Devam et</string>
<string name="try_again_button">Tekrar deneyin</string> <string name="try_again_button">Tekrar deneyin</string>
@@ -139,67 +91,14 @@
<string name="contact_added_toast">Kişi eklendi: %s</string> <string name="contact_added_toast">Kişi eklendi: %s</string>
<string name="contact_already_exists"> %s kişisi zaten var</string> <string name="contact_already_exists"> %s kişisi zaten var</string>
<string name="qr_code_invalid">QR kod hatalı</string> <string name="qr_code_invalid">QR kod hatalı</string>
<string name="camera_error">Kamera hatası</string>
<string name="connecting_to_device">Cihaza bağlanıyor\u2026</string> <string name="connecting_to_device">Cihaza bağlanıyor\u2026</string>
<string name="authenticating_with_device">Cihazla kimlik doğrulama\u2026</string> <string name="authenticating_with_device">Cihazla kimlik doğrulama\u2026</string>
<!--Adding Contacts Remotely-->
<string name="add_contact_nearby_title">Yakındaki kişiyi ekle</string>
<string name="add_contact_remotely_title">Uzaktaki kişiyi ekle</string>
<string name="contact_name_hint">Kişiye bir takma ad verin</string>
<string name="contact_link_intro">Buraya kişinizden bağlantı girin</string>
<string name="contact_link_hint">Kişinin bağlantısı</string>
<string name="paste_button">Yapıştır</string>
<string name="add_contact_button">Kişi ekle</string>
<string name="copy_button">Kopyala</string>
<string name="share_button">Paylaş</string>
<string name="send_link_title">Değiş tokuş bağlantıları</string>
<string name="add_contact_choose_nickname">Takma Ad Seçin</string>
<string name="add_contact_choose_a_nickname">Bir takma ad girin</string>
<string name="nickname_intro">Kişinize bir takma ad verin. Onu sadece siz görebilirsiniz.</string>
<string name="your_link">Bu bağlantıyı eklemek istediğiniz kişiye verin</string>
<string name="link_clip_label">Briar bağlantısı</string>
<string name="link_copied_toast">Bağlantı kopyalandı</string>
<string name="adding_contact_error">Kişi eklenirken bir hata oluştu.</string>
<string name="pending_contact_requests_snackbar">Bekleyen iletişim istekleri var</string>
<string name="pending_contact_requests">Bekleyen İletişim İstekleri</string>
<string name="no_pending_contacts">Bekleyen kişi yok</string>
<string name="add_contact_remote_connecting">Bağlantı kuruluyor…</string>
<string name="waiting_for_contact_to_come_online">Kişinin çevrimiçi olması bekleniyor...</string>
<string name="connecting">Bağlantı kuruluyor…</string>
<string name="adding_contact">Kişi Ekleniyor...</string>
<string name="adding_contact_failed">Kişi ekleme başarısız oldu</string>
<string name="dialog_title_remove_pending_contact">Kaldırmayı Onayla</string>
<string name="dialog_message_remove_pending_contact">Bu kişi hala ekleniyor. Şimdi kaldırırsanız eklenmez.</string>
<string name="own_link_error">Kişinizin bağlantısını girin, kendi bağlantınızı değil</string>
<string name="nickname_missing">Lütfen bir takma ad girin.</string>
<string name="invalid_link">Geçersiz bağlantı</string>
<string name="missing_link">Lütfen bir bağlantı girin</string>
<!--This is a numeral indicating the first step in a series of screens-->
<string name="step_1">1</string>
<!--This is a numeral indicating the second step in a series of screens-->
<string name="step_2">2</string>
<plurals name="contact_added_notification_text">
<item quantity="one">Yeni kişi eklendi.</item>
<item quantity="other">%d yeni kişi eklendi.</item>
</plurals>
<string name="offline_state">İnternet bağlantısı yok</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s ve %s aynı kişi mi?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
<string name="same_person_button">Aynı Kişi</string>
<!--This is a button for answering that two nicknames refer to different people. This string
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Farklı Kişi</string>
<!--Introductions--> <!--Introductions-->
<string name="introduction_onboarding_title">Kişilerinizi tanıştırın</string> <string name="introduction_onboarding_title">Kişilerinizi tanıştırın</string>
<string name="introduction_onboarding_text">Kişilerinizi birbirinize tanıtabilirsiniz, bu nedenle Briar\'a bağlanmak için şahsen bir araya gelmeniz gerekmez.</string> <string name="introduction_onboarding_text">Kişilerinizi birbirinize tanıtabilirsiniz, bu nedenle Briar\'a bağlanmak için şahsen bir araya gelmeniz gerekmez.</string>
<string name="introduction_menu_item">Tanıştır</string> <string name="introduction_menu_item">Tanıştır</string>
<string name="introduction_activity_title">Kişi seç</string> <string name="introduction_activity_title">Kişi seç</string>
<string name="introduction_message_title">Kişileri Tanıştırın</string> <string name="introduction_message_title">Kişileri Tanıştırın</string>
<string name="introduction_message_hint">Bir ileti ekle (isteğe bağlı)</string>
<string name="introduction_button">Tanıştır</string> <string name="introduction_button">Tanıştır</string>
<string name="introduction_sent">Tanıştırma isteğiniz gönderildi.</string> <string name="introduction_sent">Tanıştırma isteğiniz gönderildi.</string>
<string name="introduction_error">Tanıştırma isteği yaparken bir hata oluştu.</string> <string name="introduction_error">Tanıştırma isteği yaparken bir hata oluştu.</string>
@@ -213,8 +112,11 @@
<string name="introduction_response_accepted_received">%1$s, %2$s ile tanışmayı kabul etti.</string> <string name="introduction_response_accepted_received">%1$s, %2$s ile tanışmayı kabul etti.</string>
<string name="introduction_response_declined_received">%1$s, %2$s ile tanışmayı reddetti.</string> <string name="introduction_response_declined_received">%1$s, %2$s ile tanışmayı reddetti.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s, %2$s kişisinin tanışmayı reddettiğini söyledi.</string> <string name="introduction_response_declined_received_by_introducee">%1$s, %2$s kişisinin tanışmayı reddettiğini söyledi.</string>
<plurals name="contact_added_notification_text">
<item quantity="one">Yeni kişi eklendi.</item>
<item quantity="other">%d yeni kişi eklendi.</item>
</plurals>
<!--Private Groups--> <!--Private Groups-->
<string name="groups_list_empty">Gösterilecek grup yok</string>
<string name="groups_created_by">%s tarafından oluşturuldu</string> <string name="groups_created_by">%s tarafından oluşturuldu</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d mesaj</item> <item quantity="one">%d mesaj</item>
@@ -226,8 +128,8 @@
<string name="groups_create_group_title">Özel Grup Oluştur</string> <string name="groups_create_group_title">Özel Grup Oluştur</string>
<string name="groups_create_group_button">Grup Oluştur</string> <string name="groups_create_group_button">Grup Oluştur</string>
<string name="groups_create_group_invitation_button">Davetiye Gönder</string> <string name="groups_create_group_invitation_button">Davetiye Gönder</string>
<string name="groups_create_group_hint">Özel grubunuz için bir ad belirleyin</string>
<string name="groups_invitation_sent">Grup davetiyesi gönderildi</string> <string name="groups_invitation_sent">Grup davetiyesi gönderildi</string>
<string name="groups_message_sent">Mesaj gönderildi</string>
<string name="groups_member_list">Üye Listesi</string> <string name="groups_member_list">Üye Listesi</string>
<string name="groups_invite_members">Üyeleri Davet Edin</string> <string name="groups_invite_members">Üyeleri Davet Edin</string>
<string name="groups_member_created_you">Grubu siz oluşturdunuz</string> <string name="groups_member_created_you">Grubu siz oluşturdunuz</string>
@@ -266,38 +168,28 @@
<string name="groups_reveal_visible_revealed_by_contact">Kişi ilişkileri grup tarafından görülebilir (%s görünür yaptı)</string> <string name="groups_reveal_visible_revealed_by_contact">Kişi ilişkileri grup tarafından görülebilir (%s görünür yaptı)</string>
<string name="groups_reveal_invisible">Kişi ilişkisi grup tarafından görülemez</string> <string name="groups_reveal_invisible">Kişi ilişkisi grup tarafından görülemez</string>
<!--Forums--> <!--Forums-->
<string name="no_forums">Gösterilecek forum yok</string>
<string name="create_forum_title">Forumu Ouştur</string> <string name="create_forum_title">Forumu Ouştur</string>
<string name="choose_forum_hint">Forumunuz için bir ad belirleyin</string>
<string name="create_forum_button">Forumu Ouştur</string> <string name="create_forum_button">Forumu Ouştur</string>
<string name="forum_created_toast">Forum Oluşturuldu</string> <string name="forum_created_toast">Forum Oluşturuldu</string>
<string name="no_forum_posts">Gösterilecek posta yok</string>
<string name="no_posts">Gönderi yok</string> <string name="no_posts">Gönderi yok</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d gönderi</item> <item quantity="one">%d gönderi</item>
<item quantity="other">%d gönderi</item> <item quantity="other">%d gönderi</item>
</plurals> </plurals>
<string name="forum_new_message_hint">Yeni Posta</string>
<string name="forum_message_reply_hint">Yeni Cevap</string> <string name="forum_message_reply_hint">Yeni Cevap</string>
<string name="btn_reply">Cevapla</string> <string name="btn_reply">Cevapla</string>
<string name="forum_leave">Forumdan Ayrıl</string> <string name="forum_leave">Forumdan Ayrıl</string>
<string name="dialog_title_leave_forum">Forumdan Ayrılmayı Onayla</string> <string name="dialog_title_leave_forum">Forumdan Ayrılmayı Onayla</string>
<string name="dialog_message_leave_forum">Bu forumdan ayrılmak istediğinize emin misiniz?\n\Bu forumu paylaştığınız kişiler güncelleme almayı durdurabilir.</string>
<string name="dialog_button_leave">Ayrıl</string> <string name="dialog_button_leave">Ayrıl</string>
<!--Forum Sharing--> <!--Forum Sharing-->
<string name="forum_share_button">Forumu Paylaş</string> <string name="forum_share_button">Forumu Paylaş</string>
<string name="contacts_selected">Kişiler Seçildi</string> <string name="contacts_selected">Kişiler Seçildi</string>
<string name="activity_share_toolbar_header">Kişi Seç</string> <string name="activity_share_toolbar_header">Kişi Seç</string>
<string name="no_contacts_selector">Gösterilecek kişi yok</string>
<string name="no_contacts_selector_action">Kişi ekledikten sonra lütfen buraya geri dönün</string>
<string name="forum_shared_snackbar">Forum, seçilen kişiler ile paylaşıldı</string> <string name="forum_shared_snackbar">Forum, seçilen kişiler ile paylaşıldı</string>
<string name="forum_share_message">Bir ileti ekle (isteğe bağlı)</string>
<string name="forum_share_error">Forumu paylaşırken bir hata meydana geldi.</string> <string name="forum_share_error">Forumu paylaşırken bir hata meydana geldi.</string>
<string name="forum_invitation_received">%1$s sizinle \"%2$s\" forumunu paylaştı.</string> <string name="forum_invitation_received">%1$s sizinle \"%2$s\" forumunu paylaştı.</string>
<string name="forum_invitation_sent">\"%1$s\" forumunu %2$s ile paylaştınız.</string> <string name="forum_invitation_sent">\"%1$s\" forumunu %2$s ile paylaştınız.</string>
<string name="forum_invitations_title">Forum Davetleri</string> <string name="forum_invitations_title">Forum Davetleri</string>
<string name="forum_joined_toast">Foruma katıldı</string>
<string name="forum_declined_toast">Davetiye reddedildi</string>
<string name="shared_by_format">%s tarafından paylaşıldı</string> <string name="shared_by_format">%s tarafından paylaşıldı</string>
<string name="forum_invitation_already_sharing">Zaten paylaşılıyor</string> <string name="forum_invitation_already_sharing">Zaten paylaşılıyor</string>
<string name="forum_invitation_response_accepted_sent">%s tarafından yapılan forum davetini kabul ettiniz.</string> <string name="forum_invitation_response_accepted_sent">%s tarafından yapılan forum davetini kabul ettiniz.</string>
@@ -313,20 +205,15 @@
</plurals> </plurals>
<string name="nobody">Hiç kimse</string> <string name="nobody">Hiç kimse</string>
<!--Blogs--> <!--Blogs-->
<string name="blogs_other_blog_empty_state">Gösterilecek posta yok</string>
<string name="read_more">daha fazlasını oku</string> <string name="read_more">daha fazlasını oku</string>
<string name="blogs_write_blog_post">Blog Gönderisi Yaz</string> <string name="blogs_write_blog_post">Blog Gönderisi Yaz</string>
<string name="blogs_write_blog_post_body_hint">Blog gönderinizi yazın</string>
<string name="blogs_publish_blog_post">Yayınla</string> <string name="blogs_publish_blog_post">Yayınla</string>
<string name="blogs_blog_post_created">Blog Gönderisi Oluşturuldu</string> <string name="blogs_blog_post_created">Blog Gönderisi Oluşturuldu</string>
<string name="blogs_blog_post_received">Yeni Blog Gönderisi Alındı</string> <string name="blogs_blog_post_received">Yeni Blog Gönderisi Alındı</string>
<string name="blogs_blog_post_scroll_to">Kaydırma</string> <string name="blogs_blog_post_scroll_to">Kaydırma</string>
<string name="blogs_feed_empty_state">Gösterilecek posta yok</string>
<string name="blogs_remove_blog">Blog\'u sil</string> <string name="blogs_remove_blog">Blog\'u sil</string>
<string name="blogs_remove_blog_ok">Tuşu Sil</string> <string name="blogs_remove_blog_ok">Tuşu Sil</string>
<string name="blogs_blog_removed">Blog kaldırıldı</string> <string name="blogs_reblog_button">Tekrar Blogla</string>
<string name="blogs_reblog_comment_hint">Bir yorum ekle (isteğe bağlı)</string>
<string name="blogs_reblog_button">Yeniden blogda yayınla</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<string name="blogs_sharing_share">Blog\'u Paylaş</string> <string name="blogs_sharing_share">Blog\'u Paylaş</string>
<string name="blogs_sharing_error">Blog\'u paylaşırken bir hata meydana geldi.</string> <string name="blogs_sharing_error">Blog\'u paylaşırken bir hata meydana geldi.</string>
@@ -336,11 +223,7 @@
<string name="blogs_sharing_response_declined_sent">%s kişisinden gelen blog davetini reddettiniz.</string> <string name="blogs_sharing_response_declined_sent">%s kişisinden gelen blog davetini reddettiniz.</string>
<string name="blogs_sharing_response_accepted_received">%s blog davetini kabul etti.</string> <string name="blogs_sharing_response_accepted_received">%s blog davetini kabul etti.</string>
<string name="blogs_sharing_response_declined_received">%s blog davetini reddetti.</string> <string name="blogs_sharing_response_declined_received">%s blog davetini reddetti.</string>
<string name="blogs_sharing_invitation_received">%1$s sizinle %2$s blogunu paylaştı.</string>
<string name="blogs_sharing_invitation_sent">%1$s blogunu %2$s ile paylaştınız.</string>
<string name="blogs_sharing_invitations_title">Blog Davetleri</string> <string name="blogs_sharing_invitations_title">Blog Davetleri</string>
<string name="blogs_sharing_joined_toast">Bloga abone olundu</string>
<string name="blogs_sharing_declined_toast">Davetiye reddedildi</string>
<string name="sharing_status_blog">Bir blog\'a abone olan herkes, blog\'u kişileriyle paylaşabilir. Bu blog\'u şu kişilerle paylaşıyorsunuz. Göremediğiniz diğer aboneler de olabilir.</string> <string name="sharing_status_blog">Bir blog\'a abone olan herkes, blog\'u kişileriyle paylaşabilir. Bu blog\'u şu kişilerle paylaşıyorsunuz. Göremediğiniz diğer aboneler de olabilir.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">RSS kaynaklarını içeri aktar</string> <string name="blogs_rss_feeds_import">RSS kaynaklarını içeri aktar</string>
@@ -356,48 +239,26 @@
<string name="blogs_rss_feeds_manage_delete_error">Besleme silinemedi!</string> <string name="blogs_rss_feeds_manage_delete_error">Besleme silinemedi!</string>
<string name="blogs_rss_feeds_manage_error">Beslemeleriniz yüklenirken bir hata meydana geldi. Lütfen daha sonra tekrar deneyin.</string> <string name="blogs_rss_feeds_manage_error">Beslemeleriniz yüklenirken bir hata meydana geldi. Lütfen daha sonra tekrar deneyin.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_changed">Briar\'ı yeniden başlattığınızda bu ayar geçerli olacaktır. Lütfen çıkın ve Briar\'ı yeniden başlatın.</string>
<string name="pref_language_default">Sistem öntanımlısı</string>
<string name="display_settings_title">Görüntüle</string> <string name="display_settings_title">Görüntüle</string>
<string name="pref_theme_title">Tema</string>
<string name="pref_theme_light">ık</string>
<string name="pref_theme_dark">Koyu</string>
<string name="pref_theme_auto">Otomatik (Gündüz)</string>
<string name="pref_theme_system">Sistem öntanımlısı</string>
<!--Settings Network--> <!--Settings Network-->
<string name="network_settings_title">Ağlar</string> <string name="network_settings_title">Ağlar</string>
<string name="bluetooth_setting">Bluetooth ile Bağlan</string> <string name="bluetooth_setting">Bluetooth ile Bağlan</string>
<string name="bluetooth_setting_enabled">Yakınlardaki her kişi</string> <string name="bluetooth_setting_enabled">Yakınlardaki her kişi</string>
<string name="bluetooth_setting_disabled">Yalnızca kişi eklerken</string> <string name="bluetooth_setting_disabled">Yalnızca kişi eklerken</string>
<string name="tor_network_setting">İnternet üzerinden Bağlan (Tor)</string>
<string name="tor_network_setting_automatic">Konuma göre otomatik</string>
<string name="tor_network_setting_without_bridges">Tor\'u köprüsüz kullanın</string>
<string name="tor_network_setting_with_bridges">Tor\'u köprü ile kullanın</string>
<string name="tor_network_setting_never">Bağlanma!</string>
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)--> <!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)-->
<string name="tor_network_setting_summary">Otomatik: %1$s (%2$siçinde)</string>
<string name="tor_mobile_data_title">Mobil veri kullan</string>
<string name="tor_only_when_charging_title">Sadece şarj ederken internet (Tor) üzerinden bağlan</string>
<string name="tor_only_when_charging_summary">Aygıt pilde çalışırken internet bağlantısını devre dışı bırak</string>
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="security_settings_title">Güvenlik</string> <string name="security_settings_title">Güvenlik</string>
<string name="pref_lock_title">Uygulama kilidi</string>
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour--> <!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_1">1 dakika</string> <string name="pref_lock_timeout_1">1 dakika</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_5">5 dakika</string> <string name="pref_lock_timeout_5">5 dakika</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_15">15 dakika</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_30">30 dakika</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_60">1 saat</string> <string name="pref_lock_timeout_60">1 saat</string>
<string name="pref_lock_timeout_never">Asla</string> <string name="pref_lock_timeout_never">Asla</string>
<string name="pref_lock_timeout_never_summary">Briar\'ı asla otomatik olarak kilitleme</string> <string name="change_password">Parola değiştir</string>
<string name="change_password">Parolayı değiştir</string>
<string name="current_password">Şimdiki parola</string>
<string name="choose_new_password">Yeni parola</string>
<string name="confirm_new_password">Yeni parolayı onaylayın</string> <string name="confirm_new_password">Yeni parolayı onaylayın</string>
<string name="password_changed">Parolanız başarıyla değişti.</string> <string name="password_changed">Parolanız başarıyla değişti.</string>
<string name="panic_setting">Panik buton ayarları</string> <string name="panic_setting">Panik buton ayarları</string>
@@ -409,7 +270,6 @@
<string name="panic_app_setting_none">Yok</string> <string name="panic_app_setting_none">Yok</string>
<string name="dialog_title_connect_panic_app">Panik Uygulaması Doğrulama</string> <string name="dialog_title_connect_panic_app">Panik Uygulaması Doğrulama</string>
<string name="dialog_message_connect_panic_app">%1$s kişisinin yıkıcı panik düğmesi eylemlerini tetiklemesine izin vermek istediğinizden emin misiniz?</string> <string name="dialog_message_connect_panic_app">%1$s kişisinin yıkıcı panik düğmesi eylemlerini tetiklemesine izin vermek istediğinizden emin misiniz?</string>
<string name="panic_setting_destructive_action">Yıkıcı Eylemler</string>
<string name="panic_setting_signout_title">Çıkış Yap</string> <string name="panic_setting_signout_title">Çıkış Yap</string>
<string name="panic_setting_signout_summary">Panik Butonuna basıldığında Briar\'dan çıkış yap</string> <string name="panic_setting_signout_summary">Panik Butonuna basıldığında Briar\'dan çıkış yap</string>
<string name="purge_setting_title">Hesabı Sil</string> <string name="purge_setting_title">Hesabı Sil</string>
@@ -418,31 +278,24 @@
<string name="uninstall_setting_summary">Bu, bir panik olayında manuel onay gerektirir</string> <string name="uninstall_setting_summary">Bu, bir panik olayında manuel onay gerektirir</string>
<!--Settings Notifications--> <!--Settings Notifications-->
<string name="notification_settings_title">Bildirimler</string> <string name="notification_settings_title">Bildirimler</string>
<string name="notify_private_messages_setting_title">Özel İletiler</string> <string name="notify_private_messages_setting_title">Özel Mesajlar</string>
<string name="notify_private_messages_setting_summary">Özel iletiler için uyarıları göster</string> <string name="notify_private_messages_setting_summary">Özel mesajlar için uyarıları göster</string>
<string name="notify_private_messages_setting_summary_26">Özel iletiler için uyarıları yapılandır</string>
<string name="notify_group_messages_setting_title">Grup iletileri</string>
<string name="notify_group_messages_setting_summary">Grup mesajları için uyarıları göster</string> <string name="notify_group_messages_setting_summary">Grup mesajları için uyarıları göster</string>
<string name="notify_group_messages_setting_summary_26">Grup iletileri için uyarıları yapılandır</string>
<string name="notify_forum_posts_setting_title">Forum gönderileri</string>
<string name="notify_forum_posts_setting_summary">Forum gönderileri için uyarıları göster</string> <string name="notify_forum_posts_setting_summary">Forum gönderileri için uyarıları göster</string>
<string name="notify_forum_posts_setting_summary_26">Forum gönderileri için uyarıları yapılandır</string>
<string name="notify_blog_posts_setting_title">Blog gönderileri</string>
<string name="notify_blog_posts_setting_summary">Blog gönderileri için uyarıları göster</string> <string name="notify_blog_posts_setting_summary">Blog gönderileri için uyarıları göster</string>
<string name="notify_blog_posts_setting_summary_26">Blog gönderileri için uyarıları yapılandır</string>
<string name="notify_vibration_setting">Titretişim</string> <string name="notify_vibration_setting">Titretişim</string>
<string name="notify_lock_screen_setting_title">Ekranı Kilitle</string>
<string name="notify_sound_setting">Ses</string> <string name="notify_sound_setting">Ses</string>
<string name="notify_sound_setting_default">Varsayılan zil sesi</string> <string name="notify_sound_setting_default">Varsayılan zil sesi</string>
<string name="notify_sound_setting_disabled">Yok</string> <string name="notify_sound_setting_disabled">Yok</string>
<string name="choose_ringtone_title">Zil sesi seçin</string> <string name="choose_ringtone_title">Zil sesi seçin</string>
<string name="cannot_load_ringtone">Zil sesi yüklenemiyor</string>
<!--Settings Feedback--> <!--Settings Feedback-->
<string name="feedback_settings_title">Geri bildirim</string> <string name="feedback_settings_title">Geri bildirim</string>
<string name="send_feedback">Geri bildirim gönder</string> <string name="send_feedback">Geri bildirim gönder</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Uyarı Bağlantısı</string> <string name="link_warning_title">Uyarı Bağlantısı</string>
<string name="link_warning_intro">Aşağıdaki bağlantıyı harici bir uygulamayla açmak üzeresiniz.</string> <string name="link_warning_intro">Aşağıdaki bağlantıyı harici bir uygulamayla açmak üzeresiniz.</string>
<string name="link_warning_text">Bu sizi tanımlamak için kullanılabilir. Size bu bağlantıyı gönderen kişiye güvenip güvenmediğinizi düşünün ve Tor Tarayıcı ile açın.</string> <string name="link_warning_text">Bu kimliğinizi ele geçirmek için kullanılabilir. Bu bağlantıyı gönderen kişiye güvenip güvenmediğinize karar verin ve Orfox ile açmayı deneyin.</string>
<string name="link_warning_open_link">Bağlantı</string> <string name="link_warning_open_link">Bağlantı</string>
<!--Crash Reporter--> <!--Crash Reporter-->
<string name="crash_report_title">Briar Çökme Raporu</string> <string name="crash_report_title">Briar Çökme Raporu</string>
@@ -463,33 +316,6 @@
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Briar\'dan çıkılıyor...</string> <string name="progress_title_logout">Briar\'dan çıkılıyor...</string>
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Ekran bindirme algılandı</string>
<string name="screen_filter_allow">Bu uygulamaların üstte çizim yapmasına izin ver</string>
<!--Permission Requests--> <!--Permission Requests-->
<string name="permission_camera_title">Kamera izinleri</string>
<string name="permission_camera_request_body">QR kodunu taramak için Briar\'ın kameraya erişmesi gerekiyor.</string>
<string name="permission_location_title">Konum izinleri</string>
<string name="permission_camera_location_title">Kamera ve konum</string>
<string name="qr_code">QR kodu</string>
<string name="show_qr_code_fullscreen">QR kodu tam ekran göster</string>
<!--App Locking--> <!--App Locking-->
<string name="lock_unlock">Briar\'ın Kilidini Aç</string>
<string name="lock_unlock_verbose">Briar\'ın kilidini açmak için aygıtınızın PİN kodunu, parolasını veya desenini girin</string>
<string name="lock_unlock_fingerprint_description">Devam etmek için, parmak izi algılayıcısına kayıtlı parmağınızla dokunun</string>
<string name="lock_unlock_password">Parola Kullanın</string>
<string name="lock_is_locked">Briar kilitli</string>
<string name="lock_tap_to_unlock">Kilitlemek için dokunun</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">Alice</string>
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_bob">Bob</string>
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_carol">Carol</string>
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
<string name="screenshot_message_1">Selam Bob!</string>
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
<string name="screenshot_message_2">Selam Alice! Bana Briar\'ı anlattığın için teşekkürler</string>
<!--This is a message to be used in screenshots.-->
<string name="screenshot_message_3">Sorun değil, umarım beğenirsin 😀</string>
</resources> </resources>

View File

@@ -4,11 +4,13 @@
<item>@string/tor_network_setting_automatic</item> <item>@string/tor_network_setting_automatic</item>
<item>@string/tor_network_setting_without_bridges</item> <item>@string/tor_network_setting_without_bridges</item>
<item>@string/tor_network_setting_with_bridges</item> <item>@string/tor_network_setting_with_bridges</item>
<item>@string/tor_network_setting_never</item>
</string-array> </string-array>
<string-array name="tor_network_setting_values"> <string-array name="tor_network_setting_values">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item>
</string-array> </string-array>
<string-array name="pref_language_values"> <string-array name="pref_language_values">

View File

@@ -567,13 +567,6 @@
<string name="lock_is_locked">Briar is locked</string> <string name="lock_is_locked">Briar is locked</string>
<string name="lock_tap_to_unlock">Tap to unlock</string> <string name="lock_tap_to_unlock">Tap to unlock</string>
<!-- Overriding Tor settings -->
<string name="tor_override_settings_title">Change Settings</string>
<string name="tor_override_settings_body">Turning on Tor will change the following settings:\n\n%1$s</string>
<string name="tor_override_mobile_data_setting">Don\'t use mobile data</string>
<string name="tor_override_network_setting">Don\'t connect to Internet (Tor) in %1$s</string>
<string name="tor_override_settings_confirm">Change</string>
<!-- Screenshots --> <!-- Screenshots -->
<!-- This is a name to be used in screenshots. Feel free to change it to a local name. --> <!-- This is a name to be used in screenshots. Feel free to change it to a local name. -->

View File

@@ -36,8 +36,8 @@ dependencyVerification {
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8', 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
'androidx.preference:preference:1.1.0:preference-1.1.0.aar:6cf1a099b03b3254041b04701841865b2708c0b546b9036c8b0dada0aa59de57', 'androidx.preference:preference:1.1.0:preference-1.1.0.aar:6cf1a099b03b3254041b04701841865b2708c0b546b9036c8b0dada0aa59de57',
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd', 'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
'androidx.recyclerview:recyclerview-selection:1.1.0-rc01:recyclerview-selection-1.1.0-rc01.aar:8a1c0e75430e528ac325554a0be05aba4c52ac05fbbc5882187fb61271d89e8f', 'androidx.recyclerview:recyclerview-selection:1.0.0:recyclerview-selection-1.0.0.aar:db3db72af8cfcd701ab6ed7a080327a2e993e3a429f5efb8f0c108bff38c4922',
'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f', 'androidx.recyclerview:recyclerview:1.1.0-beta04:recyclerview-1.1.0-beta04.aar:c3c8310eb058a660a845cf814a54f56e6f448b7855f9ccea2a5ad18189f57f69',
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83', 'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
'androidx.test.espresso:espresso-contrib:3.2.0:espresso-contrib-3.2.0.aar:9e43811e5845e84f2964f0032fd50cd11dca3dc8d3b0703626dd12d50433bb35', 'androidx.test.espresso:espresso-contrib:3.2.0:espresso-contrib-3.2.0.aar:9e43811e5845e84f2964f0032fd50cd11dca3dc8d3b0703626dd12d50433bb35',
'androidx.test.espresso:espresso-core:3.2.0:espresso-core-3.2.0.aar:beb4712c2520c1da30ac1f25506871f16ea5b83ee686ece5a258769df1a01e15', 'androidx.test.espresso:espresso-core:3.2.0:espresso-core-3.2.0.aar:beb4712c2520c1da30ac1f25506871f16ea5b83ee686ece5a258769df1a01e15',

View File

@@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe @ThreadSafe
@@ -20,16 +19,10 @@ import javax.annotation.concurrent.ThreadSafe;
public class MessageTreeImpl<T extends MessageTree.MessageNode> public class MessageTreeImpl<T extends MessageTree.MessageNode>
implements MessageTree<T> { implements MessageTree<T> {
@GuardedBy("this")
private final Map<MessageId, List<T>> nodeMap = new HashMap<>(); private final Map<MessageId, List<T>> nodeMap = new HashMap<>();
@GuardedBy("this")
private final List<T> roots = new ArrayList<>(); private final List<T> roots = new ArrayList<>();
@GuardedBy("this")
private final List<List<T>> unsortedLists = new ArrayList<>(); private final List<List<T>> unsortedLists = new ArrayList<>();
@SuppressWarnings("UseCompareMethod")
private Comparator<T> comparator = (o1, o2) -> private Comparator<T> comparator = (o1, o2) ->
Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp()); Long.valueOf(o1.getTimestamp()).compareTo(o2.getTimestamp());
@@ -57,13 +50,11 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
add(Collections.singletonList(node)); add(Collections.singletonList(node));
} }
@GuardedBy("this")
private void markAsUnsorted(List<T> list) { private void markAsUnsorted(List<T> list) {
if (!unsortedLists.contains(list)) if (!unsortedLists.contains(list))
unsortedLists.add(list); unsortedLists.add(list);
} }
@GuardedBy("this")
private void parseNode(T node) { private void parseNode(T node) {
if (node.getParentId() == null) { if (node.getParentId() == null) {
roots.add(node); roots.add(node);
@@ -76,7 +67,6 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
} }
} }
@GuardedBy("this")
private void sortUnsorted() { private void sortUnsorted() {
for (List<T> list : unsortedLists) { for (List<T> list : unsortedLists) {
Collections.sort(list, comparator); Collections.sort(list, comparator);
@@ -84,7 +74,6 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
unsortedLists.clear(); unsortedLists.clear();
} }
@GuardedBy("this")
private void traverse(List<T> list, T node, int level) { private void traverse(List<T> list, T node, int level) {
list.add(node); list.add(node);
List<T> children = nodeMap.get(node.getId()); List<T> children = nodeMap.get(node.getId());
@@ -114,7 +103,7 @@ public class MessageTreeImpl<T extends MessageTree.MessageNode>
} }
@Override @Override
public synchronized boolean contains(MessageId m) { public boolean contains(MessageId m) {
return nodeMap.containsKey(m); return nodeMap.containsKey(m);
} }
} }

View File

@@ -71,10 +71,7 @@ class SessionParserImpl implements SessionParser {
@Override @Override
public CreatorSession parseCreatorSession(GroupId contactGroupId, public CreatorSession parseCreatorSession(GroupId contactGroupId,
BdfDictionary d) throws FormatException { BdfDictionary d) throws FormatException {
if (getRole(d) != CREATOR) { if (getRole(d) != CREATOR) throw new IllegalArgumentException();
throw new IllegalArgumentException(
"Expected creator, but found " + getRole(d).name());
}
return new CreatorSession(contactGroupId, getPrivateGroupId(d), return new CreatorSession(contactGroupId, getPrivateGroupId(d),
getLastLocalMessageId(d), getLastRemoteMessageId(d), getLastLocalMessageId(d), getLastRemoteMessageId(d),
getLocalTimestamp(d), getInviteTimestamp(d), getLocalTimestamp(d), getInviteTimestamp(d),

View File

@@ -230,8 +230,6 @@ public class TestDataCreatorImpl implements TestDataCreator {
sb.append(getRandomLanAddress()); sb.append(getRandomLanAddress());
} }
lan.put(LanTcpConstants.PROP_IP_PORTS, sb.toString()); lan.put(LanTcpConstants.PROP_IP_PORTS, sb.toString());
String port = String.valueOf(getRandomPortNumber());
lan.put(LanTcpConstants.PROP_PORT, port);
props.put(LanTcpConstants.ID, lan); props.put(LanTcpConstants.ID, lan);
// Tor // Tor
@@ -268,21 +266,18 @@ public class TestDataCreatorImpl implements TestDataCreator {
sb.append("10."); sb.append("10.");
sb.append(random.nextInt(2)).append('.'); sb.append(random.nextInt(2)).append('.');
sb.append(random.nextInt(2)).append('.'); sb.append(random.nextInt(2)).append('.');
sb.append(random.nextInt(255)); sb.append(random.nextInt(256));
} else { } else {
sb.append("192.168."); sb.append("192.168.");
sb.append(random.nextInt(2)).append('.'); sb.append(random.nextInt(2)).append('.');
sb.append(random.nextInt(255)); sb.append(random.nextInt(256));
} }
// port // port
sb.append(':').append(getRandomPortNumber()); sb.append(":");
sb.append(1024 + random.nextInt(50000));
return sb.toString(); return sb.toString();
} }
private int getRandomPortNumber() {
return 32768 + random.nextInt(32768);
}
private String getRandomTorAddress() { private String getRandomTorAddress() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
// address // address