mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user