Merge branch '1961-connect-via-bt-ui' into 'master'

Simple version of Connect via Bluetooth UI

See merge request briar/briar!1398
This commit is contained in:
akwizgran
2021-04-21 11:57:53 +00:00
15 changed files with 450 additions and 72 deletions

View File

@@ -10,4 +10,6 @@ public interface FeatureFlags {
boolean shouldEnableProfilePictures(); boolean shouldEnableProfilePictures();
boolean shouldEnableDisappearingMessages(); boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth();
} }

View File

@@ -38,6 +38,11 @@ public class BrambleCoreIntegrationTestModule {
public boolean shouldEnableDisappearingMessages() { public boolean shouldEnableDisappearingMessages() {
return true; return true;
} }
@Override
public boolean shouldEnableConnectViaBluetooth() {
return true;
}
}; };
} }
} }

View File

@@ -298,6 +298,11 @@ public class AppModule {
public boolean shouldEnableDisappearingMessages() { public boolean shouldEnableDisappearingMessages() {
return IS_DEBUG_BUILD; return IS_DEBUG_BUILD;
} }
@Override
public boolean shouldEnableConnectViaBluetooth() {
return IS_DEBUG_BUILD;
}
}; };
} }
} }

View File

@@ -28,6 +28,7 @@ import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment; import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.BluetoothConnecterDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsDialog; import org.briarproject.briar.android.conversation.ConversationSettingsDialog;
import org.briarproject.briar.android.conversation.ImageActivity; import org.briarproject.briar.android.conversation.ImageActivity;
@@ -236,4 +237,6 @@ public interface ActivityComponent {
void inject(ConversationSettingsDialog dialog); void inject(ConversationSettingsDialog dialog);
void inject(
BluetoothConnecterDialogFragment bluetoothConnecterDialogFragment);
} }

View File

