Resolve merge conflict with 1872-key-agreement

This commit is contained in:
ameba23
2021-04-08 12:23:18 +02:00
6 changed files with 107 additions and 57 deletions

View File

@@ -25,7 +25,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -50,12 +49,6 @@ public class AddNearbyContactActivity extends BriarActivity
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private AddNearbyContactViewModel viewModel; private AddNearbyContactViewModel viewModel;
private AddNearbyContactPermissionManager permissionManager;
private final ActivityResultLauncher<String[]> permissionLauncher =
registerForActivityResult(new RequestMultiplePermissions(), r ->
permissionManager.onRequestPermissionResult(r,
viewModel::showQrCodeFragmentIfAllowed));
private final ActivityResultLauncher<Integer> bluetoothLauncher = private final ActivityResultLauncher<Integer> bluetoothLauncher =
registerForActivityResult(new RequestBluetoothDiscoverable(), registerForActivityResult(new RequestBluetoothDiscoverable(),
this::onBluetoothDiscoverableResult); this::onBluetoothDiscoverableResult);
@@ -65,8 +58,6 @@ public class AddNearbyContactActivity extends BriarActivity
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory) viewModel = new ViewModelProvider(this, viewModelFactory)
.get(AddNearbyContactViewModel.class); .get(AddNearbyContactViewModel.class);
permissionManager = new AddNearbyContactPermissionManager(this,
permissionLauncher::launch, viewModel.isBluetoothSupported());
} }
@Override @Override
@@ -79,8 +70,6 @@ public class AddNearbyContactActivity extends BriarActivity
if (state == null) { if (state == null) {
showInitialFragment(AddNearbyContactIntroFragment.newInstance()); showInitialFragment(AddNearbyContactIntroFragment.newInstance());
} }
viewModel.getCheckPermissions().observeEvent(this, check ->
permissionManager.checkPermissions());
viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r -> viewModel.getRequestBluetoothDiscoverable().observeEvent(this, r ->
requestBluetoothDiscoverable()); // never false requestBluetoothDiscoverable()); // never false
viewModel.getShowQrCodeFragment().observeEvent(this, show -> { viewModel.getShowQrCodeFragment().observeEvent(this, show -> {
@@ -92,13 +81,6 @@ public class AddNearbyContactActivity extends BriarActivity
.observe(this, this::onAddContactStateChanged); .observe(this, this::onAddContactStateChanged);
} }
@Override
public void onStart() {
super.onStart();
// Permissions may have been granted manually while we were stopped
permissionManager.resetPermissions();
}
@Override @Override
protected void onPostResume() { protected void onPostResume() {
super.onPostResume(); super.onPostResume();
@@ -143,17 +125,13 @@ public class AddNearbyContactActivity extends BriarActivity
} }
private void requestBluetoothDiscoverable() { private void requestBluetoothDiscoverable() {
if (!viewModel.isBluetoothSupported()) { Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE);
viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER); if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability");
viewModel.setBluetoothDecision(BluetoothDecision.WAITING);
bluetoothLauncher.launch(120); // 2min discoverable
} else { } else {
Intent i = new Intent(ACTION_REQUEST_DISCOVERABLE); viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER);
if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability");
viewModel.setBluetoothDecision(BluetoothDecision.WAITING);
bluetoothLauncher.launch(120); // 2min discoverable
} else {
viewModel.setBluetoothDecision(BluetoothDecision.NO_ADAPTER);
}
} }
} }
@@ -169,7 +147,7 @@ public class AddNearbyContactActivity extends BriarActivity
} }
} }
private void onAddContactStateChanged(AddContactState state) { private void onAddContactStateChanged(@Nullable AddContactState state) {
if (state instanceof ContactExchangeFinished) { if (state instanceof ContactExchangeFinished) {
ContactExchangeResult result = ContactExchangeResult result =
((ContactExchangeFinished) state).result; ((ContactExchangeFinished) state).result;

View File

@@ -100,7 +100,7 @@ public class AddNearbyContactFragment extends BaseFragment
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
cameraView.setPreviewConsumer(viewModel.qrCodeDecoder); cameraView.setPreviewConsumer(viewModel.getQrCodeDecoder());
} }
@Override @Override
@@ -153,7 +153,7 @@ public class AddNearbyContactFragment extends BaseFragment
} }
@UiThread @UiThread
private void onAddContactStateChanged(AddContactState state) { private void onAddContactStateChanged(@Nullable AddContactState state) {
if (state instanceof AddContactState.KeyAgreementListening) { if (state instanceof AddContactState.KeyAgreementListening) {
Bitmap qrCode = Bitmap qrCode =
((AddContactState.KeyAgreementListening) state).qrCode; ((AddContactState.KeyAgreementListening) state).qrCode;

View File

@@ -15,6 +15,8 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.view.View.FOCUS_DOWN; import static android.view.View.FOCUS_DOWN;
@@ -23,15 +25,22 @@ import static android.view.View.FOCUS_DOWN;
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class AddNearbyContactIntroFragment extends BaseFragment { public class AddNearbyContactIntroFragment extends BaseFragment {
public static final String TAG = AddNearbyContactIntroFragment.class.getName(); public static final String TAG =
AddNearbyContactIntroFragment.class.getName();
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private AddNearbyContactViewModel viewModel; private AddNearbyContactViewModel viewModel;
private AddNearbyContactPermissionManager permissionManager;
private ScrollView scrollView; private ScrollView scrollView;
private final ActivityResultLauncher<String[]> permissionLauncher =
registerForActivityResult(new RequestMultiplePermissions(), r ->
permissionManager.onRequestPermissionResult(r,
viewModel::showQrCodeFragmentIfAllowed));
public static AddNearbyContactIntroFragment newInstance() { public static AddNearbyContactIntroFragment newInstance() {
Bundle args = new Bundle(); Bundle args = new Bundle();
AddNearbyContactIntroFragment AddNearbyContactIntroFragment
@@ -45,6 +54,9 @@ public class AddNearbyContactIntroFragment extends BaseFragment {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory) viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(AddNearbyContactViewModel.class); .get(AddNearbyContactViewModel.class);
permissionManager = new AddNearbyContactPermissionManager(
requireActivity(), permissionLauncher::launch,
viewModel.isBluetoothSupported());
} }
@Nullable @Nullable
@@ -57,13 +69,17 @@ public class AddNearbyContactIntroFragment extends BaseFragment {
false); false);
scrollView = v.findViewById(R.id.scrollView); scrollView = v.findViewById(R.id.scrollView);
View button = v.findViewById(R.id.continueButton); View button = v.findViewById(R.id.continueButton);
button.setOnClickListener(view -> viewModel.onContinueClicked()); button.setOnClickListener(view -> viewModel.onContinueClicked(() ->
permissionManager.checkPermissions()
));
return v; return v;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
// Permissions may have been granted manually while we were stopped
permissionManager.resetPermissions();
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
} }

View File

@@ -1,20 +1,26 @@
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;
import org.briarproject.briar.android.activity.BaseActivity;
import java.util.Map; import java.util.Map;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity;
import static android.Manifest.permission.ACCESS_FINE_LOCATION; 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;
@@ -28,11 +34,11 @@ public class AddNearbyContactPermissionManager {
private Permission cameraPermission = Permission.UNKNOWN; private Permission cameraPermission = Permission.UNKNOWN;
private Permission locationPermission = Permission.UNKNOWN; private Permission locationPermission = Permission.UNKNOWN;
private final BaseActivity ctx; private final FragmentActivity ctx;
private final Consumer<String[]> requestPermissions; private final Consumer<String[]> requestPermissions;
private final boolean isBluetoothSupported; private final boolean isBluetoothSupported;
public AddNearbyContactPermissionManager(BaseActivity ctx, public AddNearbyContactPermissionManager(FragmentActivity ctx,
Consumer<String[]> requestPermissions, Consumer<String[]> requestPermissions,
boolean isBluetoothSupported) { boolean isBluetoothSupported) {
this.ctx = ctx; this.ctx = ctx;
@@ -45,6 +51,19 @@ public 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;
}
}
public static boolean areEssentialPermissionsGranted(Context ctx, public static boolean areEssentialPermissionsGranted(Context ctx,
boolean isBluetoothSupported) { boolean isBluetoothSupported) {
int ok = PERMISSION_GRANTED; int ok = PERMISSION_GRANTED;
@@ -54,14 +73,15 @@ public class AddNearbyContactPermissionManager {
!isBluetoothSupported); !isBluetoothSupported);
} }
boolean areEssentialPermissionsGranted() { private boolean areEssentialPermissionsGranted() {
return cameraPermission == Permission.GRANTED && return cameraPermission == Permission.GRANTED &&
(SDK_INT < 23 || locationPermission == Permission.GRANTED || (SDK_INT < 23 || locationPermission == Permission.GRANTED ||
!isBluetoothSupported); !isBluetoothSupported);
} }
public boolean checkPermissions() { public boolean checkPermissions() {
if (areEssentialPermissionsGranted()) return true; boolean locationEnabled = isLocationEnabled(ctx);
if (locationEnabled && areEssentialPermissionsGranted()) return true;
// If an essential permission has been permanently denied, ask the // If an essential permission has been permanently denied, ask the
// user to change the setting // user to change the setting
if (cameraPermission == Permission.PERMANENTLY_DENIED) { if (cameraPermission == Permission.PERMANENTLY_DENIED) {
@@ -86,8 +106,10 @@ public 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 { } else if (isLocationEnabled(ctx)) {
requestPermissions(); requestPermissions();
} else {
showLocationDialog(ctx);
} }
return false; return false;
} }
@@ -113,6 +135,25 @@ public 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

@@ -65,6 +65,7 @@ import javax.inject.Provider;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.core.util.Supplier;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -83,6 +84,8 @@ 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.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;
@@ -133,8 +136,6 @@ class AddNearbyContactViewModel extends AndroidViewModel
private final ContactExchangeManager contactExchangeManager; private final ContactExchangeManager contactExchangeManager;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
private final MutableLiveEvent<Boolean> checkPermissions =
new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> requestBluetoothDiscoverable = private final MutableLiveEvent<Boolean> requestBluetoothDiscoverable =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> showQrCodeFragment = private final MutableLiveEvent<Boolean> showQrCodeFragment =
@@ -142,8 +143,9 @@ class AddNearbyContactViewModel extends AndroidViewModel
private final MutableLiveData<AddContactState> state = private final MutableLiveData<AddContactState> state =
new MutableLiveData<>(); new MutableLiveData<>();
final QrCodeDecoder qrCodeDecoder; private final QrCodeDecoder qrCodeDecoder;
final BroadcastReceiver bluetoothReceiver = new BluetoothStateReceiver(); private final BroadcastReceiver bluetoothReceiver =
new BluetoothStateReceiver();
@Nullable @Nullable
private final BluetoothAdapter bt; private final BluetoothAdapter bt;
@@ -169,7 +171,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
private boolean hasEnabledBluetooth = false; private boolean hasEnabledBluetooth = false;
@Nullable @Nullable
private KeyAgreementTask task; private volatile KeyAgreementTask task;
private volatile boolean gotLocalPayload = false, gotRemotePayload = false; private volatile boolean gotLocalPayload = false, gotRemotePayload = false;
@Inject @Inject
@@ -211,13 +213,12 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
void onContinueClicked() { void onContinueClicked(Supplier<Boolean> checkPermissions) {
if (bluetoothDecision == REFUSED) { if (bluetoothDecision == REFUSED) {
bluetoothDecision = UNKNOWN; // Ask again bluetoothDecision = UNKNOWN; // Ask again
} }
wasContinueClicked = true; wasContinueClicked = true;
checkPermissions.setEvent(true); if (checkPermissions.get()) showQrCodeFragmentIfAllowed();
showQrCodeFragmentIfAllowed();
} }
@UiThread @UiThread
@@ -226,7 +227,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
boolean isWifiReady() { private boolean isWifiReady() {
if (wifiPlugin == null) return true; // Continue without wifi if (wifiPlugin == null) return true; // Continue without wifi
State state = wifiPlugin.getState(); State state = wifiPlugin.getState();
// Wait for plugin to become enabled // Wait for plugin to become enabled
@@ -234,7 +235,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
boolean isBluetoothReady() { private boolean isBluetoothReady() {
if (bt == null || bluetoothPlugin == null) { if (bt == null || bluetoothPlugin == null) {
// Continue without Bluetooth // Continue without Bluetooth
return true; return true;
@@ -254,7 +255,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
void enableWifiIfWeShould() { private void enableWifiIfWeShould() {
if (hasEnabledWifi) return; if (hasEnabledWifi) return;
if (wifiPlugin == null) return; if (wifiPlugin == null) return;
State state = wifiPlugin.getState(); State state = wifiPlugin.getState();
@@ -266,7 +267,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
void enableBluetoothIfWeShould() { private void enableBluetoothIfWeShould() {
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return; if (bluetoothDecision != BluetoothDecision.ACCEPTED) return;
if (hasEnabledBluetooth) return; if (hasEnabledBluetooth) return;
if (bluetoothPlugin == null || !isBluetoothSupported()) return; if (bluetoothPlugin == null || !isBluetoothSupported()) return;
@@ -279,7 +280,7 @@ class AddNearbyContactViewModel extends AndroidViewModel
} }
@UiThread @UiThread
void startAddingContact() { private void startAddingContact() {
// If we return to the intro fragment, the continue button needs to be // If we return to the intro fragment, the continue button needs to be
// clicked again before showing the QR code fragment // clicked again before showing the QR code fragment
wasContinueClicked = false; wasContinueClicked = false;
@@ -290,6 +291,8 @@ class AddNearbyContactViewModel extends AndroidViewModel
// Bluetooth again // Bluetooth again
hasEnabledWifi = false; hasEnabledWifi = false;
hasEnabledBluetooth = false; hasEnabledBluetooth = false;
// reset state, so we don't show an old QR code again
state.setValue(null);
// start to listen with a KeyAgreementTask // start to listen with a KeyAgreementTask
startListening(); startListening();
showQrCodeFragment.setEvent(true); showQrCodeFragment.setEvent(true);
@@ -365,14 +368,20 @@ class AddNearbyContactViewModel extends AndroidViewModel
void showQrCodeFragmentIfAllowed() { void showQrCodeFragmentIfAllowed() {
boolean permissionsGranted = areEssentialPermissionsGranted( boolean permissionsGranted = areEssentialPermissionsGranted(
getApplication(), isBluetoothSupported()); getApplication(), isBluetoothSupported());
if (isActivityResumed && wasContinueClicked && permissionsGranted) { boolean locationEnabled = isLocationEnabled(getApplication());
if (isActivityResumed && wasContinueClicked && permissionsGranted &&
locationEnabled) {
if (isWifiReady() && isBluetoothReady()) { if (isWifiReady() && isBluetoothReady()) {
LOG.info("Wifi and Bluetooth are ready"); LOG.info("Wifi and Bluetooth are ready");
startAddingContact(); startAddingContact();
} else { } else {
enableWifiIfWeShould(); enableWifiIfWeShould();
if (bluetoothDecision == UNKNOWN) { if (bluetoothDecision == UNKNOWN) {
requestBluetoothDiscoverable.setEvent(true); if (isBluetoothSupported()) {
requestBluetoothDiscoverable.setEvent(true);
} else {
bluetoothDecision = NO_ADAPTER;
}
} else if (bluetoothDecision == REFUSED) { } else if (bluetoothDecision == REFUSED) {
// Ask again when the user clicks "continue" // Ask again when the user clicks "continue"
} else { } else {
@@ -501,8 +510,8 @@ class AddNearbyContactViewModel extends AndroidViewModel
showQrCodeFragmentIfAllowed(); showQrCodeFragmentIfAllowed();
} }
LiveEvent<Boolean> getCheckPermissions() { QrCodeDecoder getQrCodeDecoder() {
return checkPermissions; return qrCodeDecoder;
} }
LiveEvent<Boolean> getRequestBluetoothDiscoverable() { LiveEvent<Boolean> getRequestBluetoothDiscoverable() {
@@ -513,6 +522,9 @@ class AddNearbyContactViewModel extends AndroidViewModel
return showQrCodeFragment; return showQrCodeFragment;
} }
/**
* This LiveData will be null initially.
*/
LiveData<AddContactState> getState() { LiveData<AddContactState> getState() {
return state; return state;
} }

View File

@@ -609,6 +609,9 @@
<string name="permission_camera_location_request_body">To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string> <string name="permission_camera_location_request_body">To scan the QR code, Briar needs access to the camera.\n\nTo discover Bluetooth devices, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
<string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string> <string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string>
<string name="permission_location_denied_body">You have denied access to your location, but Briar needs this permission to discover Bluetooth devices.\n\nPlease consider granting access.</string> <string name="permission_location_denied_body">You have denied access to your location, but Briar needs this permission to discover Bluetooth devices.\n\nPlease consider granting access.</string>
<string name="permission_location_setting_title">Location setting</string>
<string name="permission_location_setting_body">Your device\'s location setting must be turned on to find other devices via Bluetooth. Please enable location to continue. You can disable it again afterwards.</string>
<string name="permission_location_setting_button">Enable location</string>
<string name="qr_code">QR code</string> <string name="qr_code">QR code</string>
<string name="show_qr_code_fullscreen">Show QR code fullscreen</string> <string name="show_qr_code_fullscreen">Show QR code fullscreen</string>