diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
index 811b1ba8ff210b3057429c24aa2bcf76d479b348..455321245797ae82a040439dc1bfff4157056ba5 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
@@ -124,7 +124,7 @@ class StatusManager @Inject constructor(
     object NeedsDozeExemption : MailboxAppState(false)
     object NotStarted : MailboxAppState(false)
     data class Starting(val status: String) : MailboxAppState(true)
-    data class StartedSettingUp(val qrCode: Bitmap) : MailboxAppState(true)
+    data class StartedSettingUp(val qrCode: Bitmap, val link: String) : MailboxAppState(true)
     object StartedSetupComplete : MailboxAppState(true)
     object ErrorClockSkew : MailboxAppState(true)
     object ErrorNoNetwork : MailboxAppState(true)
@@ -204,12 +204,15 @@ class StatusManager @Inject constructor(
                 else -> Starting(getString(R.string.startup_publishing_onion_service))
             }
             setup == SetupComplete.FALSE -> {
+                // FIXME we shouldn't do expensive calls on the UiThread
                 val dm = Resources.getSystem().displayMetrics
                 val size = min(dm.widthPixels, dm.heightPixels)
                 val bitMatrix = qrCodeEncoder.getQrCodeBitMatrix(size)
                 StartedSettingUp(
-                    bitMatrix?.let { it -> QrCodeUtils.renderQrCode(it) }
-                        ?: error("The QR code bit matrix is expected to be non-null here")
+                    qrCode = bitMatrix?.let { it -> QrCodeUtils.renderQrCode(it) }
+                        ?: error("The QR code bit matrix is expected to be non-null here"),
+                    link = qrCodeEncoder.getLink()
+                        ?: error("The QR code link is expected to be non-null here"),
                 )
             }
             setup == SetupComplete.TRUE -> StartedSetupComplete
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt
index bc7db26741a7f65aad013ab226af63fcf153923f..0340ce138a2546e9b25754b571926a9f352f4aa9 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/MainActivity.kt
@@ -83,6 +83,21 @@ class MainActivity : AppCompatActivity() {
         hadBeenStartedOnSave =
             savedInstanceState?.getBoolean(BUNDLE_LIFECYCLE_HAS_STARTED) ?: false
 
+        // set action bar titles based on navigation destination
+        nav.addOnDestinationChangedListener { _, destination, _ ->
+            // never show the up indicator. exceptions below
+            supportActionBar?.setDisplayHomeAsUpEnabled(false)
+            title = when (destination.id) {
+                R.id.qrCodeFragment -> getString(R.string.link_title)
+                R.id.qrCodeLinkFragment -> {
+                    supportActionBar?.setDisplayHomeAsUpEnabled(true)
+                    getString(R.string.link_text_title)
+                }
+                R.id.statusFragment -> getString(R.string.app_name)
+                else -> ""
+            }
+        }
+
         launchAndRepeatWhileStarted {
             viewModel.appState.collect { onAppStateChanged(it) }
         }
@@ -100,35 +115,68 @@ class MainActivity : AppCompatActivity() {
             return
         }
         when (state) {
-            Undecided -> {} // Do nothing yet
-            NeedOnboarding -> if (nav.currentDestination?.id == R.id.initFragment)
-                nav.navigate(actionGlobalOnboardingContainer())
-            NeedsDozeExemption -> if (nav.currentDestination?.id != R.id.doNotKillMeFragment)
-                nav.navigate(actionGlobalDoNotKillMeFragment())
-            NotStarted -> nav.navigate(actionGlobalStartupFragment())
+            Undecided -> supportActionBar?.hide() // hide action bar until we need it
+            NeedOnboarding -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id == R.id.initFragment)
+                    nav.navigate(actionGlobalOnboardingContainer())
+            }
+            NeedsDozeExemption -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.doNotKillMeFragment)
+                    nav.navigate(actionGlobalDoNotKillMeFragment())
+            }
+            NotStarted -> {
+                supportActionBar?.hide()
+                nav.navigate(actionGlobalStartupFragment())
+            }
             // It is important to navigate here from various fragments. The normal case is
             // that we come from the init fragment, do-not-kill fragment or the onboarding fragment.
             // However, when the service got killed and the app has been restored with a different
             // UI state such as the qr code screen or the status screen, then we also want to
             // navigate to the startup fragment.