@@ -1,10 +1,6 @@
package org.briarproject.briar.android.contact.add.nearby; package org.briarproject.briar.android.contact.add.nearby;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -19,11 +15,11 @@ import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.CAMERA; import static android.Manifest.permission.CAMERA;
import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
import static android.widget.Toast.LENGTH_LONG;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static androidx.core.content.ContextCompat.checkSelfPermission; import static androidx.core.content.ContextCompat.checkSelfPermission;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener; 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 AddNearbyContactPermissionManager { class AddNearbyContactPermissionManager {
@@ -51,19 +47,6 @@ class AddNearbyContactPermissionManager {
locationPermission = Permission.UNKNOWN; locationPermission = Permission.UNKNOWN;
} }
/**
* @return true if location is enabled,
* or it isn't required due to this being a SDK < 28 device.
*/
static boolean isLocationEnabled(Context ctx) {
if (SDK_INT >= 28) {
LocationManager lm = ctx.getSystemService(LocationManager.class);
return lm.isLocationEnabled();
} else {
return true;
}
}
static boolean areEssentialPermissionsGranted(Context ctx, static boolean areEssentialPermissionsGranted(Context ctx,
boolean isBluetoothSupported) { boolean isBluetoothSupported) {
int ok = PERMISSION_GRANTED; int ok = PERMISSION_GRANTED;
@@ -106,7 +89,7 @@ class AddNearbyContactPermissionManager {
} else if (locationPermission == Permission.SHOW_RATIONALE) { } else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_location_title, showRationale(R.string.permission_location_title,
R.string.permission_location_request_body); R.string.permission_location_request_body);
} else if (isLocationEnabled(ctx)) { } else if (locationEnabled) {
requestPermissions(); requestPermissions();
} else { } else {
showLocationDialog(ctx); showLocationDialog(ctx);
@@ -135,25 +118,6 @@ class AddNearbyContactPermissionManager {
builder.show(); builder.show();
} }
private static void showLocationDialog(Context ctx) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(R.string.permission_location_setting_title);
builder.setMessage(R.string.permission_location_setting_body);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.permission_location_setting_button,
(dialog, which) -> {
Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
try {
ctx.startActivity(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity,
LENGTH_LONG).show();
}
});
builder.show();
}
private void requestPermissions() { private void requestPermissions() {
String[] permissions; String[] permissions;
if (isBluetoothSupported) { if (isBluetoothSupported) {

View File

@@ -82,11 +82,11 @@ 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.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.isLocationEnabled;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.UNKNOWN;
import static org.briarproject.briar.android.util.UiUtils.handleException; import static org.briarproject.briar.android.util.UiUtils.handleException;
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
@NotNullByDefault @NotNullByDefault
class AddNearbyContactViewModel extends AndroidViewModel class AddNearbyContactViewModel extends AndroidViewModel

View File

@@ -0,0 +1,182 @@
package org.briarproject.briar.android.conversation;
import android.app.Activity;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.widget.Toast;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
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.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
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.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
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 {
private final Logger LOG = getLogger(BluetoothConnecter.class.getName());
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private final Application app;
private final PluginManager pluginManager;
private final Executor ioExecutor;
private final AndroidExecutor androidExecutor;
private final ConnectionRegistry connectionRegistry;
private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
private volatile Plugin bluetoothPlugin;
private Permission locationPermission = Permission.UNKNOWN;
@Inject
BluetoothConnecter(Application app,
PluginManager pluginManager,
@IoExecutor Executor ioExecutor,
AndroidExecutor androidExecutor,
ConnectionRegistry connectionRegistry) {
this.app = app;
this.pluginManager = pluginManager;
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
this.connectionRegistry = connectionRegistry;
}
boolean isConnectedViaBluetooth(ContactId contactId) {
return connectionRegistry.isConnected(contactId, BluetoothConstants.ID);
}
boolean isDiscovering() {
// TODO bluetoothPlugin.isDiscovering()
return false;
}
/**
* Call this when the using activity or fragment starts,
* because permissions might have changed while it was stopped.
*/
void reset() {
locationPermission = Permission.UNKNOWN;
// 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);
}
@UiThread
void onLocationPermissionResult(Activity activity,
@Nullable Boolean result) {
if (result != null && result) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(activity,
ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
}
boolean isBluetoothNotSupported() {
return bt == null || bluetoothPlugin == null;
}
boolean areRequirementsFulfilled(Context ctx,
ActivityResultLauncher<String> permissionRequest,
Runnable onLocationDenied) {
boolean permissionGranted =
SDK_INT < 23 || locationPermission == Permission.GRANTED;
boolean locationEnabled = isLocationEnabled(ctx);
if (permissionGranted && locationEnabled) return true;
if (locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(ctx, onLocationDenied);
} else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx, permissionRequest);
} else if (!locationEnabled) {
showLocationDialog(ctx);
}
return false;
}
private void showDenialDialog(Context ctx, Runnable onLocationDenied) {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_denied_body)
.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
.setNegativeButton(R.string.cancel, (v, d) ->
onLocationDenied.run())
.show();
}
private void showRationale(Context ctx,
ActivityResultLauncher<String> permissionRequest) {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_request_body)
.setPositiveButton(R.string.ok, (dialog, which) ->
permissionRequest.launch(ACCESS_FINE_LOCATION))
.show();
}
@UiThread
void onBluetoothDiscoverable(ContactItem contact) {
connect(contact.getContact().getId());
}
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);
}
} catch (InterruptedException e) {
logException(LOG, WARNING, e);
}
});
}
private void showToast(@StringRes int res) {
androidExecutor.runOnUiThread(() ->
Toast.makeText(app, res, Toast.LENGTH_LONG).show()
);
}
}

View File

