Implement backend for connect via bluetooth

This commit is contained in:
Daniel Lublin
2021-04-17 10:29:12 +02:00
parent 0f5ea6ae66
commit e2a11d42f8
10 changed files with 789 additions and 638 deletions

View File

@@ -73,6 +73,7 @@ public class AddNearbyContactIntroFragment extends BaseFragment {
scrollView = v.findViewById(R.id.scrollView);
View button = v.findViewById(R.id.continueButton);
button.setOnClickListener(view -> {
viewModel.stopDiscovery();
viewModel.onContinueClicked();
if (permissionManager.checkPermissions()) {
viewModel.showQrCodeFragmentIfAllowed();

View File

@@ -44,6 +44,7 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error;
@@ -149,7 +150,9 @@ class AddNearbyContactViewModel extends AndroidViewModel
@Nullable
private final BluetoothAdapter bt;
@Nullable // UiThread
private Plugin wifiPlugin, bluetoothPlugin;
private Plugin wifiPlugin;
@Nullable // UiThread
private BluetoothPlugin bluetoothPlugin;
// UiThread
private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
@@ -195,7 +198,8 @@ class AddNearbyContactViewModel extends AndroidViewModel
this.connectionManager = connectionManager;
bt = BluetoothAdapter.getDefaultAdapter();
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
bluetoothPlugin = (BluetoothPlugin) pluginManager
.getPlugin(BluetoothConstants.ID);
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
eventBus.addListener(this);
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
@@ -218,7 +222,8 @@ class AddNearbyContactViewModel extends AndroidViewModel
@UiThread
void resetPlugins() {
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
bluetoothPlugin = (BluetoothPlugin) pluginManager
.getPlugin(BluetoothConstants.ID);
}
@UiThread
@@ -375,6 +380,13 @@ class AddNearbyContactViewModel extends AndroidViewModel
}
}
void stopDiscovery() {
if (!isBluetoothSupported() || !bluetoothPlugin.isDiscovering()) {
return;
}
bluetoothPlugin.stopDiscoverAndConnect();
}
@SuppressWarnings("StatementWithEmptyBody")
@UiThread
void showQrCodeFragmentIfAllowed() {

View File

@@ -6,23 +6,30 @@ import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.widget.Toast;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.ContactItem;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
@@ -31,17 +38,24 @@ import androidx.appcompat.app.AlertDialog;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
class BluetoothConnecter {
class BluetoothConnecter implements EventListener {
private final Logger LOG = getLogger(BluetoothConnecter.class.getName());
private final long BT_ACTIVE_TIMEOUT = SECONDS.toMillis(5);
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
@@ -52,32 +66,41 @@ class BluetoothConnecter {
private final AndroidExecutor androidExecutor;
private final ConnectionRegistry connectionRegistry;
private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
private final EventBus eventBus;
private final TransportPropertyManager transportPropertyManager;
private final ConnectionManager connectionManager;
private volatile Plugin bluetoothPlugin;
private volatile BluetoothPlugin bluetoothPlugin;
private Permission locationPermission = Permission.UNKNOWN;
private ContactId contactId = null;
@Inject
BluetoothConnecter(Application app,
PluginManager pluginManager,
@IoExecutor Executor ioExecutor,
AndroidExecutor androidExecutor,
ConnectionRegistry connectionRegistry) {
ConnectionRegistry connectionRegistry,
EventBus eventBus,
TransportPropertyManager transportPropertyManager,
ConnectionManager connectionManager) {
this.app = app;
this.pluginManager = pluginManager;
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
this.bluetoothPlugin = (BluetoothPlugin) pluginManager.getPlugin(ID);
this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus;
this.transportPropertyManager = transportPropertyManager;
this.connectionManager = connectionManager;
}
boolean isConnectedViaBluetooth(ContactId contactId) {
return connectionRegistry.isConnected(contactId, BluetoothConstants.ID);
return connectionRegistry.isConnected(contactId, ID);
}
boolean isDiscovering() {
// TODO bluetoothPlugin.isDiscovering()
return false;
return bluetoothPlugin.isDiscovering();
}
/**
@@ -89,7 +112,7 @@ class BluetoothConnecter {
// When this class is instantiated before we are logged in
// (like when returning to a killed activity), bluetoothPlugin would be
// null and we consider bluetooth not supported. So reset here.
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
bluetoothPlugin = (BluetoothPlugin) pluginManager.getPlugin(ID);
}
@UiThread
@@ -149,30 +172,84 @@ class BluetoothConnecter {
@UiThread
void onBluetoothDiscoverable(ContactItem contact) {
connect(contact.getContact().getId());
contactId = contact.getContact().getId();
connect();
}
private void connect(ContactId contactId) {
// TODO
// * enable bluetooth connections setting, if not enabled
// * wait for plugin to become active
ioExecutor.execute(() -> {
Random r = new Random();
try {
showToast(R.string.toast_connect_via_bluetooth_start);
// TODO do real work here
Thread.sleep(r.nextInt(3000) + 3000);
if (r.nextBoolean()) {
showToast(R.string.toast_connect_via_bluetooth_success);
} else {
showToast(R.string.toast_connect_via_bluetooth_error);
@Override
public void eventOccurred(@NonNull Event e) {
if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
if (c.getContactId().equals(contactId) && c.isIncoming() &&
c.getTransportId() == ID) {
if (bluetoothPlugin != null) {
bluetoothPlugin.stopDiscoverAndConnect();
}
} catch (InterruptedException e) {
logException(LOG, WARNING, e);
LOG.info("Contact connected to us");
showToast(R.string.toast_connect_via_bluetooth_success);
}
}
}
private void connect() {
pluginManager.setPluginEnabled(ID, true);
ioExecutor.execute(() -> {
if (!waitForBluetoothActive()) {
showToast(R.string.bt_plugin_status_inactive);
LOG.warning("Bluetooth plugin didn't become active");
return;
}
showToast(R.string.toast_connect_via_bluetooth_start);
eventBus.addListener(this);
try {
String uuid = null;
try {
uuid = transportPropertyManager
.getRemoteProperties(contactId, ID).get(PROP_UUID);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
if (isNullOrEmpty(uuid)) {
LOG.warning("PROP_UUID missing for contact");
return;
}
DuplexTransportConnection conn = bluetoothPlugin
.discoverAndConnectForSetup(uuid);
if (conn == null) {
if (!isConnectedViaBluetooth(contactId)) {
LOG.warning("Failed to connect");
showToast(R.string.toast_connect_via_bluetooth_error);
} else {
LOG.info("Failed to connect, but contact connected");
}
return;
}
connectionManager.manageOutgoingConnection(contactId, ID, conn);
showToast(R.string.toast_connect_via_bluetooth_success);
} finally {
eventBus.removeListener(this);
}
});
}
private boolean waitForBluetoothActive() {
long left = BT_ACTIVE_TIMEOUT;
final long sleep = 250;
try {
while (left > 0) {
if (bluetoothPlugin.getState() == ACTIVE) {
return true;
}
Thread.sleep(sleep);
left -= sleep;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return (bluetoothPlugin.getState() == ACTIVE);
}
private void showToast(@StringRes int res) {
androidExecutor.runOnUiThread(() ->
Toast.makeText(app, res, Toast.LENGTH_LONG).show()

View File

@@ -91,7 +91,7 @@ public class BluetoothConnecterDialogFragment extends DialogFragment {
return;
}
if (bluetoothConnecter.isDiscovering()) {
// TODO showToast(R.string.toast_connect_via_bluetooth_discovering);
showToast(R.string.toast_connect_via_bluetooth_already_discovering);
dismiss();
}
}

View File

@@ -34,6 +34,7 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.sync.ClientId;

View File

@@ -175,6 +175,7 @@
<string name="menu_item_connect_via_bluetooth">Connect via Bluetooth</string>
<string name="dialog_title_connect_via_bluetooth">Connect via Bluetooth</string>
<string name="dialog_message_connect_via_bluetooth">Your contact needs to be nearby for this to work.\n\nYou and your contact should both press \"Start\" at the same time.</string>
<string name="toast_connect_via_bluetooth_already_discovering">Already trying to connect via Bluetooth</string>
<string name="toast_connect_via_bluetooth_not_discoverable">Cannot continue without Bluetooth</string>
<string name="toast_connect_via_bluetooth_no_location_permission">Cannot continue without location permission</string>
<string name="toast_connect_via_bluetooth_start">Connecting via Bluetooth…</string>