From a9c4669c75465ca926ab12064a098045413fdd18 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 9 Mar 2021 14:53:26 -0300 Subject: [PATCH] Simple version of Connect via Bluetooth UI --- .../conversation/BluetoothConnecter.java | 120 ++++++++++++++++++ .../conversation/ConversationActivity.java | 81 +++++++----- .../conversation/ConversationViewModel.java | 9 +- .../RequestBluetoothDiscoverable.java | 30 +++++ .../main/res/menu/conversation_actions.xml | 6 + briar-android/src/main/res/values/strings.xml | 8 ++ 6 files changed, 222 insertions(+), 32 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/conversation/RequestBluetoothDiscoverable.java diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java new file mode 100644 index 000000000..8c749319d --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java @@ -0,0 +1,120 @@ +package org.briarproject.briar.android.conversation; + +import android.app.Application; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.widget.Toast; + +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.system.AndroidExecutor; +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.StringRes; +import androidx.annotation.UiThread; +import androidx.appcompat.app.AlertDialog; + +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +class BluetoothConnecter { + + private final Logger LOG = getLogger(BluetoothConnecter.class.getName()); + + private final Application app; + private final Executor ioExecutor; + private final AndroidExecutor androidExecutor; + + private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); + private final Plugin bluetoothPlugin; + + @Inject + BluetoothConnecter(Application app, + PluginManager pluginManager, + @IoExecutor Executor ioExecutor, + AndroidExecutor androidExecutor) { + this.app = app; + this.ioExecutor = ioExecutor; + this.androidExecutor = androidExecutor; + this.bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID); + } + + static void showDialog(Context ctx, + ActivityResultLauncher permissionRequest) { + new AlertDialog.Builder(ctx, R.style.BriarDialogTheme) + .setTitle(R.string.dialog_title_connect_via_bluetooth) + .setMessage(R.string.dialog_message_connect_via_bluetooth) + .setPositiveButton(R.string.start, (dialog, which) -> + permissionRequest.launch(ACCESS_FINE_LOCATION)) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + @UiThread + void onLocationPermissionResult(boolean result, + ActivityResultLauncher bluetoothDiscoverableRequest) { + if (result) { + if (isBluetoothSupported()) { + bluetoothDiscoverableRequest.launch(120); + } else { + showToast(R.string.toast_connect_via_bluetooth_error); + } + } else { + // could also show the same dialog as KeyAgreementActivity + showToast(R.string.permission_location_denied_body); + } + } + + private boolean isBluetoothSupported() { + return bt != null && bluetoothPlugin != null; + } + + @UiThread + void onBluetoothDiscoverable(boolean result, ContactItem contact) { + if (result) { + connect(contact); + } else { + showToast(R.string.toast_connect_via_bluetooth_not_discoverable); + } + } + + private void connect(ContactItem contact) { + // 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); + } + } catch (InterruptedException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void showToast(@StringRes int res) { + androidExecutor.runOnUiThread(() -> + Toast.makeText(app, res, Toast.LENGTH_LONG).show() + ); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java index 17da35e55..38e7e659c 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationActivity.java @@ -47,6 +47,7 @@ import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.attachment.AttachmentRetriever; import org.briarproject.briar.android.blog.BlogActivity; +import org.briarproject.briar.android.contact.ContactItem; import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache; import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache; import org.briarproject.briar.android.forum.ForumActivity; @@ -91,6 +92,8 @@ import java.util.logging.Logger; import javax.inject.Inject; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.appcompat.app.AlertDialog; @@ -211,6 +214,21 @@ public class ConversationActivity extends BriarActivity private volatile ContactId contactId; + private final ActivityResultLauncher bluetoothDiscoverableRequest = + registerForActivityResult(new RequestBluetoothDiscoverable(), r -> { + // MenuItem only gets enabled after contactItem has loaded + ContactItem contact = + requireNonNull(viewModel.getContactItem().getValue()); + BluetoothConnecter bc = viewModel.getBluetoothConnecter(); + bc.onBluetoothDiscoverable(r, contact); + }); + private final ActivityResultLauncher permissionRequest = + registerForActivityResult(new RequestPermission(), result -> { + BluetoothConnecter bc = viewModel.getBluetoothConnecter(); + bc.onLocationPermissionResult(result, + bluetoothDiscoverableRequest); + }); + @Override public void onCreate(@Nullable Bundle state) { if (SDK_INT >= 21) { @@ -370,9 +388,11 @@ public class ConversationActivity extends BriarActivity this::showIntroductionOnboarding); } }); - // enable alias action if available - observeOnce(viewModel.getContactItem(), this, contact -> - menu.findItem(R.id.action_set_alias).setEnabled(true)); + // enable alias and bluetooth action once available + observeOnce(viewModel.getContactItem(), this, contact -> { + menu.findItem(R.id.action_set_alias).setEnabled(true); + menu.findItem(R.id.action_connect_via_bluetooth).setEnabled(true); + }); // Show auto-delete menu item if feature is enabled if (featureFlags.shouldEnableDisappearingMessages()) { MenuItem item = menu.findItem(R.id.action_conversation_settings); @@ -381,40 +401,39 @@ public class ConversationActivity extends BriarActivity viewModel.getPrivateMessageFormat().observe(this, format -> item.setEnabled(format == TEXT_IMAGES_AUTO_DELETE)); } - return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { - // Handle presses on the action bar items - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - case R.id.action_introduction: - if (contactId == null) return false; - Intent intent = new Intent(this, IntroductionActivity.class); - intent.putExtra(CONTACT_ID, contactId.getInt()); - startActivityForResult(intent, REQUEST_INTRODUCTION); - return true; - case R.id.action_set_alias: - AliasDialogFragment.newInstance().show( - getSupportFragmentManager(), AliasDialogFragment.TAG); - return true; - case R.id.action_conversation_settings: - if (contactId == null) return false; - onAutoDeleteTimerNoticeClicked(); - return true; - case R.id.action_delete_all_messages: - askToDeleteAllMessages(); - return true; - case R.id.action_social_remove_person: - askToRemoveContact(); - return true; - default: - return super.onOptionsItemSelected(item); + // contactId gets set before in onCreate() + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + onBackPressed(); + return true; + } else if (itemId == R.id.action_introduction) { + Intent intent = new Intent(this, IntroductionActivity.class); + intent.putExtra(CONTACT_ID, contactId.getInt()); + startActivityForResult(intent, REQUEST_INTRODUCTION); + return true; + } else if (itemId == R.id.action_set_alias) { + AliasDialogFragment.newInstance().show( + getSupportFragmentManager(), AliasDialogFragment.TAG); + return true; + } else if (itemId == R.id.action_conversation_settings) { + onAutoDeleteTimerNoticeClicked(); + return true; + } else if (itemId == R.id.action_connect_via_bluetooth) { + BluetoothConnecter.showDialog(this, permissionRequest); + return true; + } else if (itemId == R.id.action_delete_all_messages) { + askToDeleteAllMessages(); + return true; + } else if (itemId == R.id.action_social_remove_person) { + askToRemoveContact(); + return true; } + return super.onOptionsItemSelected(item); } @Override diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index dcc42e49c..557602f31 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -101,6 +101,7 @@ public class ConversationViewModel extends DbViewModel private final AttachmentCreator attachmentCreator; private final AutoDeleteManager autoDeleteManager; private final ConversationManager conversationManager; + private final BluetoothConnecter bluetoothConnecter; @Nullable private ContactId contactId = null; @@ -139,7 +140,8 @@ public class ConversationViewModel extends DbViewModel AttachmentRetriever attachmentRetriever, AttachmentCreator attachmentCreator, AutoDeleteManager autoDeleteManager, - ConversationManager conversationManager) { + ConversationManager conversationManager, + BluetoothConnecter bluetoothConnecter) { super(application, dbExecutor, lifecycleManager, db, androidExecutor); this.db = db; this.eventBus = eventBus; @@ -152,6 +154,7 @@ public class ConversationViewModel extends DbViewModel this.attachmentCreator = attachmentCreator; this.autoDeleteManager = autoDeleteManager; this.conversationManager = conversationManager; + this.bluetoothConnecter = bluetoothConnecter; messagingGroupId = map(contactItem, c -> messagingManager.getContactGroup(c.getContact()).getId()); eventBus.addListener(this); @@ -411,6 +414,10 @@ public class ConversationViewModel extends DbViewModel return attachmentRetriever; } + BluetoothConnecter getBluetoothConnecter() { + return bluetoothConnecter; + } + LiveData getContactItem() { return contactItem; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/RequestBluetoothDiscoverable.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RequestBluetoothDiscoverable.java new file mode 100644 index 000000000..95023f6bf --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/RequestBluetoothDiscoverable.java @@ -0,0 +1,30 @@ +package org.briarproject.briar.android.conversation; + +import android.content.Context; +import android.content.Intent; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import androidx.activity.result.contract.ActivityResultContract; +import androidx.annotation.Nullable; + +import static android.app.Activity.RESULT_CANCELED; +import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; +import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION; + +@NotNullByDefault +class RequestBluetoothDiscoverable + extends ActivityResultContract { + + @Override + public Intent createIntent(Context context, Integer duration) { + Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); + i.putExtra(EXTRA_DISCOVERABLE_DURATION, duration); + return i; + } + + @Override + public Boolean parseResult(int resultCode, @Nullable Intent intent) { + return resultCode != RESULT_CANCELED; + } +} diff --git a/briar-android/src/main/res/menu/conversation_actions.xml b/briar-android/src/main/res/menu/conversation_actions.xml index f3d057b08..dad562bab 100644 --- a/briar-android/src/main/res/menu/conversation_actions.xml +++ b/briar-android/src/main/res/menu/conversation_actions.xml @@ -23,6 +23,12 @@ app:showAsAction="never" tools:visible="true" /> + + Allow Open Change + Start No data … The entered text is too long @@ -168,6 +169,13 @@ Change contact name Contact name Disappearing messages + Connect via Bluetooth + Connect via Bluetooth + Your contact needs to be in close proximity for this to work. They need to press start at the same time as you and confirm the following system dialog. + Can not continue without Bluetooth + Connecting via Bluetooth… + Successfully connected via Bluetooth + Could not connect via Bluetooth Your messages will disappear after %1$s. %2$s