@@ -0,0 +1,151 @@
package org.briarproject.briar.android.conversation;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.util.RequestBluetoothDiscoverable;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.DialogInterface.BUTTON_POSITIVE;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothConnecterDialogFragment extends DialogFragment {
final static String TAG = BluetoothConnecterDialogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
private BluetoothConnecter bluetoothConnecter;
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
registerForActivityResult(new RequestBluetoothDiscoverable(),
this::onBluetoothDiscoverable);
private final ActivityResultLauncher<String> permissionRequest =
registerForActivityResult(new RequestPermission(),
this::onPermissionRequestResult);
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
bluetoothConnecter = viewModel.getBluetoothConnecter();
}
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context ctx = requireContext();
return new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.dialog_title_connect_via_bluetooth)
.setMessage(R.string.dialog_message_connect_via_bluetooth)
// actual listener gets set in onResume()
.setPositiveButton(R.string.start, null)
.setNegativeButton(R.string.cancel, null)
.setCancelable(false) // keep it open until dismissed
.create();
}
@Override
public void onStart() {
super.onStart();
bluetoothConnecter.reset();
if (bluetoothConnecter.isBluetoothNotSupported()) {
showToast(R.string.toast_connect_via_bluetooth_error);
dismiss();
return;
}
// MenuItem only gets enabled after contactItem has loaded
ContactItem contact =
requireNonNull(viewModel.getContactItem().getValue());
ContactId contactId = contact.getContact().getId();
if (bluetoothConnecter.isConnectedViaBluetooth(contactId)) {
showToast(R.string.toast_connect_via_bluetooth_success);
dismiss();
return;
}
if (bluetoothConnecter.isDiscovering()) {
// TODO showToast(R.string.toast_connect_via_bluetooth_discovering);
dismiss();
}
}
@Override
public void onResume() {
super.onResume();
// Set the click listener for the START button here
// to prevent it from automatically dismissing the dialog.
// The dialog is shown in onStart(), so we set the listener here later.
AlertDialog dialog = (AlertDialog) getDialog();
Button positiveButton = dialog.getButton(BUTTON_POSITIVE);
positiveButton.setOnClickListener(this::onStartClicked);
}
private void onStartClicked(View v) {
// The dialog starts a permission request which comes back as true
// if the permission is already granted.
// So we can use the request as a generic entry point to the whole flow.
permissionRequest.launch(ACCESS_FINE_LOCATION);
}
private void onPermissionRequestResult(@Nullable Boolean result) {
Activity a = requireActivity();
// update permission result in BluetoothConnecter
bluetoothConnecter.onLocationPermissionResult(a, result);
// what to do when the user denies granting the location permission
Runnable onLocationPermissionDenied = () -> {
Toast.makeText(requireContext(),
R.string.toast_connect_via_bluetooth_no_location_permission,
LENGTH_LONG).show();
dismiss();
};
// if requirements are fulfilled, request Bluetooth discoverability
if (bluetoothConnecter.areRequirementsFulfilled(a, permissionRequest,
onLocationPermissionDenied)) {
bluetoothDiscoverableRequest.launch(120); // for 2min
}
}
private void onBluetoothDiscoverable(@Nullable Boolean result) {
if (result != null && result) {
// MenuItem only gets enabled after contactItem has loaded
ContactItem contact =
requireNonNull(viewModel.getContactItem().getValue());
bluetoothConnecter.onBluetoothDiscoverable(contact);
dismiss();
} else {
showToast(R.string.toast_connect_via_bluetooth_not_discoverable);
}
}
private void showToast(@StringRes int stringRes) {
Toast.makeText(requireContext(), stringRes, LENGTH_LONG).show();
}
}

View File

