Merge branch '2162-mailbox-pairing-ui-end' into 'master'

Implement final parts of UI for pairing Briar with mailbox

Closes #2162

See merge request briar/briar!1590
This commit is contained in:
akwizgran
2022-02-25 14:27:34 +00:00
17 changed files with 402 additions and 192 deletions

View File

@@ -1,59 +1,25 @@
package org.briarproject.bramble.api.mailbox;
import javax.annotation.Nullable;
public abstract class MailboxPairingState {
/**
* The QR code payload that was scanned by the user.
* This is null if the code should not be re-used anymore in this state.
*/
@Nullable
public final String qrCodePayload;
MailboxPairingState(@Nullable String qrCodePayload) {
this.qrCodePayload = qrCodePayload;
}
public static class QrCodeReceived extends MailboxPairingState {
public QrCodeReceived(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class Pairing extends MailboxPairingState {
public Pairing(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class Paired extends MailboxPairingState {
public Paired() {
super(null);
}
}
public static class InvalidQrCode extends MailboxPairingState {
public InvalidQrCode() {
super(null);
}
}
public static class MailboxAlreadyPaired extends MailboxPairingState {
public MailboxAlreadyPaired() {
super(null);
}
}
public static class ConnectionError extends MailboxPairingState {
public ConnectionError(String qrCodePayload) {
super(qrCodePayload);
}
}
public static class UnexpectedError extends MailboxPairingState {
public UnexpectedError(String qrCodePayload) {
super(qrCodePayload);
}
}
}

View File

@@ -71,7 +71,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.clock = clock;
this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager;
state = new MailboxPairingState.QrCodeReceived(payload);
state = new MailboxPairingState.QrCodeReceived();
}
@Override
@@ -100,15 +100,15 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired());
} catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError(payload));
onMailboxError(e, new MailboxPairingState.ConnectionError());
} catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError(payload));
onMailboxError(e, new MailboxPairingState.UnexpectedError());
}
}
private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing(payload));
setState(new MailboxPairingState.Pairing());
MailboxAuthToken ownerToken = api.setup(mailboxProperties);
MailboxProperties ownerProperties = new MailboxProperties(
mailboxProperties.getBaseUrl(), ownerToken, true);

View File

@@ -46,6 +46,7 @@
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:theme="@style/BriarTheme"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"

View File

@@ -44,9 +44,12 @@ import org.briarproject.briar.android.hotspot.ManualHotspotFragment;
import org.briarproject.briar.android.hotspot.QrHotspotFragment;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.mailbox.ErrorFragment;
import org.briarproject.briar.android.mailbox.MailboxScanFragment;
import org.briarproject.briar.android.mailbox.MailboxStatusFragment;
import org.briarproject.briar.android.mailbox.OfflineFragment;
import org.briarproject.briar.android.mailbox.SetupDownloadFragment;
import org.briarproject.briar.android.mailbox.SetupIntroFragment;
import org.briarproject.briar.android.removabledrive.ChooserFragment;
import org.briarproject.briar.android.removabledrive.ReceiveFragment;
import org.briarproject.briar.android.removabledrive.SendFragment;
@@ -243,9 +246,15 @@ public interface AndroidComponent
void inject(BluetoothIntroFragment bluetoothIntroFragment);
void inject(SetupIntroFragment setupIntroFragment);
void inject(SetupDownloadFragment setupDownloadFragment);
void inject(MailboxScanFragment mailboxScanFragment);
void inject(OfflineFragment offlineFragment);
void inject(ErrorFragment errorFragment);
void inject(MailboxStatusFragment mailboxStatusFragment);
}

View File

