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 {
minSdkVersion 16
targetSdkVersion 28
versionCode 10207
versionName "1.2.7"
versionCode 10205
versionName "1.2.5"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
@@ -21,7 +20,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.IoUtils.deleteFileOrDir;
import static org.briarproject.bramble.util.LogUtils.logFileOrDir;
@@ -32,12 +30,6 @@ class AndroidAccountManager extends AccountManagerImpl
private static final Logger LOG =
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;
private final SharedPreferences prefs;
@@ -89,7 +81,7 @@ class AndroidAccountManager extends AccountManagerImpl
if (!prefs.edit().clear().commit())
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<>();
File dataDir = getDataDir();
@Nullable
@@ -98,12 +90,14 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not list files in app data dir");
} else {
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(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) {
@@ -115,16 +109,12 @@ class AndroidAccountManager extends AccountManagerImpl
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) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting " + file.getAbsolutePath());
}
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() {

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.net.ConnectivityManager.CONNECTIVITY_ACTION;
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.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
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_OFF);
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);
appContext.registerReceiver(networkStateReceiver, filter);
}
@Override
@@ -137,8 +136,7 @@ class AndroidNetworkManager implements NetworkManager, Service {
}
private boolean isApEvent(@Nullable String action) {
return WIFI_AP_STATE_CHANGED_ACTION.equals(action) ||
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action);
return WIFI_AP_STATE_CHANGED_ACTION.equals(action);
}
}
}

View File

