Merge branch '2403-show-progress-while-connecting-to-mailbox' into 'master'

Show progress while connecting to mailbox

Closes #2403

See merge request briar/briar!1747
This commit is contained in:
akwizgran
2022-12-14 12:20:43 +00:00
7 changed files with 128 additions and 32 deletions

View File

@@ -2,10 +2,27 @@ package org.briarproject.bramble.api.mailbox;
public abstract class MailboxPairingState { public abstract class MailboxPairingState {
public static class QrCodeReceived extends MailboxPairingState { public abstract static class Pending extends MailboxPairingState {
public final long timeStarted;
private Pending(long timeStarted) {
this.timeStarted = timeStarted;
}
} }
public static class Pairing extends MailboxPairingState { public static class QrCodeReceived extends Pending {
public QrCodeReceived(long timeStarted) {
super(timeStarted);
}
}
public static class Pairing extends Pending {
public Pairing(long timeStarted) {
super(timeStarted);
}
} }
public static class Paired extends MailboxPairingState { public static class Paired extends MailboxPairingState {

View File

@@ -52,6 +52,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final long timeStarted;
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
@@ -77,7 +78,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
state = new MailboxPairingState.QrCodeReceived(); timeStarted = clock.currentTimeMillis();
state = new MailboxPairingState.QrCodeReceived(timeStarted);
} }
@Override @Override
@@ -114,7 +116,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private void pairMailbox() throws IOException, ApiException, DbException { private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload); MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing()); setState(new MailboxPairingState.Pairing(timeStarted));
MailboxProperties ownerProperties = api.setup(mailboxProperties); MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis(); long time = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {

View File

@@ -68,8 +68,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testInitialQrCodeReceivedState() { public void testInitialQrCodeReceivedState() {
MailboxPairingTask task = MailboxPairingTask task = createPairingTask(getRandomString(42));
factory.createPairingTask(getRandomString(42));
task.addObserver(state -> task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.QrCodeReceived) assertTrue(state instanceof MailboxPairingState.QrCodeReceived)
); );
@@ -77,15 +76,14 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testInvalidQrCode() { public void testInvalidQrCode() {
MailboxPairingTask task1 = MailboxPairingTask task1 = createPairingTask(getRandomString(42));
factory.createPairingTask(getRandomString(42));
task1.run(); task1.run();
task1.addObserver(state -> task1.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode) assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
); );
String goodLength = "00" + getRandomString(63); String goodLength = "00" + getRandomString(63);
MailboxPairingTask task2 = factory.createPairingTask(goodLength); MailboxPairingTask task2 = createPairingTask(goodLength);
task2.run(); task2.run();
task2.addObserver(state -> task2.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode) assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
@@ -121,7 +119,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
}}); }});
AtomicInteger i = new AtomicInteger(0); AtomicInteger i = new AtomicInteger(0);
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.addObserver(state -> { task.addObserver(state -> {
if (i.get() == 0) { if (i.get() == 0) {
assertEquals(MailboxPairingState.QrCodeReceived.class, assertEquals(MailboxPairingState.QrCodeReceived.class,
@@ -165,7 +163,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(e)); will(throwException(e));
}}); }});
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.run(); task.run();
task.addObserver(state -> assertEquals(state.getClass(), s)); task.addObserver(state -> assertEquals(state.getClass(), s));
} }
@@ -188,7 +186,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(new DbException())); will(throwException(new DbException()));
}}); }});
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.run(); task.run();
task.addObserver(state -> assertEquals(state.getClass(), task.addObserver(state -> assertEquals(state.getClass(),
MailboxPairingState.UnexpectedError.class)); MailboxPairingState.UnexpectedError.class));
@@ -202,4 +200,12 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
p1.getServerSupports().equals(p2.getServerSupports())); p1.getServerSupports().equals(p2.getServerSupports()));
} }
private MailboxPairingTask createPairingTask(String qrCodePayload) {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
return factory.createPairingTask(qrCodePayload);
}
} }

View File