-            is Starting -> if (nav.currentDestination?.id != R.id.startupFragment)
-                nav.navigate(actionGlobalStartupFragment())
-            is StartedSettingUp -> if (nav.currentDestination?.id != R.id.qrCodeFragment)
-                nav.navigate(actionGlobalQrCodeFragment())
-            StartedSetupComplete ->
-                if (nav.currentDestination?.id == R.id.qrCodeFragment)
+            is Starting -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.startupFragment)
+                    nav.navigate(actionGlobalStartupFragment())
+            }
+            is StartedSettingUp -> {
+                supportActionBar?.show()
+                if (nav.currentDestination?.id != R.id.qrCodeFragment &&
+                    nav.currentDestination?.id != R.id.qrCodeLinkFragment
+                ) nav.navigate(actionGlobalQrCodeFragment())
+            }
+            StartedSetupComplete -> {
+                if (nav.currentDestination?.id == R.id.qrCodeFragment) {
+                    supportActionBar?.hide()
                     nav.navigate(actionGlobalSetupCompleteFragment())
-                else if (nav.currentDestination?.id != R.id.statusFragment &&
+                } else if (nav.currentDestination?.id != R.id.statusFragment &&
                     nav.currentDestination?.id != R.id.setupCompleteFragment
-                ) nav.navigate(actionGlobalStatusFragment())
-            ErrorNoNetwork -> if (nav.currentDestination?.id != R.id.noNetworkFragment)
-                nav.navigate(actionGlobalNoNetworkFragment())
-            ErrorClockSkew -> if (nav.currentDestination?.id != R.id.clockSkewFragment)
-                nav.navigate(actionGlobalClockSkewFragment())
-            Stopping -> if (nav.currentDestination?.id != R.id.stoppingFragment)
-                nav.navigate(actionGlobalStoppingFragment())
-            Wiping -> if (nav.currentDestination?.id != R.id.wipingFragment)
-                nav.navigate(actionGlobalWipingFragment())
+                ) {
+                    supportActionBar?.show()
+                    nav.navigate(actionGlobalStatusFragment())
+                }
+            }
+            ErrorNoNetwork -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.noNetworkFragment)
+                    nav.navigate(actionGlobalNoNetworkFragment())
+            }
+            ErrorClockSkew -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.clockSkewFragment)
+                    nav.navigate(actionGlobalClockSkewFragment())
+            }
+            Stopping -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.stoppingFragment)
+                    nav.navigate(actionGlobalStoppingFragment())
+            }
+            Wiping -> {
+                supportActionBar?.hide()
+                if (nav.currentDestination?.id != R.id.wipingFragment)
+                    nav.navigate(actionGlobalWipingFragment())
+            }
             Stopped -> {} // nothing to do but needs to be exhaustive for Kotlin 1.7
         }
     }
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt
index 44cb232a1c4219876d76ebe87ae58658d1f6669a..db5a5893b04454565b44a662e9a5f50e13f8fec0 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeFragment.kt
@@ -21,20 +21,25 @@ package org.briarproject.mailbox.android.ui
 
 import android.os.Bundle
 import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
 import android.widget.ImageView
+import androidx.core.view.MenuProvider
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle.State.RESUMED
+import androidx.navigation.fragment.findNavController
 import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.collect
 import org.briarproject.mailbox.R
 import org.briarproject.mailbox.android.StatusManager.MailboxAppState
 import org.briarproject.mailbox.android.StatusManager.StartedSettingUp
 
 @AndroidEntryPoint