@@ -8,34 +8,24 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
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.ParametersNotNullByDefault;
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.PluginException;
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.Clock;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
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_ON;
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_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.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
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.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -84,9 +61,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG =
getLogger(AndroidBluetoothPlugin.class.getName());
private static final int MIN_DEVICE_DISCOVERY_MS = 2_000;
private static final int MAX_DEVICE_DISCOVERY_MS = 30_000;
private static final int MAX_SERVICE_DISCOVERY_MS = 15_000;
private static final int MAX_DISCOVERY_MS = 10_000;
private final AndroidExecutor androidExecutor;
private final Context appContext;
@@ -160,31 +135,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override
void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) {
cancelDiscoverability();
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
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
void setEnabledByUs() {
wasEnabledByUs = true;
@@ -250,8 +206,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Nullable
DuplexTransportConnection discoverAndConnect(String uuid) {
if (adapter == null) return null;
for (BluetoothDevice d : discoverDevices()) {
String address = d.getAddress();
for (String address : discoverDevices()) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
@@ -267,184 +222,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return null;
}
@Override
public boolean supportsDiscovery() {
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");
private Collection<String> discoverDevices() {
List<String> addresses = new ArrayList<>();
BlockingQueue<Intent> intents = new LinkedBlockingQueue<>();
QueueingReceiver receiver = new QueueingReceiver(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);
DiscoveryReceiver receiver = new DiscoveryReceiver(intents);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_DISCOVERY_STARTED);
filter.addAction(ACTION_DISCOVERY_FINISHED);
@@ -452,9 +233,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
appContext.registerReceiver(receiver, filter);
try {
if (adapter.startDiscovery()) {
long start = clock.currentTimeMillis();
long end = start + MAX_DEVICE_DISCOVERY_MS;
long now = start;
long now = clock.currentTimeMillis();
long end = now + MAX_DISCOVERY_MS;
while (now < end) {
Intent i = intents.poll(end - now, MILLISECONDS);
if (i == null) break;
@@ -463,27 +243,14 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
LOG.info("Discovery started");
} else if (ACTION_DISCOVERY_FINISHED.equals(action)) {
LOG.info("Discovery finished");
now = clock.currentTimeMillis();
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;
}
break;
} else if (ACTION_FOUND.equals(action)) {
BluetoothDevice d = requireNonNull(
i.getParcelableExtra(EXTRA_DEVICE));
// Ignore Bluetooth LE devices
if (SDK_INT < 18 || d.getType() != DEVICE_TYPE_LE) {
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered "
+ scrubMacAddress(d.getAddress()));
}
if (!devices.contains(d)) devices.add(d);
}
BluetoothDevice d = i.getParcelableExtra(EXTRA_DEVICE);
String address = d.getAddress();
if (LOG.isLoggable(INFO))
LOG.info("Discovered " + scrubMacAddress(address));
if (!addresses.contains(address))
addresses.add(address);
}
now = clock.currentTimeMillis();
}
@@ -498,9 +265,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
adapter.cancelDiscovery();
appContext.unregisterReceiver(receiver);
}
// Shuffle the devices so we don't always try the same one first
shuffle(devices);
return devices;
// Shuffle the addresses so we don't always try the same one first
Collections.shuffle(addresses);
return addresses;
}
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 QueueingReceiver(BlockingQueue<Intent> intents) {
private DiscoveryReceiver(BlockingQueue<Intent> intents) {
this.intents = intents;
}

View File

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

View File

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

View File

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

View File

@@ -8,4 +8,7 @@ public interface BluetoothConstants {
String PROP_ADDRESS = "address";
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");
// Transport properties (shared with contacts)
// a transport property (shared with contacts)
String PROP_IP_PORTS = "ipPorts";
String PROP_PORT = "port";
// A local setting
// a local setting
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.settings.SettingsManager;
import java.util.List;
import java.util.Collection;
@NotNullByDefault
public interface Plugin {
@@ -13,13 +13,8 @@ public interface Plugin {
enum State {
/**
* The plugin has not finished starting or has been stopped.
*/
STARTING_STOPPING,
/**
* The plugin is disabled by settings. Use {@link #getReasonsDisabled()}
* to find out which settings are responsible.
* The plugin has not been started, has been stopped, or is disabled by
* settings.
*/
DISABLED,
@@ -47,7 +42,14 @@ public interface Plugin {
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.
*/
int REASON_USER = 1;
@@ -83,13 +85,14 @@ public interface Plugin {
State getState();
/**
* Returns a set of flags indicating why the plugin is
* {@link State#DISABLED disabled}, or 0 if the plugin is not disabled.
* Returns an integer code indicating why the plugin is
* {@link State#DISABLED disabled}, or -1 if the plugin is not disabled.
* <p>
* The flags used are plugin-specific, except the generic flag
* {@link #REASON_USER}, which may be used by any plugin.
* The codes used are plugin-specific, except the generic codes
* {@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
@@ -106,5 +109,6 @@ public interface Plugin {
* Attempts to create connections using the given transport properties,
* 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_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
// Reason codes returned by Plugin#getReasonDisabled()
int REASON_BATTERY = 2;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_MOBILE_DATA = 4;
/**
* Reason flag returned by {@link Plugin#getReasonsDisabled()}.
*/
int REASON_COUNTRY_BLOCKED = 8;
int REASON_MOBILE_DATA = 3;
int REASON_COUNTRY_BLOCKED = 4;
}

View File

@@ -1,18 +1,14 @@
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.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.properties.TransportProperties;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.briarproject.bramble.api.rendezvous.RendezvousEndpoint;
import java.util.List;
import javax.annotation.Nullable;
/**
@@ -62,16 +58,4 @@ public interface DuplexPlugin extends Plugin {
@Nullable
RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k,
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 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.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.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -277,7 +277,7 @@ class PluginManagerImpl implements PluginManager, Service {
private final TransportId id;
private final AtomicReference<State> state =
new AtomicReference<>(STARTING_STOPPING);
new AtomicReference<>(DISABLED);
private Callback(TransportId 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.ConnectionManager;
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.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
@@ -33,7 +32,6 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
@@ -212,20 +210,18 @@ class PollerImpl implements Poller, EventListener {
@IoExecutor
private void poll(Plugin p) {
TransportId t = p.getId();
if (LOG.isLoggable(INFO)) LOG.info("Polling " + t);
if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t);
try {
Map<ContactId, TransportProperties> remote =
transportPropertyManager.getRemoteProperties(t);
Collection<ContactId> connected =
connectionRegistry.getConnectedContacts(t);
List<Pair<TransportProperties, ConnectionHandler>> properties =
new ArrayList<>();
Collection<Pair<TransportProperties, ConnectionHandler>>
properties = new ArrayList<>();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
if (!connected.contains(c)) {
ConnHandler handler = new ConnHandler(c, t);
properties.add(new Pair<>(e.getValue(), handler));
}
if (!connected.contains(c))
properties.add(new Pair<>(e.getValue(), new Handler(c, t)));
}
if (!properties.isEmpty()) p.poll(properties);
} 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 final PollTask task;
@@ -296,23 +268,16 @@ class PollerImpl implements Poller, EventListener {
int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false);
// FIXME: Revert
if (plugin instanceof DuplexPlugin) {
DuplexPlugin d = (DuplexPlugin) plugin;
if (d.supportsDiscovery()) discover(d);
else poll(d);
} else {
poll(plugin);
}
poll(plugin);
}
}
private class ConnHandler implements ConnectionHandler {
private class Handler implements ConnectionHandler {
private final ContactId contactId;
private final TransportId transportId;
private ConnHandler(ContactId contactId, TransportId transportId) {
private Handler(ContactId contactId, TransportId transportId) {
this.contactId = contactId;
this.transportId = transportId;
}
@@ -335,27 +300,4 @@ class PollerImpl implements Poller, EventListener {
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.plugin.Backoff;
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.PluginException;
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.security.SecureRandom;
import java.util.List;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.Executor;
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.PROP_ADDRESS;
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.Plugin.State.ACTIVE;
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.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -160,15 +159,16 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
state.setStarted(enabledByUser);
try {
initialiseAdapter();
} catch (IOException e) {
state.setNoAdapter();
throw new PluginException(e);
}
updateProperties();
Settings settings = callback.getSettings();
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, false);
state.setStarted(enabledByUser);
if (enabledByUser) {
if (isAdapterEnabled()) bind();
else enableAdapter();
@@ -252,8 +252,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
public int getReasonDisabled() {
return state.getReasonDisabled();
}
@Override
@@ -267,8 +267,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
}
@Override
public void poll(
List<Pair<TransportProperties, ConnectionHandler>> properties) {
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
@@ -412,17 +412,6 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof EnableBluetoothEvent) {
@@ -488,6 +477,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@GuardedBy("this")
private boolean started = false,
stopped = false,
noAdapter = false,
enabledByUser = false;
@GuardedBy("this")
@@ -509,6 +499,11 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
return ss;
}
synchronized void setNoAdapter() {
noAdapter = true;
callback.pluginStateChanged(getState());
}
@Nullable
synchronized SS setEnabledByUser(boolean enabledByUser) {
this.enabledByUser = enabledByUser;
@@ -537,13 +532,14 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
if (!started || stopped || !enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
synchronized int getReasonDisabled() {
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.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
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.emptyList;
import static java.util.Collections.sort;
import static java.util.logging.Level.INFO;
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.PREF_LAN_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.IoUtils.tryToClose;
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 LanAddressComparator ADDRESS_COMPARATOR =
new LanAddressComparator();
private static final int MAX_ADDRESSES = 4;
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,
int maxLatency, int maxIdleTime, int connectionTimeout) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime,
connectionTimeout);
int maxLatency, int maxIdleTime) {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
}
@Override
@@ -87,83 +62,38 @@ class LanTcpPlugin extends TcpPlugin {
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
protected List<InetSocketAddress> getLocalSocketAddresses() {
// Use the same address and port as last time if available
TransportProperties p = callback.getLocalProperties();
int preferredPort = parsePortProperty(p.get(PROP_PORT));
String oldIpPorts = p.get(PROP_IP_PORTS);
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
List<InetSocketAddress> locals = new ArrayList<>();
List<InetSocketAddress> fallbacks = new ArrayList<>();
for (InetAddress local : getUsableLocalInetAddresses()) {
// If we've used this address before, try to use the same port
int port = preferredPort;
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local)) {
port = old.getPort();
break;
for (InetAddress local : getLocalIpAddresses()) {
if (isAcceptableAddress(local)) {
// If this is the old address, try to use the same port
for (InetSocketAddress old : olds) {
if (old.getAddress().equals(local))
locals.add(new InetSocketAddress(local, old.getPort()));
}
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;
}
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) {
if (isNullOrEmpty(ipPorts)) return emptyList();
String[] split = ipPorts.split(SEPARATOR);
List<InetSocketAddress> addresses = new ArrayList<>();
if (isNullOrEmpty(ipPorts)) return addresses;
for (String ipPort : ipPorts.split(SEPARATOR)) {
for (String ipPort : split) {
InetSocketAddress a = parseSocketAddress(ipPort);
if (a != null) addresses.add(a);
}
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
protected void setLocalSocketAddress(InetSocketAddress a) {
String ipPort = getIpPortString(a);
@@ -201,20 +131,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override
protected List<InetSocketAddress> getRemoteSocketAddresses(
TransportProperties p) {
String ipPorts = 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;
return parseSocketAddresses(p.get(PROP_IP_PORTS));
}
private boolean isAcceptableAddress(InetAddress a) {
@@ -227,33 +144,53 @@ class LanTcpPlugin extends TcpPlugin {
}
@Override
protected boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote) {
protected boolean isConnectable(InetSocketAddress remote) {
if (remote.getPort() == 0) return false;
if (!isAcceptableAddress(remote.getAddress())) return false;
// 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();
int prefixLength = local.getNetworkPrefixLength();
return areAddressesInSameNetwork(localIp, remoteIp, prefixLength);
return addressesAreOnSameLan(localIp, remoteIp);
}
// Package access for testing
static boolean areAddressesInSameNetwork(byte[] localIp, byte[] remoteIp,
int prefixLength) {
if (localIp.length != remoteIp.length) return false;
// Compare the first prefixLength bits of the addresses
for (int i = 0; i < prefixLength; i++) {
int byteIndex = i >> 3;
int bitIndex = i & 7; // 0 to 7
int mask = 128 >> bitIndex; // Select the bit at bitIndex
if ((localIp[byteIndex] & mask) != (remoteIp[byteIndex] & mask)) {
return false; // Addresses differ at bit i
}
}
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
// 10.0.0.0/8
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
// 172.16.0.0/12
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
// 192.168.0.0/16
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
// Unrecognised prefix - may be compatible
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
public boolean supportsKeyAgreement() {
return true;
@@ -293,11 +230,6 @@ class LanTcpPlugin extends TcpPlugin {
byte[] commitment, BdfList descriptor) {
ServerSocket ss = state.getServerSocket();
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;
try {
remote = parseSocketAddress(descriptor);
@@ -305,7 +237,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Invalid IP/port in key agreement descriptor");
return null;
}
if (!isConnectable(local, remote)) {
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
@@ -318,7 +250,7 @@ class LanTcpPlugin extends TcpPlugin {
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout);
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -367,4 +299,19 @@ class LanTcpPlugin extends TcpPlugin {
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
public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30_000; // 30 seconds
private static final int MAX_IDLE_TIME = 30_000; // 30 seconds
private static final int CONNECTION_TIMEOUT = 3_000; // 3 seconds
private static final int MIN_POLLING_INTERVAL = 60_000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 600_000; // 10 mins
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
@@ -51,8 +50,8 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(PluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback, MAX_LATENCY,
MAX_IDLE_TIME, CONNECTION_TIMEOUT);
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -2,15 +2,16 @@ package org.briarproject.bramble.plugin.tcp;
import org.briarproject.bramble.PoliteExecutor;
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.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
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.duplex.DuplexPlugin;
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.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
@@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
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.DISABLED;
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.LogUtils.logException;
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 Backoff backoff;
protected final PluginCallback callback;
protected final int maxLatency, maxIdleTime;
protected final int connectionTimeout, socketTimeout;
protected final int maxLatency, maxIdleTime, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false);
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.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote);
protected abstract boolean isConnectable(InetSocketAddress remote);
TcpPlugin(Executor ioExecutor, Backoff backoff, PluginCallback callback,
int maxLatency, int maxIdleTime, int connectionTimeout) {
int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.connectionTimeout = connectionTimeout;
if (maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
@@ -205,8 +202,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
public int getReasonDisabled() {
return state.getReasonDisabled();
}
@Override
@@ -220,8 +217,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
}
@Override
public void poll(
List<Pair<TransportProperties, ConnectionHandler>> properties) {
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
for (Pair<TransportProperties, ConnectionHandler> p : properties) {
@@ -243,18 +240,8 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
public DuplexTransportConnection createConnection(TransportProperties p) {
ServerSocket ss = state.getServerSocket();
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)) {
// Don't try to connect to our own address
if (!canConnectToOwnAddress() &&
remote.getAddress().equals(ss.getInetAddress())) {
continue;
}
if (!isConnectable(local, remote)) {
if (!isConnectable(remote)) {
if (LOG.isLoggable(INFO)) {
LOG.info(scrubSocketAddress(remote) +
" is not connectable from " +
@@ -267,7 +254,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
LOG.info("Connecting to " + scrubSocketAddress(remote));
Socket s = createSocket();
s.bind(new InetSocketAddress(ss.getInetAddress(), 0));
s.connect(remote, connectionTimeout);
s.connect(remote);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubSocketAddress(remote));
@@ -281,19 +268,6 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
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 {
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
public boolean supportsRendezvous() {
return false;
@@ -333,38 +323,14 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
throw new UnsupportedOperationException();
}
@Override
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() {
Collection<InetAddress> getLocalIpAddresses() {
try {
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
return ifaces == null ? emptyList() : list(ifaces);
Enumeration<NetworkInterface> ifaces = getNetworkInterfaces();
if (ifaces == null) return emptyList();
List<InetAddress> addrs = new ArrayList<>();
for (NetworkInterface iface : list(ifaces))
addrs.addAll(list(iface.getInetAddresses()));
return addrs;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return emptyList();
@@ -450,13 +416,13 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
}
synchronized State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!enabledByUser) return DISABLED;
if (!started || stopped || !enabledByUser) return DISABLED;
return serverSocket == null ? INACTIVE : ACTIVE;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? REASON_USER : 0;
synchronized int getReasonDisabled() {
if (!started || stopped) return REASON_STARTING_STOPPING;
return enabledByUser ? -1 : REASON_USER;
}
}
}

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ public interface CircumventionProvider {
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}.
*/
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.plugin.Backoff;
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.PluginException;
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.ENABLING;
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.ID;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
@@ -193,6 +191,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
state.setStarted();
if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory.");
@@ -280,7 +279,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} catch (IOException e) {
throw new PluginException(e);
}
state.setStarted();
state.setTorStarted();
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
@@ -535,8 +534,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public int getReasonsDisabled() {
return state.getReasonsDisabled();
public int getReasonDisabled() {
return state.getReasonDisabled();
}
@Override
@@ -550,8 +549,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
@Override
public void poll(
List<Pair<TransportProperties, ConnectionHandler>> properties) {
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
if (getState() != ACTIVE) return;
backoff.increment();
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
public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") &&
@@ -786,8 +774,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
String country = locationUtils.getCurrentCountry();
boolean blocked =
circumventionProvider.isTorProbablyBlocked(country);
boolean enabledByUser =
settings.getBoolean(PREF_PLUGIN_ENABLE, true);
boolean enabledByUser = settings.getBoolean(PREF_PLUGIN_ENABLE, true);
int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
@@ -803,58 +790,55 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.info("Charging: " + charging);
}
int reasonsDisabled = 0;
boolean enableNetwork = false, enableBridges = false;
boolean useMeek = false, enableConnectionPadding = false;
boolean disabledBySettings = false;
int reasonDisabled = REASON_STARTING_STOPPING;
if (!online) {
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 {
if (!enabledByUser) {
LOG.info("User has disabled Tor");
reasonsDisabled |= REASON_USER;
}
if (!charging && onlyWhenCharging) {
LOG.info("Configured not to use battery");
reasonsDisabled |= REASON_BATTERY;
}
if (!useMobile && !wifi) {
LOG.info("Configured not to use mobile data");
reasonsDisabled |= REASON_MOBILE_DATA;
}
if (automatic && blocked && !bridgesWork) {
LOG.info("Country is blocked");
reasonsDisabled |= REASON_COUNTRY_BLOCKED;
}
if (reasonsDisabled != 0) {
LOG.info("Disabling network due to settings");
LOG.info("Enabling network");
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
} else {
LOG.info("Using obfs4 bridges");
enableBridges = true;
}
} else {
LOG.info("Enabling network");
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
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");
}
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 {
if (enableNetwork) {
@@ -879,14 +863,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@GuardedBy("this")
private boolean started = false,
stopped = false,
torStarted = false,
networkInitialised = false,
networkEnabled = false,
bootstrapped = false,
circuitBuilt = false,
settingsChecked = false;
disabledBySettings = false;
@GuardedBy("this")
private int reasonsDisabled = 0;
private int reasonDisabled = REASON_STARTING_STOPPING;
@GuardedBy("this")
@Nullable
@@ -897,8 +882,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.pluginStateChanged(getState());
}
// Doesn't affect getState()
synchronized void setTorStarted() {
torStarted = true;
}
synchronized boolean isTorRunning() {
return started && !stopped;
return torStarted && !stopped;
}
@Nullable
@@ -929,9 +919,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.pluginStateChanged(getState());
}
synchronized void setReasonsDisabled(int reasonsDisabled) {
settingsChecked = true;
this.reasonsDisabled = reasonsDisabled;
synchronized void setDisabledBySettings(boolean disabledBySettings,
int reasonDisabled) {
this.disabledBySettings = disabledBySettings;
this.reasonDisabled = reasonDisabled;
callback.pluginStateChanged(getState());
}
@@ -948,17 +939,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
synchronized State getState() {
if (!started || stopped || !settingsChecked) {
return STARTING_STOPPING;
}
if (reasonsDisabled != 0) return DISABLED;
if (!started || stopped || disabledBySettings) return DISABLED;
if (!networkInitialised) return ENABLING;
if (!networkEnabled) return INACTIVE;
return bootstrapped && circuitBuilt ? ACTIVE : ENABLING;
}
synchronized int getReasonsDisabled() {
return getState() == DISABLED ? reasonsDisabled : 0;
synchronized int getReasonDisabled() {
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.singletonMap;
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.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -351,17 +351,14 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId);
will(returnValue(singletonMap(contactId, properties)));
oneOf(connectionRegistry).getConnectedContacts(transportId);
will(returnValue(emptyList()));
// Poll the plugin
oneOf(plugin).poll(with(listOf(pairOf(
equal(properties), any(ConnectionHandler.class)))));
oneOf(plugin).poll(with(collectionOf(
pairOf(equal(properties), any(ConnectionHandler.class)))));
}});
poller.eventOccurred(new TransportActiveEvent(transportId));
@@ -397,9 +394,6 @@ public class PollerImplTest extends BrambleMockTestCase {
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
will(returnValue(future));
// FIXME: Revert
oneOf(plugin).supportsDiscovery();
will(returnValue(false));
// Get the transport properties and connected contacts
oneOf(transportPropertyManager).getRemoteProperties(transportId);
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.TransportConnectionReader;
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.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@@ -22,6 +23,7 @@ import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Comparator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
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.TRANSPORT_ID_LAN;
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.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff();
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
public void testAreAddressesInSameNetwork() {
// Local and remote in 10.0.0.0/8
assertTrue(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255), 8));
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255), 9));
// Local and remote in 172.16.0.0/12
assertTrue(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255), 12));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255), 13));
// Local and remote in 192.168.0.0/16
assertTrue(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255), 17));
// Local and remote in 169.254.0.0/16
assertTrue(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(169, 254, 255, 255), 16));
assertFalse(areAddressesInSameNetwork(makeAddress(169, 254, 0, 0),
makeAddress(169, 254, 255, 255), 17));
// Local in 10.0.0.0/8, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0),
makeAddress(172, 31, 255, 255), 8));
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0),
makeAddress(192, 168, 255, 255), 8));
assertFalse(areAddressesInSameNetwork(makeAddress(10, 0, 0, 0),
makeAddress(169, 254, 255, 255), 8));
// Local in 172.16.0.0/12, remote in a different network
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0),
makeAddress(10, 255, 255, 255), 12));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0),
makeAddress(192, 168, 255, 255), 12));
assertFalse(areAddressesInSameNetwork(makeAddress(172, 16, 0, 0),
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));
public void testAddressesAreOnSameLan() {
Callback callback = new Callback();
LanTcpPlugin plugin = new LanTcpPlugin(ioExecutor, backoff, callback,
0, 0);
// Local and remote in 10.0.0.0/8 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(10, 255, 255, 255)));
// Local and remote in 172.16.0.0/12 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(172, 31, 255, 255)));
// Local and remote in 192.168.0.0/16 should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(192, 168, 255, 255)));
// Local and remote in 169.254.0.0/16 (link-local) should return true
assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
makeAddress(169, 254, 255, 255)));
// Local and remote in different recognised prefixes should return false
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(172, 31, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(192, 168, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(10, 255, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(192, 168, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(10, 255, 255, 255)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(172, 31, 255, 255)));
// Remote prefix unrecognised should return false
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(172, 16, 0, 0),
makeAddress(1, 2, 3, 4)));
assertFalse(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
makeAddress(1, 2, 3, 4)));
// Both prefixes unrecognised should return true (could be link-local)
assertTrue(plugin.addressesAreOnSameLan(makeAddress(1, 2, 3, 4),
makeAddress(5, 6, 7, 8)));
}
private byte[] makeAddress(int... parts) {
@@ -126,7 +95,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test
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();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -155,7 +130,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test
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();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -198,7 +179,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test
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();
assertTrue(callback.propertiesLatch.await(5, SECONDS));
KeyAgreementListener kal =
@@ -240,7 +227,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
@Test
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();
// The plugin should have bound a socket and stored the port number
assertTrue(callback.propertiesLatch.await(5, SECONDS));
@@ -285,12 +278,63 @@ public class LanTcpPluginTest extends BrambleTestCase {
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 {
for (NetworkInterface i : list(getNetworkInterfaces())) {
for (InetAddress a : list(i.getInetAddresses())) {
if (a instanceof Inet4Address) {
if (a instanceof Inet4Address)
return a.isLinkLocalAddress() || a.isSiteLocalAddress();
}
}
}
return false;
@@ -299,9 +343,7 @@ public class LanTcpPluginTest extends BrambleTestCase {
@NotNullByDefault
private static class Callback implements PluginCallback {
// Properties will be stored twice: the preferred port at startup,
// and the IP:port when the server socket is bound
private final CountDownLatch propertiesLatch = new CountDownLatch(2);
private final CountDownLatch propertiesLatch = new CountDownLatch(1);
private final CountDownLatch connectionsLatch = new CountDownLatch(1);
private final TransportProperties local = new TransportProperties();
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.rendezvous.RendezvousConstants.POLLING_INTERVAL_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.TestUtils.getAgreementPrivateKey;
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
@@ -196,7 +196,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(listOf(pairOf(
oneOf(plugin).poll(with(collectionOf(pairOf(
equal(transportProperties),
any(ConnectionHandler.class)))));
}});
@@ -248,7 +248,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(clock).currentTimeMillis();
will(returnValue(beforeExpiry));
oneOf(eventBus).broadcast(with(any(RendezvousPollEvent.class)));
oneOf(plugin).poll(with(listOf(pairOf(
oneOf(plugin).poll(with(collectionOf(pairOf(
equal(transportProperties),
any(ConnectionHandler.class)))));
}});

View File

@@ -5,24 +5,24 @@ import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import java.util.List;
import java.util.Collection;
import javax.annotation.Nullable;
@NotNullByDefault
public class ListMatcher<T> extends BaseMatcher<List<T>> {
public class CollectionMatcher<T> extends BaseMatcher<Collection<T>> {
private final Matcher<T> elementMatcher;
public ListMatcher(Matcher<T> elementMatcher) {
public CollectionMatcher(Matcher<T> elementMatcher) {
this.elementMatcher = elementMatcher;
}
@Override
public boolean matches(@Nullable Object item) {
if (!(item instanceof List)) return false;
List list = (List) item;
for (Object element : list) {
if (!(item instanceof Collection)) return false;
Collection collection = (Collection) item;
for (Object element : collection) {
if (!elementMatcher.matches(element)) return false;
}
return true;
@@ -33,7 +33,7 @@ public class ListMatcher<T> extends BaseMatcher<List<T>> {
description.appendText("matches a collection");
}
public static <T> ListMatcher<T> listOf(Matcher<T> t) {
return new ListMatcher<>(t);
public static <T> CollectionMatcher<T> collectionOf(Matcher<T> 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.ParametersNotNullByDefault;
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.PluginException;
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.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
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.Logger.getLogger;
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.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.StringUtils.isNullOrEmpty;
@@ -122,8 +121,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
@Override
public int getReasonsDisabled() {
return 0;
public int getReasonDisabled() {
return getState() == DISABLED ? REASON_STARTING_STOPPING : -1;
}
@Override
@@ -137,8 +136,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
@Override
public void poll(
List<Pair<TransportProperties, ConnectionHandler>> properties) {
public void poll(Collection<Pair<TransportProperties, ConnectionHandler>>
properties) {
throw new UnsupportedOperationException();
}
@@ -214,17 +213,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsDiscovery() {
return false;
}
@Override
public void discoverPeers(
List<Pair<TransportProperties, DiscoveryHandler>> properties) {
throw new UnsupportedOperationException();
}
@Override
public void incomingCallConnected() {
LOG.info("Incoming call connected");
@@ -292,7 +280,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
private State getState() {
if (!started || stopped) return STARTING_STOPPING;
if (!started || stopped) return DISABLED;
if (failed) return INACTIVE;
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 {
minSdkVersion 16
targetSdkVersion 28
versionCode 10207
versionName "1.2.7"
versionCode 10205
versionName "1.2.5"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${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.constraintlayout:constraintlayout:1.1.3'
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 '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 androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@NotNullByDefault
@@ -17,10 +18,7 @@ public interface BlogController extends BaseController {
void setGroupId(GroupId g);
@UiThread
void setBlogSharingListener(BlogSharingListener listener);
@UiThread
void unsetBlogSharingListener(BlogSharingListener listener);
void setBlogSharingListener(@Nullable BlogSharingListener listener);
void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListen
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
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.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -87,12 +87,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
private ContactListAdapter adapter;
private BriarRecyclerView list;
/**
* The Snackbar is non-null when shown and null otherwise.
* Use {@link #showSnackBar()} and {@link #dismissSnackBar()} to interact.
*/
@Nullable
private Snackbar snackbar = null;
private Snackbar snackbar;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -168,6 +163,13 @@ public class ContactListFragment extends BaseFragment implements EventListener,
list.setEmptyText(getString(R.string.no_contacts));
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;
}
@@ -201,9 +203,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(this::dismissSnackBar);
runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else {
runOnUiThreadUnlessDestroyed(this::showSnackBar);
runOnUiThreadUnlessDestroyed(() -> snackbar.show());
}
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -218,7 +220,6 @@ public class ContactListFragment extends BaseFragment implements EventListener,
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();
dismissSnackBar();
}
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.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.view.LayoutInflater;
@@ -37,10 +38,12 @@ import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
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 org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -196,7 +199,9 @@ public class NicknameFragment extends BaseFragment {
private void showWarningDialog(String name1, String name2) {
Context ctx = requireContext();
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.setMessage(
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 =
contactManager.getPendingContacts();
List<PendingContactItem> items = new ArrayList<>(pairs.size());
boolean online = pairs.isEmpty();
boolean online = items.isEmpty();
for (Pair<PendingContact, PendingContactState> pair : pairs) {
PendingContact p = pair.getFirst();
PendingContactState state = pair.getSecond();

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.conversation;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.transition.Fade;
import android.transition.Transition;
@@ -34,6 +35,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog.Builder;
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.FragmentManager;
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 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.getDialogIcon;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -276,7 +278,10 @@ public class ImageActivity extends BriarActivity
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_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.setNegativeButton(R.string.cancel, null);
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.DISABLED;
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_PERMISSION_CAMERA_LOCATION;
@@ -259,9 +258,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private boolean shouldEnableWifi() {
if (hasEnabledWifi) return false;
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return false;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
return p != null && p.getState() == DISABLED;
}
private void requestBluetoothDiscoverable() {
@@ -287,9 +284,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
if (hasEnabledBluetooth) return false;
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
if (p == null) return false;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
return p != null && p.getState() == DISABLED;
}
@Override

View File

@@ -7,7 +7,9 @@ import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import com.google.android.material.navigation.NavigationView;
@@ -45,7 +47,9 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
@@ -84,11 +88,11 @@ public class NavDrawerActivity extends BriarActivity implements
@Inject
ViewModelProvider.Factory viewModelFactory;
@Inject
LifecycleManager lifecycleManager;
private DrawerLayout drawerLayout;
private ScrollView drawerScrollView;
private NavigationView navigation;
@Override
@@ -110,8 +114,26 @@ public class NavDrawerActivity extends BriarActivity implements
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);
chevronView.setOnClickListener(v ->
drawerScrollView.fullScroll(FOCUS_DOWN)
);
Toolbar toolbar = findViewById(R.id.toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
@@ -350,4 +372,5 @@ public class NavDrawerActivity extends BriarActivity implements
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.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.LocationUtils;
import java.util.concurrent.Executor;
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.WARNING;
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.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.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.util.LogUtils.logException;
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.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
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;
@NotNullByDefault
@@ -64,7 +57,6 @@ public class NavDrawerViewModel extends AndroidViewModel
private final Executor dbExecutor;
private final SettingsManager settingsManager;
private final PluginManager pluginManager;
private final LocationUtils locationUtils;
private final EventBus eventBus;
private final MutableLiveData<Boolean> showExpiryWarning =
@@ -82,12 +74,11 @@ public class NavDrawerViewModel extends AndroidViewModel
@Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, PluginManager pluginManager,
LocationUtils locationUtils, EventBus eventBus) {
EventBus eventBus) {
super(app);
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager;
this.pluginManager = pluginManager;
this.locationUtils = locationUtils;
this.eventBus = eventBus;
eventBus.addListener(this);
updatePluginStates();
@@ -203,7 +194,7 @@ public class NavDrawerViewModel extends AndroidViewModel
private State getTransportState(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? STARTING_STOPPING : plugin.getState();
return plugin == null ? DISABLED : plugin.getState();
}
@Nullable
@@ -225,31 +216,8 @@ public class NavDrawerViewModel extends AndroidViewModel
return liveData;
}
int getReasonsDisabled(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? 0 : plugin.getReasonsDisabled();
}
void setPluginEnabled(TransportId t, boolean 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;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
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.briar.R;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.SwitchCompat;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.LifecycleOwner;
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 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.DISABLED;
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.util.UiUtils.getDialogIcon;
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 SwitchCompat torSwitch, wifiSwitch, btSwitch;
private boolean expanded = false;
PluginViewController(View v, AppCompatActivity activity,
PluginViewController(View v, LifecycleOwner owner,
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);
wifiIcon = v.findViewById(R.id.wifiIcon);
@@ -89,48 +38,12 @@ class PluginViewController {
for (TransportId t : TRANSPORT_IDS) {
// a OnCheckedChangeListener would get triggered on programmatic updates
SwitchCompat switchCompat = getSwitch(t);
switchCompat.setOnClickListener(buttonView -> {
if (switchCompat.isChecked()) tryToEnablePlugin(t);
else viewModel.setPluginEnabled(t, false);
// Revert the switch to its previous state until the plugin
// changes its state
switchCompat.toggle();
});
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);
switchCompat.setOnClickListener(buttonView ->
// TODO check reason first and change settings if needed
viewModel.setPluginEnabled(t, switchCompat.isChecked())
);
viewModel.getPluginState(t)
.observe(owner, state -> stateUpdate(t, state));
}
}
@@ -147,9 +60,7 @@ class PluginViewController {
}
private void updateSwitch(SwitchCompat switchCompat, State state) {
boolean checked = state != STARTING_STOPPING && state != DISABLED;
switchCompat.setChecked(checked);
switchCompat.setEnabled(state != STARTING_STOPPING);
switchCompat.setChecked(state != DISABLED);
}
private ImageView getIcon(TransportId id) {
@@ -172,42 +83,4 @@ class PluginViewController {
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 javax.annotation.Nullable;
import androidx.annotation.UiThread;
@NotNullByDefault
@@ -19,10 +21,7 @@ interface GroupListController extends DbController {
* The listener must be set right after the controller was injected
*/
@UiThread
void setGroupListListener(GroupListListener listener);
@UiThread
void unsetGroupListListener(GroupListListener listener);
void setGroupListListener(@Nullable GroupListListener listener);
@UiThread
void onStart();

View File

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

View File

@@ -112,7 +112,7 @@ public class GroupListFragment extends BaseFragment implements
@Override
public void onDestroy() {
super.onDestroy();
controller.unsetGroupListListener(this);
controller.setGroupListListener(null);
}
@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 java.util.logging.Level.INFO;
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_NETWORK;
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.util.LogUtils.logDuration;
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.activity.RequestCodes.REQUEST_RINGTONE;
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.triggerFeedback;
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
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 =
circumventionProvider.isTorProbablyBlocked(country);
boolean useBridges = circumventionProvider.doBridgesWork(country);
@@ -339,8 +341,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
try {
long start = now();
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
torSettings = migrateTorSettings(
settingsManager.getSettings(TOR_NAMESPACE));
torSettings = settingsManager.getSettings(TOR_NAMESPACE);
settingsLoaded = true;
logDuration(LOG, "Loading settings", start);
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() {
listener.runOnUiThreadUnlessDestroyed(() -> {
// due to events, we might try to display before a load completed
@@ -681,7 +669,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
displaySettings();
} else if (namespace.equals(TOR_NAMESPACE)) {
LOG.info("Tor settings updated");
torSettings = migrateTorSettings(s.getSettings());
torSettings = s.getSettings();
displaySettings();
}
}

View File

@@ -6,7 +6,6 @@ import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.PowerManager;
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.widget.LinkDialogFragment;
import java.util.Locale;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
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_YES;
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.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -388,7 +381,7 @@ public class UiUtils {
/**
* Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)},
* but without a {@link LifecycleOwner}.
* <p>
*
* Warning: Do NOT call from objects that have a lifecycle.
*/
@UiThread
@@ -409,19 +402,4 @@ public class UiUtils {
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;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -11,7 +10,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
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.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@@ -67,23 +64,18 @@ public class LinkDialogFragment extends DialogFragment {
urlView.setText(url);
// prepare normal intent or intent chooser
Context ctx = requireContext();
Intent i = new Intent(ACTION_VIEW, Uri.parse(url));
PackageManager packageManager = ctx.getPackageManager();
List activities =
packageManager.queryIntentActivities(i, MATCH_DEFAULT_ONLY);
PackageManager packageManager =
requireNonNull(getContext()).getPackageManager();
List activities = packageManager.queryIntentActivities(i,
MATCH_DEFAULT_ONLY);
boolean choice = activities.size() > 1;
Intent intent = choice ? Intent.createChooser(i,
getString(R.string.link_warning_open_link)) : i;
Button openButton = v.findViewById(R.id.openButton);
openButton.setOnClickListener(v1 -> {
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent);
} else {
Toast.makeText(ctx, R.string.error_start_activity, LENGTH_SHORT)
.show();
}
startActivity(intent);
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"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawerScrollView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -7,6 +8,36 @@
android:fillViewport="true"
android:orientation="vertical">
<include layout="@layout/navigation_menu_collapsed" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ScrollView>
<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>

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"?>
<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: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" />
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"
tools:showIn="@layout/navigation_menu">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/chevronView"
@@ -35,30 +20,12 @@
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" />
<!-- 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" />
tools:ignore="ContentDescription"
tools:visibility="visible" />
<View
android:id="@+id/longRangeBackground"
android:id="@+id/backgroundView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
@@ -76,8 +43,7 @@
android:text="@string/transport_internet"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/torSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/chevronView" />
app:layout_constraintStart_toStartOf="@+id/torIcon" />
<ImageView
android:id="@+id/torIcon"
@@ -102,23 +68,22 @@
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:text="@string/transport_tor"
app:layout_constraintBottom_toTopOf="@+id/nearbyLabel"
app:layout_constraintBottom_toTopOf="@id/nearbyLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/longRangeLabel"
tools:checked="true" />
<TextView
android:id="@+id/nearbyLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:gravity="center_vertical"
android:text="@string/transport_nearby"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@+id/wifiSwitch"
app:layout_constraintStart_toStartOf="@+id/torIcon"
app:layout_constraintTop_toBottomOf="@+id/torSwitch" />
app:layout_constraintStart_toStartOf="@+id/torIcon" />
<ImageView
android:id="@+id/wifiIcon"
@@ -147,7 +112,6 @@
app:layout_constraintBottom_toTopOf="@+id/btSwitch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/wifiIcon"
app:layout_constraintTop_toBottomOf="@+id/nearbyLabel"
tools:checked="true" />
<ImageView
@@ -177,7 +141,23 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/btIcon"
app:layout_constraintTop_toBottomOf="@+id/wifiSwitch"
app:layout_constraintVertical_bias="1.0"
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'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!--Setup-->
<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>

View File

@@ -125,16 +125,9 @@
<string name="set_contact_alias_hint">Nombre del contacto</string>
<string name="set_alias_button">Cambiar</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_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="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>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!--Setup-->
<string name="setup_title">ברוך הבא אל Briar</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_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_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="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>

View File

@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!--Setup-->
<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>

View File

@@ -128,13 +128,6 @@
<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_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="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>

View File

@@ -258,11 +258,11 @@
<string name="introduction_error">Произошла ошибка во время представления.</string>
<string name="introduction_response_error">Ошибка при ответе на представление</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_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_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_answered_received">%1$s попросил вас представить %2$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_accepted_received">%1$s принял(-а) представление %2$s.</string>
<string name="introduction_response_declined_received">%1$s отказался от представления %2$s.</string>
@@ -303,8 +303,8 @@
<!--Private Group Invitations-->
<string name="groups_invitations_title">Приглашения в группу</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_joined">Присоединился(-лась) к группе</string>
<string name="groups_invitations_invitation_received">%1$s пригласил вас присоединиться к группе \"%2$s\".</string>
<string name="groups_invitations_joined">Присоединился к группе</string>
<string name="groups_invitations_declined">Приглашение в группу отклонено</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d открытое приглашение в группу</item>
@@ -360,10 +360,10 @@
<string name="forum_invitation_sent">Вы поделились форумом \"%1$s\" с %2$s.</string>
<string name="forum_invitations_title">Приглашения на форум</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="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_declined_sent">Вы отклонили приглашение на форум от %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_error">Произошла ошибка при при попытке поделиться этим блогом.</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_declined_sent">Вы отклонили приглашение в блог от %s.</string>
<string name="blogs_sharing_response_accepted_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_invitations_title">Приглашения в блог</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="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_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-->
<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>
@@ -492,8 +492,8 @@
<string name="choose_ringtone_title">Välj ringsignal</string>
<string name="cannot_load_ringtone">Kan ej ladda ringsignal</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Synpunkter</string>
<string name="send_feedback">Lämna synpunkter</string>
<string name="feedback_settings_title">Återkoppling</string>
<string name="send_feedback">Ge återkoppling</string>
<!--Link Warning-->
<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>
@@ -505,9 +505,9 @@
<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="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="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="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>

View File

@@ -1,41 +1,24 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!--Setup-->
<string name="setup_title">Briar\'a Hoşgeldiniz</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_password">Parolanızı belirleyin</string>
<string name="confirm_password">Parolanızı doğrulayın</string>
<string name="name_too_long">İsim çok uzun</string>
<string name="password_too_weak">Parola çok zayıf</string>
<string name="passwords_do_not_match">Parolalar uyuşmuyor</string>
<string name="create_account_button">Hesap 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>
<string name="passwords_do_not_match">Girdiğiniz iki parola uyuşmuyor</string>
<string name="create_account_button">Hesabı Oluştur</string>
<!--Login-->
<string name="enter_password">Parola</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="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="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_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-->
<string name="nav_drawer_open_description">Gezinme çekmecesini aç</string>
<string name="nav_drawer_close_description">Gezinme çekmecesini kapat</string>
@@ -52,10 +35,6 @@
<string name="transport_bt">Bluetooth</string>
<string name="transport_lan">Wi-Fi</string>
<!--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_text">Briar\'ı açmak için dokunun</string>
<plurals name="private_message_notification_text">
@@ -94,43 +73,16 @@
<string name="ellipsis"></string>
<string name="text_too_long">Girilen metin çok uzun</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="sorry">Üzgünüm</string>
<string name="error_start_activity">Sisteminizde mevcut değil</string>
<!--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="no_private_messages">Gösterilecek ileti yok</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="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="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-->
<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="continue_button">Devam et</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_already_exists"> %s kişisi zaten var</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="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-->
<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_menu_item">Tanıştır</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_hint">Bir ileti ekle (isteğe bağlı)</string>
<string name="introduction_button">Tanıştır</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>
@@ -213,8 +112,11 @@
<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_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-->
<string name="groups_list_empty">Gösterilecek grup yok</string>
<string name="groups_created_by">%s tarafından oluşturuldu</string>
<plurals name="messages">
<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_button">Grup Oluştur</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_message_sent">Mesaj gönderildi</string>
<string name="groups_member_list">Üye Listesi</string>
<string name="groups_invite_members">Üyeleri Davet Edin</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_invisible">Kişi ilişkisi grup tarafından görülemez</string>
<!--Forums-->
<string name="no_forums">Gösterilecek forum yok</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="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>
<plurals name="posts">
<item quantity="one">%d gönderi</item>
<item quantity="other">%d gönderi</item>
</plurals>
<string name="forum_new_message_hint">Yeni Posta</string>
<string name="forum_message_reply_hint">Yeni Cevap</string>
<string name="btn_reply">Cevapla</string>
<string name="forum_leave">Forumdan Ayrıl</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>
<!--Forum Sharing-->
<string name="forum_share_button">Forumu Paylaş</string>
<string name="contacts_selected">Kişiler Seçildi</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_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_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_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="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>
@@ -313,20 +205,15 @@
</plurals>
<string name="nobody">Hiç kimse</string>
<!--Blogs-->
<string name="blogs_other_blog_empty_state">Gösterilecek posta yok</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_body_hint">Blog gönderinizi yazın</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_received">Yeni Blog Gönderisi Alındı</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_ok">Tuşu Sil</string>
<string name="blogs_blog_removed">Blog kaldırıldı</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>
<string name="blogs_reblog_button">Tekrar Blogla</string>
<!--Blog Sharing-->
<string name="blogs_sharing_share">Blog\'u Paylaş</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_accepted_received">%s blog davetini kabul etti.</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_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>
<!--RSS Feeds-->
<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_error">Beslemeleriniz yüklenirken bir hata meydana geldi. Lütfen daha sonra tekrar deneyin.</string>
<!--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="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-->
<string name="network_settings_title">Ağlar</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_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)-->
<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-->
<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-->
<!--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>
<!--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>
<!--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"-->
<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"-->
<string name="pref_lock_timeout_60">1 saat</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">Parolayı değiştir</string>
<string name="current_password">Şimdiki parola</string>
<string name="choose_new_password">Yeni parola</string>
<string name="change_password">Parola değiştir</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="panic_setting">Panik buton ayarları</string>
@@ -409,7 +270,6 @@
<string name="panic_app_setting_none">Yok</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="panic_setting_destructive_action">Yıkıcı Eylemler</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="purge_setting_title">Hesabı Sil</string>
@@ -418,31 +278,24 @@
<string name="uninstall_setting_summary">Bu, bir panik olayında manuel onay gerektirir</string>
<!--Settings Notifications-->
<string name="notification_settings_title">Bildirimler</string>
<string name="notify_private_messages_setting_title">Özel İletiler</string>
<string name="notify_private_messages_setting_summary">Özel iletiler 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_private_messages_setting_title">Özel Mesajlar</string>
<string name="notify_private_messages_setting_summary">Özel 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_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_26">Blog gönderileri için uyarıları yapılandır</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_default">Varsayılan zil sesi</string>
<string name="notify_sound_setting_disabled">Yok</string>
<string name="choose_ringtone_title">Zil sesi seçin</string>
<string name="cannot_load_ringtone">Zil sesi yüklenemiyor</string>
<!--Settings Feedback-->
<string name="feedback_settings_title">Geri bildirim</string>
<string name="send_feedback">Geri bildirim gönder</string>
<!--Link Warning-->
<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_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>
<!--Crash Reporter-->
<string name="crash_report_title">Briar Çökme Raporu</string>
@@ -463,33 +316,6 @@
<!--Sign Out-->
<string name="progress_title_logout">Briar\'dan çıkılıyor...</string>
<!--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-->
<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-->
<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>

View File

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

View File

@@ -567,13 +567,6 @@
<string name="lock_is_locked">Briar is locked</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 -->
<!-- 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.preference:preference:1.1.0:preference-1.1.0.aar:6cf1a099b03b3254041b04701841865b2708c0b546b9036c8b0dada0aa59de57',
'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:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f',
'androidx.recyclerview:recyclerview-selection:1.0.0:recyclerview-selection-1.0.0.aar:db3db72af8cfcd701ab6ed7a080327a2e993e3a429f5efb8f0c108bff38c4922',
'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.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',

View File

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

View File

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

View File

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