@@ -99,6 +99,7 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -370,9 +371,14 @@ public class ConversationActivity extends BriarActivity
this::showIntroductionOnboarding); this::showIntroductionOnboarding);
} }
}); });
// enable alias action if available if (!featureFlags.shouldEnableConnectViaBluetooth()) {
observeOnce(viewModel.getContactItem(), this, contact -> menu.findItem(R.id.action_connect_via_bluetooth).setVisible(false);
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 // Show auto-delete menu item if feature is enabled
if (featureFlags.shouldEnableDisappearingMessages()) { if (featureFlags.shouldEnableDisappearingMessages()) {
MenuItem item = menu.findItem(R.id.action_conversation_settings); MenuItem item = menu.findItem(R.id.action_conversation_settings);
@@ -381,40 +387,41 @@ public class ConversationActivity extends BriarActivity
viewModel.getPrivateMessageFormat().observe(this, format -> viewModel.getPrivateMessageFormat().observe(this, format ->
item.setEnabled(format == TEXT_IMAGES_AUTO_DELETE)); item.setEnabled(format == TEXT_IMAGES_AUTO_DELETE));
} }
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items // contactId gets set before in onCreate()
switch (item.getItemId()) { int itemId = item.getItemId();
case android.R.id.home: if (itemId == android.R.id.home) {
onBackPressed(); onBackPressed();
return true; return true;
case R.id.action_introduction: } else if (itemId == R.id.action_introduction) {
if (contactId == null) return false; Intent intent = new Intent(this, IntroductionActivity.class);
Intent intent = new Intent(this, IntroductionActivity.class); intent.putExtra(CONTACT_ID, contactId.getInt());
intent.putExtra(CONTACT_ID, contactId.getInt()); startActivityForResult(intent, REQUEST_INTRODUCTION);
startActivityForResult(intent, REQUEST_INTRODUCTION); return true;
return true; } else if (itemId == R.id.action_set_alias) {
case R.id.action_set_alias: AliasDialogFragment.newInstance().show(
AliasDialogFragment.newInstance().show( getSupportFragmentManager(), AliasDialogFragment.TAG);
getSupportFragmentManager(), AliasDialogFragment.TAG); return true;
return true; } else if (itemId == R.id.action_conversation_settings) {
case R.id.action_conversation_settings: onAutoDeleteTimerNoticeClicked();
if (contactId == null) return false; return true;
onAutoDeleteTimerNoticeClicked(); } else if (itemId == R.id.action_connect_via_bluetooth) {
return true; FragmentManager fm = getSupportFragmentManager();
case R.id.action_delete_all_messages: new BluetoothConnecterDialogFragment().show(fm,
askToDeleteAllMessages(); BluetoothConnecterDialogFragment.TAG);
return true; return true;
case R.id.action_social_remove_person: } else if (itemId == R.id.action_delete_all_messages) {
askToRemoveContact(); askToDeleteAllMessages();
return true; return true;
default: } else if (itemId == R.id.action_social_remove_person) {
return super.onOptionsItemSelected(item); askToRemoveContact();
return true;
} }
return super.onOptionsItemSelected(item);
} }
@Override @Override

View File

@@ -101,6 +101,7 @@ public class ConversationViewModel extends DbViewModel
private final AttachmentCreator attachmentCreator; private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager; private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager; private final ConversationManager conversationManager;
private final BluetoothConnecter bluetoothConnecter;
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
@@ -139,7 +140,8 @@ public class ConversationViewModel extends DbViewModel
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator, AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager, AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) { ConversationManager conversationManager,
BluetoothConnecter bluetoothConnecter) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -152,6 +154,7 @@ public class ConversationViewModel extends DbViewModel
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager; this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager; this.conversationManager = conversationManager;
this.bluetoothConnecter = bluetoothConnecter;
messagingGroupId = map(contactItem, c -> messagingGroupId = map(contactItem, c ->
messagingManager.getContactGroup(c.getContact()).getId()); messagingManager.getContactGroup(c.getContact()).getId());
eventBus.addListener(this); eventBus.addListener(this);
@@ -411,6 +414,10 @@ public class ConversationViewModel extends DbViewModel
return attachmentRetriever; return attachmentRetriever;
} }
BluetoothConnecter getBluetoothConnecter() {
return bluetoothConnecter;
}
LiveData<ContactItem> getContactItem() { LiveData<ContactItem> getContactItem() {
return contactItem; return contactItem;
} }

View File

