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 shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth();
}

View File

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

View File

@@ -298,6 +298,11 @@ public class AppModule {
public boolean shouldEnableDisappearingMessages() {
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.PendingContactListActivity;
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.ConversationSettingsDialog;
import org.briarproject.briar.android.conversation.ImageActivity;
@@ -236,4 +237,6 @@ public interface ActivityComponent {
void inject(ConversationSettingsDialog dialog);
void inject(
BluetoothConnecterDialogFragment bluetoothConnecterDialogFragment);
}

View File

@@ -1,10 +1,6 @@
package org.briarproject.briar.android.contact.add.nearby;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.widget.Toast;
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.content.pm.PackageManager.PERMISSION_GRANTED;
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.content.ContextCompat.checkSelfPermission;
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 {
@@ -51,19 +47,6 @@ class AddNearbyContactPermissionManager {
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,
boolean isBluetoothSupported) {
int ok = PERMISSION_GRANTED;
@@ -106,7 +89,7 @@ class AddNearbyContactPermissionManager {
} else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_location_title,
R.string.permission_location_request_body);
} else if (isLocationEnabled(ctx)) {
} else if (locationEnabled) {
requestPermissions();
} else {
showLocationDialog(ctx);
@@ -135,25 +118,6 @@ class AddNearbyContactPermissionManager {
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() {
String[] permissions;
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.util.LogUtils.logException;
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.REFUSED;
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.isLocationEnabled;
@NotNullByDefault
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.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
@@ -370,9 +371,14 @@ 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));
if (!featureFlags.shouldEnableConnectViaBluetooth()) {
menu.findItem(R.id.action_connect_via_bluetooth).setVisible(false);
}
// 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 +387,41 @@ 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) {
FragmentManager fm = getSupportFragmentManager();
new BluetoothConnecterDialogFragment().show(fm,
BluetoothConnecterDialogFragment.TAG);
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

View File

@@ -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<ContactItem> getContactItem() {
return contactItem;
}

View File

@@ -3,11 +3,13 @@ package org.briarproject.briar.android.util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.net.Uri;
import android.os.PowerManager;
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.os.Build.MANUFACTURER;
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.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.FORMAT_ABBREV_ALL;
@@ -331,6 +334,19 @@ public class UiUtils {
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() {
return (SDK_INT == 24 || SDK_INT == 25) &&
MANUFACTURER.equalsIgnoreCase("Samsung");
@@ -474,6 +490,25 @@ public class UiUtils {
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) {
Drawable icon =
VectorDrawableCompat.create(ctx.getResources(), resId, null);

View File

@@ -23,6 +23,12 @@
app:showAsAction="never"
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
android:id="@+id/action_delete_all_messages"
android:title="@string/delete_all_messages"

View File

@@ -142,6 +142,7 @@
<string name="allow">Allow</string>
<string name="open">Open</string>
<string name="change">Change</string>
<string name="start">Start</string>
<string name="no_data">No data</string>
<string name="ellipsis"></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_hint">Contact name</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." -->
<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." -->

View File

@@ -94,5 +94,6 @@ internal class HeadlessModule(private val appDir: File) {
override fun shouldEnableImageAttachments() = false
override fun shouldEnableProfilePictures() = 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 shouldEnableProfilePictures() = false
override fun shouldEnableDisappearingMessages() = false
override fun shouldEnableConnectViaBluetooth() = false
}
@Provides