mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 12:49:55 +01:00
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:
@@ -10,4 +10,6 @@ public interface FeatureFlags {
|
|||||||
boolean shouldEnableProfilePictures();
|
boolean shouldEnableProfilePictures();
|
||||||
|
|
||||||
boolean shouldEnableDisappearingMessages();
|
boolean shouldEnableDisappearingMessages();
|
||||||
|
|
||||||
|
boolean shouldEnableConnectViaBluetooth();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ public class BrambleCoreIntegrationTestModule {
|
|||||||
public boolean shouldEnableDisappearingMessages() {
|
public boolean shouldEnableDisappearingMessages() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableConnectViaBluetooth() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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." -->
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user