@@ -3,11 +3,13 @@ package org.briarproject.briar.android.util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.Spannable; import android.text.Spannable;
@@ -72,6 +74,7 @@ import static android.content.Intent.EXTRA_MIME_TYPES;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.MANUFACTURER; import static android.os.Build.MANUFACTURER;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS;
import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS; import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS;
import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_ALL; import static android.text.format.DateUtils.FORMAT_ABBREV_ALL;
@@ -331,6 +334,19 @@ public class UiUtils {
return i; return i;
} }
/**
* @return true if location is enabled,
* or it isn't required due to this being a SDK < 28 device.
*/
public static boolean isLocationEnabled(Context ctx) {
if (SDK_INT >= 28) {
LocationManager lm = ctx.getSystemService(LocationManager.class);
return lm.isLocationEnabled();
} else {
return true;
}
}
public static boolean isSamsung7() { public static boolean isSamsung7() {
return (SDK_INT == 24 || SDK_INT == 25) && return (SDK_INT == 24 || SDK_INT == 25) &&
MANUFACTURER.equalsIgnoreCase("Samsung"); MANUFACTURER.equalsIgnoreCase("Samsung");
@@ -474,6 +490,25 @@ public class UiUtils {
return isoCode; return isoCode;
} }
public static void showLocationDialog(Context ctx) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setTitle(R.string.permission_location_setting_title);
builder.setMessage(R.string.permission_location_setting_body);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.permission_location_setting_button,
(dialog, which) -> {
Intent i = new Intent(ACTION_LOCATION_SOURCE_SETTINGS);
try {
ctx.startActivity(i);
} catch (ActivityNotFoundException e) {
Toast.makeText(ctx, R.string.error_start_activity,
LENGTH_LONG).show();
}
});
builder.show();
}
public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) { public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
Drawable icon = Drawable icon =
VectorDrawableCompat.create(ctx.getResources(), resId, null); VectorDrawableCompat.create(ctx.getResources(), resId, null);

View File

@@ -23,6 +23,12 @@
app:showAsAction="never" app:showAsAction="never"
tools:visible="true" /> tools:visible="true" />
<item
android:id="@+id/action_connect_via_bluetooth"
android:enabled="false"
android:title="@string/menu_item_connect_via_bluetooth"
app:showAsAction="never" />
<item <item
android:id="@+id/action_delete_all_messages" android:id="@+id/action_delete_all_messages"
android:title="@string/delete_all_messages" android:title="@string/delete_all_messages"

View File

@@ -142,6 +142,7 @@
<string name="allow">Allow</string> <string name="allow">Allow</string>
<string name="open">Open</string> <string name="open">Open</string>
<string name="change">Change</string> <string name="change">Change</string>
<string name="start">Start</string>
<string name="no_data">No data</string> <string name="no_data">No data</string>
<string name="ellipsis"></string> <string name="ellipsis"></string>
<string name="text_too_long">The entered text is too long</string> <string name="text_too_long">The entered text is too long</string>
@@ -168,6 +169,14 @@
<string name="set_contact_alias">Change contact name</string> <string name="set_contact_alias">Change contact name</string>
<string name="set_contact_alias_hint">Contact name</string> <string name="set_contact_alias_hint">Contact name</string>
<string name="menu_item_disappearing_messages">Disappearing messages</string> <string name="menu_item_disappearing_messages">Disappearing messages</string>
<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_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>
<string name="toast_connect_via_bluetooth_success">Successfully connected via Bluetooth</string>
<string name="toast_connect_via_bluetooth_error">Could not connect via Bluetooth</string>
<!-- The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more." --> <!-- The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more." -->
<string name="auto_delete_msg_you_enabled">Your messages will disappear after %1$s. %2$s</string> <string name="auto_delete_msg_you_enabled">Your messages will disappear after %1$s. %2$s</string>
<!-- The placeholder at the end will add "Tap to learn more." --> <!-- The placeholder at the end will add "Tap to learn more." -->

View File

@@ -94,5 +94,6 @@ internal class HeadlessModule(private val appDir: File) {
override fun shouldEnableImageAttachments() = false override fun shouldEnableImageAttachments() = false
override fun shouldEnableProfilePictures() = false override fun shouldEnableProfilePictures() = false
override fun shouldEnableDisappearingMessages() = false override fun shouldEnableDisappearingMessages() = false
override fun shouldEnableConnectViaBluetooth() = false
} }
} }

View File

@@ -82,6 +82,7 @@ internal class HeadlessTestModule(private val appDir: File) {
override fun shouldEnableImageAttachments() = false override fun shouldEnableImageAttachments() = false
override fun shouldEnableProfilePictures() = false override fun shouldEnableProfilePictures() = false
override fun shouldEnableDisappearingMessages() = false override fun shouldEnableDisappearingMessages() = false
override fun shouldEnableConnectViaBluetooth() = false
} }
@Provides @Provides