@@ -158,11 +158,9 @@ public class MailboxActivity extends BriarActivity {
} }
Fragment f; Fragment f;
String tag; String tag;
if (s instanceof MailboxPairingState.QrCodeReceived) { if (s instanceof MailboxPairingState.Pending) {
f = new MailboxConnectingFragment(); long timeStarted = ((MailboxPairingState.Pending) s).timeStarted;
tag = MailboxConnectingFragment.TAG; f = MailboxConnectingFragment.newInstance(timeStarted);
} else if (s instanceof MailboxPairingState.Pairing) {
f = new MailboxConnectingFragment();
tag = MailboxConnectingFragment.TAG; tag = MailboxConnectingFragment.TAG;
} else if (s instanceof MailboxPairingState.InvalidQrCode) { } else if (s instanceof MailboxPairingState.InvalidQrCode) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(

View File

@@ -1,10 +1,15 @@
package org.briarproject.briar.android.mailbox; package org.briarproject.briar.android.mailbox;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -12,25 +17,71 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static org.briarproject.briar.android.util.UiUtils.formatDuration;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class MailboxConnectingFragment extends Fragment { public class MailboxConnectingFragment extends Fragment {
static final String TAG = MailboxConnectingFragment.class.getName(); static final String TAG = MailboxConnectingFragment.class.getName();
private static final String ARG_STARTED = "started";
private static final long TIMEOUT_MS = TorConstants.EXTRA_CONNECT_TIMEOUT;
private static final long REFRESH_INTERVAL_MS = 1_000;
private final Handler handler = new Handler(Looper.getMainLooper());
// Capture a method reference so we use the same reference for posting
// and removing
private final Runnable refresher = this::updateProgressBar;
private ProgressBar progressBar;
private long timeStarted;
public static MailboxConnectingFragment newInstance(long timeStarted) {
MailboxConnectingFragment f = new MailboxConnectingFragment();
Bundle args = new Bundle();
args.putLong(ARG_STARTED, timeStarted);
f.setArguments(args);
return f;
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_mailbox_connecting, View v = inflater.inflate(R.layout.fragment_mailbox_connecting,
container, false); container, false);
progressBar = v.findViewById(R.id.progressBar);
TextView info = v.findViewById(R.id.info);
String duration = formatDuration(requireContext(), TIMEOUT_MS);
info.setText(getString(R.string.mailbox_setup_connecting_info,
duration));
timeStarted = requireArguments().getLong(ARG_STARTED);
return v;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
requireActivity().setTitle(R.string.mailbox_setup_title); requireActivity().setTitle(R.string.mailbox_setup_title);
updateProgressBar();
} }
@Override
public void onStop() {
super.onStop();
handler.removeCallbacks(refresher);
}
private void updateProgressBar() {
long elapsedMs = System.currentTimeMillis() - timeStarted;
int percent = (int) (elapsedMs * 100 / TIMEOUT_MS);
percent = Math.min(Math.max(percent, 0), 100);
progressBar.setProgress(percent);
handler.postDelayed(refresher, REFRESH_INTERVAL_MS);
}
} }

View File

@@ -1,31 +1,51 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ProgressBar <TextView
android:id="@+id/progressBar" android:id="@+id/title"
style="?android:attr/progressBarStyleLarge" style="@style/TextAppearance.AppCompat.Large"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/textView" android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center"
android:text="@string/mailbox_setup_connecting"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
<TextView <ProgressBar
android:id="@+id/textView" android:id="@+id/progressBar"
style="@style/TextAppearance.AppCompat.Large" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:text="@string/mailbox_setup_connecting" android:layout_marginTop="@dimen/margin_xlarge"
android:max="100"
app:layout_constraintBottom_toTopOf="@+id/info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="@dimen/margin_xlarge"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" /> app:layout_constraintTop_toBottomOf="@+id/progressBar"
tools:text="This may take up to 2 minutes" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -633,7 +633,9 @@
<string name="mailbox_setup_download_link">Share Download Link</string> <string name="mailbox_setup_download_link">Share Download Link</string>
<string name="mailbox_setup_button_scan">Scan Mailbox QR code</string> <string name="mailbox_setup_button_scan">Scan Mailbox QR code</string>
<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="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_connecting">Connecting to Mailbox</string>
<!-- This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes". -->
<string name="mailbox_setup_connecting_info">This may take up to %1s</string>
<string name="mailbox_setup_qr_code_wrong_title">Wrong QR code</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_title">Mailbox already linked</string>