-class QrCodeFragment : Fragment() {
+class QrCodeFragment : Fragment(), MenuProvider {
 
     private val viewModel: MailboxViewModel by activityViewModels()
     private lateinit var qrCodeView: ImageView
@@ -56,12 +61,25 @@ class QrCodeFragment : Fragment() {
             viewModel.stopLifecycle()
             requireActivity().finishAffinity()
         }
+        requireActivity().addMenuProvider(this, viewLifecycleOwner, RESUMED)
 
         launchAndRepeatWhileStarted {
             viewModel.appState.collect { onAppStateChanged(it) }
         }
     }
 
+    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+        menuInflater.inflate(R.menu.link_actions, menu)
+    }
+
+    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+        if (menuItem.itemId == R.id.action_show_link) {
+            findNavController().navigate(R.id.action_qrCodeFragment_to_qrCodeLinkFragment)
+            return true
+        }
+        return false
+    }
+
     private fun onAppStateChanged(state: MailboxAppState) {
         if (state is StartedSettingUp) {
             qrCodeView.setImageBitmap(state.qrCode)
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..652356162267247c22ba8a0027e13b73d594428a
--- /dev/null
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/QrCodeLinkFragment.kt
@@ -0,0 +1,119 @@
+/*
+ *     Briar Mailbox
+ *     Copyright (C) 2021-2022  The Briar Project
+ *
+ *     This program is free software: you can redistribute it and/or modify
+ *     it under the terms of the GNU Affero General Public License as
+ *     published by the Free Software Foundation, either version 3 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU Affero General Public License for more details.
+ *
+ *     You should have received a copy of the GNU Affero General Public License
+ *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+package org.briarproject.mailbox.android.ui
+
+import android.content.ActivityNotFoundException
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Intent
+import android.content.Intent.ACTION_SEND
+import android.content.Intent.EXTRA_TEXT
+import android.os.Build.VERSION.SDK_INT
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import android.widget.Toast
+import android.widget.Toast.LENGTH_LONG
+import android.widget.Toast.LENGTH_SHORT
+import androidx.core.content.ContextCompat.getSystemService
+import androidx.core.view.MenuProvider
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import com.google.android.material.button.MaterialButton
+import dagger.hilt.android.AndroidEntryPoint
+import org.briarproject.mailbox.R
+import org.briarproject.mailbox.android.StatusManager.MailboxAppState
+import org.briarproject.mailbox.android.StatusManager.StartedSettingUp
+
+@AndroidEntryPoint
+class QrCodeLinkFragment : Fragment(), MenuProvider {
+
+    private val viewModel: MailboxViewModel by activityViewModels()
+    private lateinit var linkView: TextView
+    private lateinit var shareButton: MaterialButton
+    private lateinit var copyButton: MaterialButton
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?,
+    ): View? {
+        return inflater.inflate(R.layout.fragment_qr_link, container, false)
+    }
+
+    override fun onViewCreated(v: View, savedInstanceState: Bundle?) {
+        linkView = v.findViewById(R.id.linkView)
+        shareButton = v.findViewById(R.id.shareButton)
+        copyButton = v.findViewById(R.id.copyButton)
+
+        // only needed for up/back navigation in action bar
+        requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
+
+        launchAndRepeatWhileStarted {
+            viewModel.appState.collect { onAppStateChanged(it) }
+        }
+    }
+
+    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+    }
+
+    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+        if (menuItem.itemId == android.R.id.home) {
+            requireActivity().onBackPressedDispatcher.onBackPressed()
+            return true
+        }
+        return false
+    }
+
+    private fun onAppStateChanged(state: MailboxAppState) {
+        if (state is StartedSettingUp) {
+            linkView.text = state.link
+            shareButton.setOnClickListener {
+                val sendIntent = Intent().apply {
+                    action = ACTION_SEND
+                    putExtra(EXTRA_TEXT, state.link)
+                    type = "text/plain"
+                }
+                val shareIntent = Intent.createChooser(sendIntent, null)
+                try {
+                    startActivity(shareIntent)
+                } catch (ignored: ActivityNotFoundException) {
+                    Toast.makeText(requireContext(), R.string.activity_not_found, LENGTH_LONG)
+                        .show()
+                }
+            }
+            copyButton.setOnClickListener {
+                val clipboard = getSystemService(requireContext(), ClipboardManager::class.java)
+                    ?: return@setOnClickListener
+                val clip = ClipData.newPlainText("Briar Mailbox text", state.link)
+                clipboard.setPrimaryClip(clip)
+                // Only show a toast for Android 12 and lower.
+                if (SDK_INT <= 32) Toast.makeText(context, R.string.copied, LENGTH_SHORT).show()
+            }
+        }
+    }
+
+}
diff --git a/mailbox-android/src/main/res/drawable/ic_content_copy.xml b/mailbox-android/src/main/res/drawable/ic_content_copy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..89f03149c328390244e5b8dd64cca1aab48ad0df
--- /dev/null
+++ b/mailbox-android/src/main/res/drawable/ic_content_copy.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" />
+</vector>
diff --git a/mailbox-android/src/main/res/drawable/ic_share.xml b/mailbox-android/src/main/res/drawable/ic_share.xml
new file mode 100644
index 0000000000000000000000000000000000000000..59b0e84dd92be63eeda3928fa3484b1a6b9ec8c1
--- /dev/null
+++ b/mailbox-android/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" />
+</vector>
diff --git a/mailbox-android/src/main/res/layout-land/fragment_qr.xml b/mailbox-android/src/main/res/layout-land/fragment_qr.xml
index d57eccc01c05f4b984270b8299799470b9f669e3..95e269bfdc34bb9fe90b26945cc6d71a76c35905 100644
--- a/mailbox-android/src/main/res/layout-land/fragment_qr.xml
+++ b/mailbox-android/src/main/res/layout-land/fragment_qr.xml
@@ -6,27 +6,11 @@
     android:layout_height="match_parent"
     tools:context=".android.ui.MainActivity">
 
-    <TextView
-        android:id="@+id/headline"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_margin="16dp"
-        android:gravity="center_horizontal"
-        android:text="@string/link_title"
-        android:textAlignment="center"
-        android:textSize="32sp"
-        app:layout_constraintBottom_toTopOf="@id/description"
-        app:layout_constraintEnd_toStartOf="@id/card"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_bias="0.0"
-        app:layout_constraintVertical_chainStyle="packed" />
-
     <TextView
         android:id="@+id/description"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginHorizontal="16dp"
+        android:layout_margin="16dp"
         android:gravity="center_horizontal"
         android:text="@string/link_description"
         android:textAlignment="center"
@@ -34,14 +18,9 @@
         app:layout_constraintBottom_toTopOf="@id/buttonCancel"
         app:layout_constraintEnd_toStartOf="@id/card"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/headline" />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierDirection="end"
-        app:constraint_referenced_ids="headline,description" />
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.0"
+        app:layout_constraintVertical_chainStyle="packed" />
 
     <com.google.android.material.button.MaterialButton
         android:id="@+id/buttonCancel"
@@ -58,11 +37,14 @@
         android:id="@+id/card"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
-        android:layout_margin="16dp"
+        android:layout_marginTop="16dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginRight="16dp"
+        android:layout_marginBottom="16dp"
         app:cardCornerRadius="32dp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toEndOf="@id/barrier"
+        app:layout_constraintStart_toEndOf="@id/description"
         app:layout_constraintTop_toTopOf="parent"
         app:srcCompat="@drawable/ic_square">
 
diff --git a/mailbox-android/src/main/res/layout/fragment_qr.xml b/mailbox-android/src/main/res/layout/fragment_qr.xml
index e25d2eb58be66a45e8831ba4cf2b984cc7ae391e..819efe791ec72ca992ec251759aa1646a4b2b183 100644
--- a/mailbox-android/src/main/res/layout/fragment_qr.xml
+++ b/mailbox-android/src/main/res/layout/fragment_qr.xml
@@ -6,20 +6,6 @@
     android:layout_height="match_parent"
     tools:context=".android.ui.MainActivity">
 
-    <TextView
-        android:id="@+id/headline"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_margin="16dp"
-        android:gravity="center"
-        android:text="@string/link_title"
-        android:textSize="32sp"
-        app:layout_constraintBottom_toTopOf="@id/description"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintVertical_bias="0.0" />
-
     <TextView
         android:id="@+id/description"
         android:layout_width="0dp"
@@ -31,7 +17,8 @@
         android:textSize="20sp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/headline" />
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.0" />
 
     <androidx.cardview.widget.CardView
         android:id="@+id/card"
diff --git a/mailbox-android/src/main/res/layout/fragment_qr_link.xml b/mailbox-android/src/main/res/layout/fragment_qr_link.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ef5ae8fb7f03d8b0be7f11b8edd0e87afb14a3ec
--- /dev/null
+++ b/mailbox-android/src/main/res/layout/fragment_qr_link.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView 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.ui.MainActivity">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:id="@+id/description"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            android:gravity="center_horizontal"
+            android:text="@string/link_text_description"
+            android:textAlignment="center"
+            android:textSize="20sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.0" />
+
+        <androidx.cardview.widget.CardView
+            android:id="@+id/card"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_margin="16dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/description"
+            app:srcCompat="@drawable/ic_square">
+
+            <TextView
+                android:id="@+id/linkView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@android:color/white"
+                android:padding="8dp"
+                android:textColor="@color/briar_night_800"
+                android:textIsSelectable="true"
+                android:textSize="18sp"
+                tools:text="briar-mailbox://eb64akialxyqs74agq63u3nsjkuohf7i5wzht5va2rmu5acmvsczzcsrioemhaalhmakamjww4p3kiaznx6jcpnx7lzhj44gxm3zzd5o" />
+
+        </androidx.cardview.widget.CardView>
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/shareButton"
+            style="@style/Widget.Material3.Button.OutlinedButton.Icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginRight="16dp"
+            android:text="@string/share"
+            app:icon="@drawable/ic_share"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/card"
+            app:layout_constraintVertical_bias="0.0" />
+
+        <Button
+            android:id="@+id/copyButton"
+            style="@style/Widget.Material3.Button.OutlinedButton.Icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginLeft="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginBottom="16dp"
+            android:text="@string/copy"
+            app:icon="@drawable/ic_content_copy"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/shareButton"
+            app:layout_constraintHorizontal_bias="0.0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/card"
+            app:layout_constraintVertical_bias="0.0" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</ScrollView>
diff --git a/mailbox-android/src/main/res/menu/link_actions.xml b/mailbox-android/src/main/res/menu/link_actions.xml
new file mode 100644
index 0000000000000000000000000000000000000000..79b21865feb344f870bbfba46889f1252698cc68
--- /dev/null
+++ b/mailbox-android/src/main/res/menu/link_actions.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_show_link"
+        android:title="@string/link_menu_title"
+        app:showAsAction="never" />
+</menu>
diff --git a/mailbox-android/src/main/res/navigation/nav_main.xml b/mailbox-android/src/main/res/navigation/nav_main.xml
index bbd9087fc3cdfd67e407484e5795493533290c2e..fbb0eee8f501fe1631ceb4be70b7f03dffaae3cf 100644
--- a/mailbox-android/src/main/res/navigation/nav_main.xml
+++ b/mailbox-android/src/main/res/navigation/nav_main.xml
@@ -28,8 +28,17 @@
     <fragment
         android:id="@+id/qrCodeFragment"
         android:name="org.briarproject.mailbox.android.ui.QrCodeFragment"
-        android:label="QrCodeFragment"
-        tools:layout="@layout/fragment_qr" />
+        android:label="@string/link_title"
+        tools:layout="@layout/fragment_qr">
+        <action
+            android:id="@+id/action_qrCodeFragment_to_qrCodeLinkFragment"
+            app:destination="@id/qrCodeLinkFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/qrCodeLinkFragment"
+        android:name="org.briarproject.mailbox.android.ui.QrCodeLinkFragment"
+        android:label="@string/link_title"
+        tools:layout="@layout/fragment_qr_link" />
     <fragment
         android:id="@+id/setupCompleteFragment"
         android:name="org.briarproject.mailbox.android.ui.SetupCompleteFragment"
diff --git a/mailbox-android/src/main/res/values-night/themes.xml b/mailbox-android/src/main/res/values-night/themes.xml
index 8cd614568d5a241ee6627b81c92a7f608b25e590..05035d002d96ce2c2606e5906ed473acd59d72dd 100644
--- a/mailbox-android/src/main/res/values-night/themes.xml
+++ b/mailbox-android/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
-    <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="Theme.BriarMailbox" parent="Theme.Material3.DayNight">
         <!-- Primary brand color. -->
         <item name="colorPrimary">@color/briar_night</item>
         <item name="colorPrimaryDark">#1F78D1</item>
diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml
index 04bb3261b2e1c3401a9c4f99fe430c79085d8f85..bcffbb7d70fa972c7d0f97604d18c57d15b7ef3f 100644
--- a/mailbox-android/src/main/res/values/strings.xml
+++ b/mailbox-android/src/main/res/values/strings.xml
@@ -29,9 +29,14 @@
     <string name="fix">Fix</string>
     <string name="ok">OK</string>
     <string name="cancel">Cancel</string>
+    <string name="share">Share</string>
+    <string name="copied">Copied</string>
 
     <string name="link_title">Link via QR code</string>
     <string name="link_description">Scan this QR code with Briar</string>
+    <string name="link_menu_title">Show as text</string>
+    <string name="link_text_title">Link via text</string>
+    <string name="link_text_description">Copy this and paste it into Briar Desktop:</string>
     <string name="link_cancel">Cancel Setup</string>
 
     <string name="startup_headline">Starting Mailbox</string>
@@ -68,6 +73,7 @@
     <string name="wipe_complete_description">Next time you have access to your Briar device, please open the Mailbox settings screen in the Briar app, then tap the \"Unlink\" button to complete the process.</string>
 
     <string name="sorry">Sorry</string>
+    <string name="activity_not_found">No app found for this action</string>
     <string name="startup_failed_activity_title">Mailbox Startup Failure</string>
     <string name="startup_failed_service_error">Mailbox was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again.</string>
     <string name="startup_failed_lifecycle_reuse">Mailbox has already stopped.\n\nPlease close the app and try again.</string>
diff --git a/mailbox-android/src/main/res/values/themes.xml b/mailbox-android/src/main/res/values/themes.xml
index ec0a371756ace43d773006f8b91470ef49b406b7..892878df15c459ee8d1946233793b8365691433f 100644
--- a/mailbox-android/src/main/res/values/themes.xml
+++ b/mailbox-android/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
-    <style name="Theme.BriarMailboxBase" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="Theme.BriarMailboxBase" parent="Theme.Material3.DayNight">
         <!-- Primary brand color. -->
         <item name="colorPrimary">@color/briar_night</item>
         <item name="colorPrimaryDark">@color/briar_blue</item>
diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
index da436f36811783ebe5a1f13d05d44fd73a554bfb..772522234aaa8bdb039b8dfcded0d9ceb1d0fc0c 100644
--- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
+++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
@@ -111,8 +111,13 @@ class Main : CliktCommand(
             }
             mailbox.waitForTorPublished()
             mailbox.getQrCode()?.let {
+                println("Please scan this with the Briar Android app:")
                 println(QrCodeRenderer.getQrString(it))
             }
+            mailbox.getLink()?.let {
+                println("Or copy and paste this into Briar Desktop:\n")
+                println(it)
+            }
         }
     }
 
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt
index 2c95bb31a1caa1e4a7323e7ffc8f433716d604a6..ec901f8772bc9b1a8d0702e88df0a21b59fce7ae 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/QrCodeEncoder.kt
@@ -58,6 +58,11 @@ class QrCodeEncoder @Inject constructor(
         return QRCodeWriter().encode(content, QR_CODE, edgeLen, edgeLen)
     }
 
+    fun getLink(): String? {
+        val bytes = getQrCodeBytes() ?: return null
+        return "briar-mailbox://${Base32.encode(bytes).lowercase()}"
+    }
+
     private fun getQrCodeBytes(): ByteArray? {
         // The ID and version of the QR code format: 3 bits for the format ID and 5 for the version
         val formatIdAndVersion = ((FORMAT_ID shl 5) or FORMAT_VERSION).toByte()
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
index 67d71043c22c194f54d327d9931a84aa0b40d758..bedad403c012c8065c57e69d2a05f8104a0ed7ac 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
@@ -127,5 +127,9 @@ abstract class AbstractMailbox(protected val customDataDir: File? = null) {
         return qrCodeEncoder.getQrCodeBitMatrix()
     }
 
+    fun getLink(): String? {
+        return qrCodeEncoder.getLink()
+    }
+
     fun getSystem(): System = system
 }