diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index a371ca4b4..93692bc0c 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -26,6 +26,8 @@ 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; @@ -71,6 +73,7 @@ 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; @@ -157,12 +160,31 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { @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; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java index 3d13f6e75..631e2a6c3 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PollerImpl.java @@ -11,6 +11,7 @@ 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; @@ -211,7 +212,7 @@ class PollerImpl implements Poller, EventListener { @IoExecutor private void poll(Plugin p) { TransportId t = p.getId(); - if (LOG.isLoggable(INFO)) LOG.info("Polling plugin " + t); + if (LOG.isLoggable(INFO)) LOG.info("Polling " + t); try { Map remote = transportPropertyManager.getRemoteProperties(t); @@ -221,8 +222,10 @@ class PollerImpl implements Poller, EventListener { new ArrayList<>(); for (Entry e : remote.entrySet()) { ContactId c = e.getKey(); - if (!connected.contains(c)) - properties.add(new Pair<>(e.getValue(), new Handler(c, t))); + if (!connected.contains(c)) { + ConnHandler handler = new ConnHandler(c, t); + properties.add(new Pair<>(e.getValue(), handler)); + } } if (!properties.isEmpty()) p.poll(properties); } catch (DbException e) { @@ -230,6 +233,30 @@ 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 remote = + transportPropertyManager.getRemoteProperties(t); + Collection connected = + connectionRegistry.getConnectedContacts(t); + List> properties = + new ArrayList<>(); + for (Entry 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; @@ -269,16 +296,23 @@ class PollerImpl implements Poller, EventListener { int delay = plugin.getPollingInterval(); if (randomiseNext) delay = (int) (delay * random.nextDouble()); schedule(plugin, delay, false); - poll(plugin); + // FIXME: Revert + if (plugin instanceof DuplexPlugin) { + DuplexPlugin d = (DuplexPlugin) plugin; + if (d.supportsDiscovery()) discover(d); + else poll(d); + } else { + poll(plugin); + } } } - private class Handler implements ConnectionHandler { + private class ConnHandler implements ConnectionHandler { private final ContactId contactId; private final TransportId transportId; - private Handler(ContactId contactId, TransportId transportId) { + private ConnHandler(ContactId contactId, TransportId transportId) { this.contactId = contactId; this.transportId = transportId; } @@ -301,4 +335,27 @@ 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); + } + }); + } + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java index 90c1cf512..d63e21802 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/plugin/PollerImplTest.java @@ -351,6 +351,9 @@ 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))); @@ -394,6 +397,9 @@ 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))); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java index 3f551b1ee..f123a1155 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java @@ -1,5 +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; @@ -17,7 +19,10 @@ 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 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; @@ -116,6 +121,14 @@ class PluginViewController { } 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); }