@@ -63,6 +63,13 @@ public class FinalFragment extends Fragment {
private NestedScrollView scrollView;
protected Button buttonView;
protected final OnBackPressedCallback onBackPressedCallback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackButtonPressed();
}
};
@Nullable
@Override
@@ -95,13 +102,8 @@ public class FinalFragment extends Fragment {
AppCompatActivity a = (AppCompatActivity) requireActivity();
a.setTitle(args.getInt(ARG_TITLE));
a.getOnBackPressedDispatcher().addCallback(
getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
onBackButtonPressed();
}
});
a.getOnBackPressedDispatcher()
.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return v;
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.mailbox;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -10,8 +11,14 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.FinalFragment;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -29,21 +36,33 @@ public class ErrorFragment extends FinalFragment {
return f;
}
@Inject
ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(MailboxViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
// Do not hijack back button events, but let the activity process them
onBackPressedCallback.remove();
buttonView.setText(R.string.try_again_button);
buttonView.setOnClickListener(view -> viewModel.showDownloadFragment());
return v;
}
@Override
protected void onBackButtonPressed() {
requireActivity().getSupportFragmentManager().popBackStack();
}
@Override
protected boolean shouldHideActionBarBackButton() {
return false;

View File

@@ -3,19 +3,20 @@ package org.briarproject.briar.android.mailbox;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
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.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.FinalFragment;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import static android.view.View.INVISIBLE;
@@ -53,16 +54,20 @@ public class MailboxActivity extends BriarActivity {
viewModel.getState().observeEvent(this, state -> {
if (state instanceof MailboxState.NotSetup) {
onNotSetup();
} else if (state instanceof MailboxState.ShowDownload) {
onShowDownload();
} else if (state instanceof MailboxState.ScanningQrCode) {
onScanningQrCode();
} else if (state instanceof MailboxState.SettingUp) {
onCodeScanned();
} else if (state instanceof MailboxState.QrCodeWrong) {
onQrCodeWrong();
} else if (state instanceof MailboxState.OfflineInSetup) {
} else if (state instanceof MailboxState.Pairing) {
MailboxPairingState s =
((MailboxState.Pairing) state).pairingState;
onMailboxPairingStateChanged(s);
} else if (state instanceof MailboxState.OfflineWhenPairing) {
onOffline();
} else if (state instanceof MailboxState.IsSetup) {
onIsSetup(((MailboxState.IsSetup) state).mailboxStatus);
} else if (state instanceof MailboxState.CameraError) {
onCameraError();
} else if (state instanceof MailboxState.IsPaired) {
onIsPaired();
} else {
throw new AssertionError("Unknown state: " + state);
}
@@ -80,9 +85,10 @@ public class MailboxActivity extends BriarActivity {
@Override
public void onBackPressed() {
if (viewModel.getState()
.getLastValue() instanceof MailboxState.SettingUp) {
// don't go back in flow if we are already setting up mailbox
MailboxState s = viewModel.getState().getLastValue();
if (s instanceof MailboxState.Pairing) {
// don't go back in the flow if we are already pairing
// with the mailbox. We provide a try-again button instead.
supportFinishAfterTransition();
} else {
super.onBackPressed();
@@ -97,22 +103,70 @@ public class MailboxActivity extends BriarActivity {
.commit();
}
private void onShowDownload() {
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(SetupDownloadFragment.TAG) != null) {
// if the fragment is already on the back stack, pop back to it
// instead of adding it to the stack again
fm.popBackStackImmediate(SetupDownloadFragment.TAG, 0);
} else {
showFragment(fm, new SetupDownloadFragment(),
SetupDownloadFragment.TAG);
}
}
private void onScanningQrCode() {
showFragment(getSupportFragmentManager(), new MailboxScanFragment(),
MailboxScanFragment.TAG);
}
private void onCodeScanned() {
showFragment(getSupportFragmentManager(),
new MailboxConnectingFragment(),
MailboxConnectingFragment.TAG, false);
}
private void onQrCodeWrong() {
Fragment f = ErrorFragment.newInstance(
R.string.mailbox_setup_qr_code_wrong_title,
R.string.mailbox_setup_qr_code_wrong_description);
showFragment(getSupportFragmentManager(), f, ErrorFragment.TAG);
private void onMailboxPairingStateChanged(MailboxPairingState s) {
progressBar.setVisibility(INVISIBLE);
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() == 0) {
// We re-launched into an existing state,
// need to re-populate the back stack.
onNotSetup();
onShowDownload();
}
Fragment f;
String tag;
if (s instanceof MailboxPairingState.QrCodeReceived) {
f = new MailboxConnectingFragment();
tag = MailboxConnectingFragment.TAG;
} else if (s instanceof MailboxPairingState.Pairing) {
f = new MailboxConnectingFragment();
tag = MailboxConnectingFragment.TAG;
} else if (s instanceof MailboxPairingState.InvalidQrCode) {
f = ErrorFragment.newInstance(
R.string.mailbox_setup_qr_code_wrong_title,
R.string.mailbox_setup_qr_code_wrong_description);
tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.MailboxAlreadyPaired) {
f = ErrorFragment.newInstance(
R.string.mailbox_setup_already_paired_title,
R.string.mailbox_setup_already_paired_description);
tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.ConnectionError) {
f = ErrorFragment.newInstance(
R.string.mailbox_setup_io_error_title,
R.string.mailbox_setup_io_error_description);
tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.UnexpectedError) {
f = ErrorFragment.newInstance(
R.string.mailbox_setup_assertion_error_title,
R.string.mailbox_setup_assertion_error_description);
tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.Paired) {
f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title,
R.drawable.ic_check_circle_outline,
R.color.briar_brand_green,
R.string.mailbox_setup_paired_description);
tag = FinalFragment.TAG;
} else {
throw new IllegalStateException("Unhandled state: " + s.getClass());
}
showFragment(fm, f, tag);
}
private void onOffline() {
@@ -120,9 +174,17 @@ public class MailboxActivity extends BriarActivity {
OfflineFragment.TAG);
}
private void onIsSetup(MailboxStatus mailboxStatus) {
// TODO
Toast.makeText(this, "NOT IMPLEMENTED", Toast.LENGTH_LONG).show();
private void onCameraError() {
Fragment f = ErrorFragment.newInstance(
R.string.mailbox_setup_camera_error_title,
R.string.mailbox_setup_camera_error_description);
showFragment(getSupportFragmentManager(), f, ErrorFragment.TAG);
}
private void onIsPaired() {
progressBar.setVisibility(INVISIBLE);
showFragment(getSupportFragmentManager(), new MailboxStatusFragment(),
MailboxStatusFragment.TAG, false);
}
}

View File

@@ -94,7 +94,7 @@ public class MailboxScanFragment extends Fragment {
logException(LOG, WARNING, e);
Toast.makeText(requireContext(), R.string.camera_error,
LENGTH_LONG).show();
requireActivity().getSupportFragmentManager().popBackStack();
viewModel.onCameraError();
}
}

View File

@@ -1,41 +1,37 @@
package org.briarproject.briar.android.mailbox;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import androidx.annotation.Nullable;
class MailboxState {
static class NotSetup extends MailboxState {
}
static class ShowDownload extends MailboxState {
}
static class ScanningQrCode extends MailboxState {
}
static class SettingUp extends MailboxState {
}
static class Pairing extends MailboxState {
final MailboxPairingState pairingState;
static class QrCodeWrong extends MailboxState {
}
static class OfflineInSetup extends MailboxState {
@Nullable
final MailboxProperties mailboxProperties;
OfflineInSetup(@Nullable MailboxProperties mailboxProperties) {
this.mailboxProperties = mailboxProperties;
}
OfflineInSetup() {
this(null);
Pairing(MailboxPairingState pairingState) {
this.pairingState = pairingState;
}
}
static class IsSetup extends MailboxState {
static class OfflineWhenPairing extends MailboxState {
}
static class CameraError extends MailboxState {
}
static class IsPaired extends MailboxState {
final MailboxStatus mailboxStatus;
IsSetup(MailboxStatus mailboxStatus) {
IsPaired(MailboxStatus mailboxStatus) {
this.mailboxStatus = mailboxStatus;
}
}

View File

@@ -0,0 +1,86 @@
package org.briarproject.briar.android.mailbox;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class MailboxStatusFragment extends Fragment {
static final String TAG = MailboxStatusFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(MailboxViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_mailbox_status,
container, false);
}
@Override
public void onViewCreated(View v, @Nullable Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
MailboxState.IsPaired state =
(MailboxState.IsPaired) viewModel.getState().getLastValue();
requireNonNull(state); // TODO check assumption
TextView statusInfoView = v.findViewById(R.id.statusInfoView);
long lastSuccess = state.mailboxStatus.getTimeOfLastSuccess();
String lastConnectionText;
if (lastSuccess < 0) {
lastConnectionText =
getString(R.string.mailbox_status_connected_never);
} else {
lastConnectionText = formatDate(requireContext(), lastSuccess);
}
String statusInfoText = getString(
R.string.mailbox_status_connected_info, lastConnectionText);
statusInfoView.setText(statusInfoText);
// TODO
// * react to status changes
// * detect problems and show them
// * update connection time periodically like conversation timestamps
// * add "Check connection" button
// * add "Unlink" button with confirmation dialog
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(R.string.mailbox_status_title);
}
}

View File

@@ -4,15 +4,14 @@ import android.app.Application;
import com.google.zxing.Result;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
@@ -25,38 +24,34 @@ import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@NotNullByDefault
class MailboxViewModel extends DbViewModel
implements QrCodeDecoder.ResultCallback {
implements QrCodeDecoder.ResultCallback, Consumer<MailboxPairingState> {
private static final Logger LOG =
getLogger(MailboxViewModel.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final CryptoComponent crypto;
private final QrCodeDecoder qrCodeDecoder;
private final PluginManager pluginManager;
private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxManager mailboxManager;
private final MutableLiveEvent<MailboxState> state =
new MutableLiveEvent<>();
@Nullable
private MailboxPairingTask pairingTask = null;
@Inject
MailboxViewModel(
@@ -66,29 +61,43 @@ class MailboxViewModel extends DbViewModel
TransactionManager db,
AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor,
CryptoComponent crypto,
PluginManager pluginManager,
MailboxSettingsManager mailboxSettingsManager) {
MailboxManager mailboxManager) {
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.crypto = crypto;
this.pluginManager = pluginManager;
this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxManager = mailboxManager;
qrCodeDecoder = new QrCodeDecoder(androidExecutor, ioExecutor, this);
checkIfSetup();
}
@Override
protected void onCleared() {
super.onCleared();
MailboxPairingTask task = pairingTask;
if (task != null) {
task.removeObserver(this);
pairingTask = null;
}
}
@UiThread
private void checkIfSetup() {
runOnDbThread(true, txn -> {
MailboxProperties props =
mailboxSettingsManager.getOwnMailboxProperties(txn);
if (props == null) state.postEvent(new NotSetup());
else {
MailboxStatus mailboxStatus =
mailboxSettingsManager.getOwnMailboxStatus(txn);
state.postEvent(new MailboxState.IsSetup(mailboxStatus));
}
}, this::handleException);
MailboxPairingTask task = mailboxManager.getCurrentPairingTask();
if (task == null) {
runOnDbThread(true, txn -> {
boolean isPaired = mailboxManager.isPaired(txn);
if (isPaired) {
MailboxStatus mailboxStatus =
mailboxManager.getMailboxStatus(txn);
state.postEvent(new MailboxState.IsPaired(mailboxStatus));
} else {
state.postEvent(new NotSetup());
}
}, this::handleException);
} else {
task.addObserver(this);
pairingTask = task;
}
}
@UiThread
@@ -96,76 +105,50 @@ class MailboxViewModel extends DbViewModel
if (isTorActive()) {
state.setEvent(new MailboxState.ScanningQrCode());
} else {
state.setEvent(new MailboxState.OfflineInSetup());
state.setEvent(new MailboxState.OfflineWhenPairing());
}
}
@UiThread
void onCameraError() {
state.setEvent(new MailboxState.CameraError());
}
@Override
@IoExecutor
public void onQrCodeDecoded(Result result) {
LOG.info("Got result from decoder");
MailboxProperties properties;
try {
properties = decodeQrCode(result.getText());
} catch (FormatException e) {
state.postEvent(new MailboxState.QrCodeWrong());
return;
}
onMailboxPropertiesReceived(properties);
onQrCodePayloadReceived(result.getText());
}
@IoExecutor
// TODO move this into core #2168
private MailboxProperties decodeQrCode(String payload)
throws FormatException {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length != 65) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code length is not 65: " + bytes.length);
}
throw new FormatException();
}
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onionAddress = crypto.encodeOnionAddress(onionPubKey);
byte[] tokenBytes = Arrays.copyOfRange(bytes, 33, 65);
MailboxAuthToken setupToken = new MailboxAuthToken(tokenBytes);
return new MailboxProperties(onionAddress, setupToken, true);
}
private void onMailboxPropertiesReceived(MailboxProperties properties) {
@AnyThread
private void onQrCodePayloadReceived(String qrCodePayload) {
if (isTorActive()) {
// TODO pass props to core #2168
state.postEvent(new MailboxState.SettingUp());
pairingTask = mailboxManager.startPairingTask(qrCodePayload);
pairingTask.addObserver(this);
} else {
state.postEvent(new MailboxState.OfflineInSetup(properties));
state.postEvent(new MailboxState.OfflineWhenPairing());
}
}
// TODO ideally also move this into core #2168
@UiThread
@Override
public void accept(MailboxPairingState mailboxPairingState) {
if (LOG.isLoggable(INFO)) {
LOG.info("New pairing state: " +
mailboxPairingState.getClass().getSimpleName());
}
state.setEvent(new MailboxState.Pairing(mailboxPairingState));
}
private boolean isTorActive() {
Plugin plugin = pluginManager.getPlugin(TorConstants.ID);
return plugin != null && plugin.getState() == ACTIVE;
}
@UiThread
void tryAgainWhenOffline() {
MailboxState.OfflineInSetup offline =
(MailboxState.OfflineInSetup) requireNonNull(
state.getLastValue());
if (offline.mailboxProperties == null) {
onScanButtonClicked();
} else {
onMailboxPropertiesReceived(offline.mailboxProperties);
}
void showDownloadFragment() {
state.setEvent(new MailboxState.ShowDownload());
}
@UiThread

View File

@@ -62,10 +62,7 @@ public class OfflineFragment extends Fragment {
startActivity(i);
});
buttonView = v.findViewById(R.id.button);
buttonView.setOnClickListener(view -> {
getParentFragmentManager().popBackStackImmediate();
viewModel.tryAgainWhenOffline();
});
buttonView.setOnClickListener(view -> viewModel.showDownloadFragment());
return v;
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.mailbox;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,12 +12,15 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static android.view.View.FOCUS_DOWN;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -24,8 +28,22 @@ public class SetupIntroFragment extends Fragment {
static final String TAG = SetupIntroFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private MailboxViewModel viewModel;
private ScrollView scrollView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(MailboxViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@@ -35,11 +53,7 @@ public class SetupIntroFragment extends Fragment {
container, false);
scrollView = v.findViewById(R.id.scrollView);
Button button = v.findViewById(R.id.continueButton);
button.setOnClickListener(view -> {
FragmentManager fm = getParentFragmentManager();
Fragment f = new SetupDownloadFragment();
showFragment(fm, f, SetupDownloadFragment.TAG);
});
button.setOnClickListener(view -> viewModel.showDownloadFragment());
return v;
}

View File

@@ -35,7 +35,7 @@
android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="@dimen/margin_large"
android:text="@string/mailbox_setup_intro"
android:text="@string/mailbox_setup_download"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/scanButton"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".android.mailbox.MailboxActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/statusTitleView"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_check_circle_outline"
app:tint="@color/briar_brand_green"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/statusTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/mailbox_status_connected_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView" />
<TextView
android:id="@+id/statusInfoView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
app:layout_constraintVertical_bias="0.0"
tools:text="@string/mailbox_status_connected_info" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -614,7 +614,7 @@
<!-- Mailbox -->
<string name="mailbox_settings_title">Mailbox</string>
<string name="mailbox_setup_title">Mailbox Setup</string>
<string name="mailbox_setup_title">Mailbox setup</string>
<string name="mailbox_setup_intro">A Mailbox enables your contacts to send you messages while you are offline. The Mailbox will receive your messages and store them until you come online.\n
\nYou can install the Briar Mailbox app on a spare device. Keep it connected to power and Wi-Fi so it\'s always online.</string>
<string name="mailbox_setup_download">First, install the Mailbox app on another device by searching for \"Briar Mailbox\" on Google Play or wherever you downloaded Briar.\n
@@ -624,10 +624,28 @@
<string name="permission_camera_qr_denied_body">You have denied access to the camera, but scanning a QR code requires using the camera.\n\nPlease consider granting access.</string>
<string name="mailbox_setup_connecting">Connecting…</string>
<string name="mailbox_setup_qr_code_wrong_title">Wrong QR code</string>
<string name="mailbox_setup_qr_code_wrong_description">The scanned code is invalid. Please open the Briar Mailbox app on your mailbox device and scan the QR code it presents.</string>
<string name="mailbox_setup_qr_code_wrong_description">The scanned code is invalid. Please open the Briar Mailbox app on your Mailbox device and scan the QR code it presents.</string>
<string name="mailbox_setup_already_paired_title">Mailbox already linked</string>
<string name="mailbox_setup_already_paired_description">Unlink the Mailbox on your other device and try again.</string>
<string name="mailbox_setup_io_error_title">Could not connect</string>
<string name="mailbox_setup_io_error_description">Ensure that both devices are connected to the Internet and try again.</string>
<string name="mailbox_setup_assertion_error_title">Mailbox error</string>
<string name="mailbox_setup_assertion_error_description">Please send feedback (with anonymous data) via the Briar app if the issue persists.</string>
<string name="mailbox_setup_camera_error_title" translatable="false">@string/camera_error</string>
<string name="mailbox_setup_camera_error_description">Could not access camera. Try again, maybe after rebooting device.</string>
<string name="mailbox_setup_paired_title">Connected</string>
<string name="mailbox_setup_paired_description">Your Mailbox has been successfully linked with Briar.\n
\nKeep your Mailbox connected to power and Wi-Fi so it\'s always online.</string>
<string name="tor_offline_title">Offline</string>
<string name="tor_offline_description">Ensure that this device is online and connections to the internet are allowed.\n\nAfterwards, wait for the globe icon in connection settings to turn green.</string>
<string name="tor_offline_description">Ensure that this device is online and connections to the Internet are allowed.\n
\nAfterwards, wait for the globe icon in the connection settings screen to turn green.</string>
<string name="tor_offline_button_check">Check connection settings</string>
<string name="mailbox_status_title">Mailbox status</string>
<string name="mailbox_status_connected_title">Mailbox is running</string>
<!-- Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Last connection: %s</string>
<!-- Indicates that there never was a connection to the mailbox. Last connection: Never -->
<string name="mailbox_status_connected_never">Never</string>
<!-- Conversation Settings -->
<string name="disappearing_messages_title">Disappearing messages</string>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">onion</domain>
</domain-config>
</network-security-config>