diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml
index feeec1655..5d41da90b 100644
--- a/briar-android/src/main/AndroidManifest.xml
+++ b/briar-android/src/main/AndroidManifest.xml
@@ -437,6 +437,15 @@
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
+
+
+
+
createDocument =
+ registerForActivityResult(
+ new ActivityResultContracts.CreateDocument(),
+ uri -> write(contactId, uri));
+ writeButton.setOnClickListener(
+ v -> createDocument.launch(viewModel.getFileName()));
+
+ readButton.setText("Read for contactId " + contactId);
+ ActivityResultLauncher getContent =
+ registerForActivityResult(
+ new ActivityResultContracts.GetContent(),
+ uri -> read(contactId, uri));
+ readButton.setOnClickListener(
+ v -> getContent.launch("application/octet-stream"));
+
+ LiveData state;
+ state = viewModel.ongoingWrite(new ContactId(contactId));
+ if (state == null) {
+ writeButton.setEnabled(true);
+ } else {
+ say("\nOngoing write:");
+ writeButton.setEnabled(false);
+ state.observe(this, (taskState) -> handleState("write", taskState));
+ }
+ state = viewModel.ongoingRead(new ContactId(contactId));
+ if (state == null) {
+ readButton.setEnabled(true);
+ } else {
+ say("\nOngoing read:");
+ readButton.setEnabled(false);
+ state.observe(this, (taskState) -> handleState("read", taskState));
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressed();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void write(int contactId, @Nullable Uri uri) {
+ if (contactId == -1) {
+ throw new IllegalStateException();
+ }
+ if (uri == null) {
+ say("no URI picked for write");
+ return;
+ }
+ say("\nWriting to URI: " + uri);
+ writeButton.setEnabled(false);
+ LiveData state = viewModel.write(new ContactId(contactId), uri);
+ state.observe(this, (taskState) -> handleState("write", taskState));
+ }
+
+ private void read(int contactId, @Nullable Uri uri) {
+ if (contactId == -1) {
+ throw new IllegalStateException();
+ }
+ if (uri == null) {
+ say("no URI picked for read");
+ return;
+ }
+ say("\nReading from URI: " + uri);
+ readButton.setEnabled(false);
+ LiveData state = viewModel.read(new ContactId(contactId), uri);
+ state.observe(this, (taskState) -> handleState("read", taskState));
+ }
+
+ private void handleState(String action, State taskState) {
+ say(String.format(Locale.getDefault(),
+ "%s: bytes done: %d of %d. %s. %s.",
+ action, taskState.getDone(), taskState.getTotal(),
+ taskState.isFinished() ? "Finished" : "Ongoing",
+ taskState.isFinished() ?
+ (taskState.isSuccess() ? "Success" : "Failed") : ".."));
+ if (taskState.isFinished()) {
+ if (action.equals("write")) {
+ writeButton.setEnabled(true);
+ } else if (action.equals("read")) {
+ readButton.setEnabled(true);
+ }
+ }
+ }
+
+ private void say(String txt) {
+ String time = new SimpleDateFormat("HH:mm:ss", Locale.getDefault())
+ .format(new Date());
+ txt = String.format("%s %s\n", time, txt);
+ text.setText(text.getText().toString().concat(txt));
+ }
+}
diff --git a/briar-android/src/main/res/layout/activity_removable_drive.xml b/briar-android/src/main/res/layout/activity_removable_drive.xml
new file mode 100644
index 000000000..3b91a49f2
--- /dev/null
+++ b/briar-android/src/main/res/layout/activity_removable_drive.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/src/main/res/menu/conversation_actions.xml b/briar-android/src/main/res/menu/conversation_actions.xml
index dad562bab..46958e0de 100644
--- a/briar-android/src/main/res/menu/conversation_actions.xml
+++ b/briar-android/src/main/res/menu/conversation_actions.xml
@@ -40,4 +40,9 @@
android:title="@string/delete_contact"
app:showAsAction="never" />
+
+