diff --git a/jami-android/app/src/main/AndroidManifest.xml b/jami-android/app/src/main/AndroidManifest.xml
index e999e2ac4e4169d64cadc998f5e41977dfc43dfd..12a488a71ae8c6c497e8d6197c5ff30b0a3a9bb6 100644
--- a/jami-android/app/src/main/AndroidManifest.xml
+++ b/jami-android/app/src/main/AndroidManifest.xml
@@ -383,6 +383,11 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
             android:exported="false"
             android:theme="@style/AppTheme" />
+        <activity
+            android:name=".tv.account.TVExportWizard"
+            android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
+            android:exported="false"
+            android:theme="@style/AppTheme" />
         <activity
             android:name=".tv.search.SearchActivity"
             android:exported="false"
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt
deleted file mode 100644
index dfc36b9dbd589570379204633e134296d5ec96ed..0000000000000000000000000000000000000000
--- a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU 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 General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.account
-
-import android.app.AlertDialog
-import android.app.DownloadManager
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import androidx.leanback.widget.GuidanceStylist.Guidance
-import androidx.leanback.widget.GuidedAction
-import cx.ring.R
-import cx.ring.databinding.ItemProgressDialogBinding
-import cx.ring.utils.AndroidFileUtils.getMimeType
-import dagger.hilt.android.AndroidEntryPoint
-import net.jami.account.JamiAccountSummaryPresenter
-import net.jami.account.JamiAccountSummaryView
-import net.jami.model.Account
-import net.jami.model.Profile
-import java.io.File
-
-@AndroidEntryPoint
-class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter, JamiAccountSummaryView>(), JamiAccountSummaryView {
-    private var mWaitDialog: AlertDialog? = null
-    private lateinit var mIdAccount: String
-    private var mHasPassword = false
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        presenter.setAccountId(mIdAccount)
-    }
-
-    override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance {
-        // Todo: finish to clean up.
-        val title = getString(R.string.account_export_title)
-        val breadcrumb = ""
-        val icon = requireContext().getDrawable(R.drawable.baseline_devices_24)
-        return Guidance(title, null, breadcrumb, icon)
-    }
-
-    override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) {
-        val context = requireContext()
-        // Todo: finish to clean up.
-//        if (mHasPassword) {
-//            addPasswordAction(context, actions, PASSWORD, getString(R.string.account_enter_password), "", "")
-//        } else {
-        addAction(context, actions, ACTION, R.string.account_start_export_button)
-//        }
-    }
-
-    override fun onGuidedActionClicked(action: GuidedAction) {
-        // Todo: finish to clean up.
-//        presenter.startAccountExport("")
-    }
-
-    override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long {
-        // Todo: finish to clean up.
-//        presenter.startAccountExport(action.description.toString())
-        return GuidedAction.ACTION_ID_NEXT
-    }
-
-    override fun onProvideTheme(): Int {
-        return R.style.Theme_Ring_Leanback_GuidedStep_First
-    }
-
-    override fun showExportingProgressDialog() {
-        mWaitDialog = AlertDialog.Builder(requireActivity())
-        .setView(ItemProgressDialogBinding.inflate(layoutInflater).root)
-        .setTitle(R.string.export_account_wait_title)
-        .setMessage(R.string.export_account_wait_message)
-        .setCancelable(false)
-        .show()
-    }
-
-    override fun showPasswordProgressDialog() {}
-    override fun accountChanged(account: Account, profile: Profile) {}
-
-    override fun passwordChangeEnded(accountId: String, ok: Boolean, newPassword: String) {}
-    override fun displayCompleteArchive(dest: File) {
-        val downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
-        downloadManager.addCompletedDownload(
-            dest.name,
-            dest.name,
-            true,
-            getMimeType(dest.absolutePath),
-            dest.absolutePath,
-            dest.length(),
-            true
-        )
-    }
-
-    override fun gotToImageCapture() {}
-    override fun askCameraPermission() {}
-    override fun goToGallery() {}
-    override fun goToMedia(accountId: String) {}
-    override fun goToSystem(accountId: String) {}
-    override fun goToAdvanced(accountId: String) {}
-    override fun goToAccount(accountId: String) {}
-    override fun showRevokingProgressDialog() {}
-    override fun deviceRevocationEnded(device: String, status: Int) {}
-    override fun updateDeviceList(devices: Map<String, String>, currentDeviceId: String) {}
-
-    companion object {
-        private const val PASSWORD = 1L
-        private const val ACTION = 2L
-        fun createInstance(idAccount: String, hasPassword: Boolean): TVAccountExport =
-            TVAccountExport().apply {
-                mIdAccount = idAccount
-                mHasPassword = hasPassword
-            }
-    }
-}
\ No newline at end of file
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6af3d6901c5c9b7a0b6d79922a110af3b3e8cf9b
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.os.Bundle
+import android.util.Log
+import android.view.*
+import androidx.fragment.app.Fragment
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.ResultPoint
+import com.journeyapps.barcodescanner.BarcodeCallback
+import com.journeyapps.barcodescanner.BarcodeResult
+import com.journeyapps.barcodescanner.DefaultDecoderFactory
+import cx.ring.R
+import cx.ring.databinding.FragmentExportSideStep1Binding
+import cx.ring.linkdevice.viewmodel.ExportSideInputError
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TVAccountExportStep1Fragment : Fragment() {
+
+    private var _binding: FragmentExportSideStep1Binding? = null
+    private val binding get() = _binding!!
+
+    private enum class InputMode { QR, CODE }
+
+    private var currentMode = InputMode.QR
+    private var isLoading = false
+
+    interface OnInputCallback {
+        fun onAuthenticationUri(authenticationUri: String)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View = FragmentExportSideStep1Binding.inflate(inflater, container, false)
+        .also { _binding = it }
+        .root
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        initializeBarcode()
+        showInputQr()
+
+        binding.switchMode.setOnClickListener {
+            if (currentMode == InputMode.QR) showInputCode()
+            else showInputQr()
+        }
+
+        binding.connect.setOnClickListener {
+            Log.i(TAG, "Connect clicked, authentication uri = ${binding.code.text}")
+            showInputCode(loading = true)
+            (requireActivity() as OnInputCallback)
+                .onAuthenticationUri(binding.code.text.toString())
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (currentMode == InputMode.QR) {
+            binding.barcodeScanner.resume()
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        if (currentMode == InputMode.QR) {
+            binding.barcodeScanner.pause()
+        }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
+    fun showError(error: ExportSideInputError?) {
+        if (currentMode == InputMode.QR) showInputQr(error = error)
+        else showInputCode(error = error)
+    }
+
+    private fun initializeBarcode() {
+        binding.barcodeScanner.apply {
+            barcodeView.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
+            decodeContinuous(onBarcodeResult)
+        }
+    }
+
+    private fun showInputQr(loading: Boolean = false, error: ExportSideInputError? = null) {
+        binding.barcodeScanner.resume()
+
+        isLoading = loading
+        currentMode = InputMode.QR
+
+        binding.inputCode.visibility = View.INVISIBLE
+        binding.inputQr.visibility = View.VISIBLE
+        binding.switchMode.text = getText(R.string.export_side_step1_switch_to_code)
+
+        if (loading) {
+            binding.barcodeScanner.visibility = View.INVISIBLE
+            binding.loadingQr.visibility = View.VISIBLE
+            binding.errorQr.visibility = View.INVISIBLE
+            binding.switchMode.isEnabled = false
+        } else if (error != null) {
+            initializeBarcode()
+            binding.barcodeScanner.resume()
+            binding.errorQr.text = when (error) {
+                ExportSideInputError.INVALID_INPUT ->
+                    getString(R.string.export_side_step1_error_malformed)
+            }
+            binding.switchMode.isEnabled = true
+            binding.barcodeScanner.visibility = View.VISIBLE
+            binding.loadingQr.visibility = View.INVISIBLE
+            binding.errorQr.visibility = View.VISIBLE
+            binding.errorQr.visibility = View.VISIBLE
+        } else {
+            binding.barcodeScanner.resume()
+            binding.barcodeScanner.visibility = View.VISIBLE
+            binding.loadingQr.visibility = View.INVISIBLE
+            binding.errorQr.visibility = View.INVISIBLE
+            binding.switchMode.isEnabled = true
+        }
+    }
+
+    private fun showInputCode(loading: Boolean = false, error: ExportSideInputError? = null) {
+        binding.barcodeScanner.pause()
+
+        isLoading = loading
+        currentMode = InputMode.CODE
+
+        binding.inputCode.visibility = View.VISIBLE
+        binding.inputQr.visibility = View.INVISIBLE
+        binding.switchMode.text = getText(R.string.export_side_step1_switch_to_qr)
+
+        binding.codeLayout.error = when (error) {
+            ExportSideInputError.INVALID_INPUT -> getText(R.string.export_side_step1_error_malformed)
+            else -> null
+        }
+
+        if (loading) {
+            binding.loadingCode.visibility = View.VISIBLE
+            binding.connect.isEnabled = false
+            binding.code.isEnabled = false
+            binding.switchMode.isEnabled = false
+        } else {
+            binding.connect.isEnabled = true
+            binding.code.isEnabled = true
+            binding.switchMode.isEnabled = true
+            binding.loadingCode.visibility = View.INVISIBLE
+        }
+    }
+
+    private val onBarcodeResult = object : BarcodeCallback {
+        override fun barcodeResult(result: BarcodeResult) {
+            if (!result.text.startsWith(SCHEME)) return
+            Log.i(TAG, "QR scanned: ${result.text}")
+            binding.barcodeScanner.pause()
+            binding.barcodeScanner.barcodeView.stopDecoding()
+            showInputQr(loading = true)
+            (requireActivity() as OnInputCallback).onAuthenticationUri(result.text)
+        }
+
+        override fun possibleResultPoints(resultPoints: List<ResultPoint>) {}
+    }
+
+    companion object {
+        private const val TAG = "TVAccountExportStep1Fragment"
+        const val SCHEME = "jami-auth://"
+    }
+}
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8d6d0b9e6be6fd3898880b31836416f74175d885
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt
@@ -0,0 +1,277 @@
+/*
+ *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.os.Bundle
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ProgressBar
+import androidx.leanback.app.GuidedStepSupportFragment
+import androidx.leanback.widget.GuidanceStylist
+import androidx.leanback.widget.GuidedAction
+import androidx.leanback.widget.GuidedActionsStylist
+import cx.ring.R
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TVAccountExportStep2Fragment : GuidedStepSupportFragment() {
+
+    private var peerAddress: String? = null
+    private lateinit var progressBar: ProgressBar
+
+    interface OnReviewCallback {
+        fun onIdentityConfirmation(confirm: Boolean)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val rootView = super.onCreateView(inflater, container, savedInstanceState)
+
+        // Wrap GuidedStepSupportFragment view inside FrameLayout
+        val frameLayout = FrameLayout(requireContext()).apply {
+            layoutParams = ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT
+            )
+        }
+
+        // Create and add ProgressBar
+        progressBar = ProgressBar(requireContext()).apply {
+            visibility = View.GONE
+            isIndeterminate = true
+        }
+        frameLayout.addView(rootView)
+        frameLayout.addView(progressBar, FrameLayout.LayoutParams(
+            FrameLayout.LayoutParams.WRAP_CONTENT,
+            FrameLayout.LayoutParams.WRAP_CONTENT
+        ).apply {
+            gravity = Gravity.CENTER
+        })
+
+        return frameLayout
+    }
+
+    override fun onCreateGuidance(savedInstanceState: Bundle?): GuidanceStylist.Guidance {
+        val id = arguments?.getString(ARG_ACCOUNT_ID) ?: ""
+        val name = arguments?.getString(ARG_REGISTERED_NAME) ?: id
+        Log.d(TAG, "onCreateGuidance: id=$id, name=$name")
+
+        return GuidanceStylist.Guidance(
+            getString(R.string.export_side_main_title),
+            id,
+            null,
+            AvatarDrawable.Builder()
+                .withId(id)
+                .withName(name)
+                .withCircleCrop(true)
+                .build(requireContext())
+                .apply {
+                    setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size))
+                }
+        )
+    }
+
+    override fun onCreateActionsStylist(): GuidedActionsStylist {
+        return TVGuidedActionsStylist()
+    }
+
+    override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) {
+        peerAddress = arguments?.getString(ARG_PEER_ADDRESS)
+        val isPasswordMode = peerAddress.isNullOrEmpty()
+
+        if (isPasswordMode) {
+            actions.add(
+                GuidedAction.Builder(requireContext())
+                    .id(ACTION_ID_PASSWORD_NOTICE)
+                    .title(getString(R.string.export_side_step2_password))
+                    .infoOnly(true)
+                    .enabled(false)
+                    .focusable(false)
+                    .build()
+            )
+        } else {
+            actions.add(
+                GuidedAction.Builder(requireContext())
+                    .id(ACTION_ID_PEER_IP)
+                    .title(getString(R.string.export_side_step2_advice_ip_only))
+                    .description(peerAddress ?: "")
+                    .infoOnly(true)
+                    .enabled(false)
+                    .focusable(false)
+                    .build()
+            )
+        }
+
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CONTINUE)
+                .title(R.string.export_side_step2_confirm)
+                .build()
+        )
+
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CANCEL)
+                .title(android.R.string.cancel)
+                .build()
+        )
+    }
+
+    override fun onGuidedActionClicked(action: GuidedAction) {
+        val callback = activity as? TVExportWizard ?: return
+        when (action.id) {
+            GuidedAction.ACTION_ID_CONTINUE -> {
+                showLoading()
+                callback.onIdentityConfirmation(true)
+            }
+            GuidedAction.ACTION_ID_CANCEL -> {
+                callback.onIdentityConfirmation(false)
+            }
+        }
+    }
+
+    private fun showLoading() {
+        actions.forEach { it.isEnabled = false }
+        setActions(actions)
+
+        (getGuidedActionsStylist() as? GuidedActionsStylist)?.actionsGridView?.apply {
+            animate()
+                .alpha(0f)
+                .setDuration(200)
+                .withEndAction { visibility = View.INVISIBLE }
+                .start()
+        }
+
+        progressBar.visibility = View.VISIBLE
+    }
+
+    fun update(peerAddress: String?) {
+        val isPasswordMode = peerAddress.isNullOrEmpty()
+        val isCurrentlyPasswordMode = findActionPositionById(ACTION_ID_PASSWORD_NOTICE) != -1
+        val isCurrentlyIPMode = findActionPositionById(ACTION_ID_PEER_IP) != -1
+
+        if (isPasswordMode && !isCurrentlyPasswordMode) {
+            actions.clear()
+            setupPasswordModeActions()
+        } else if (!isPasswordMode && !isCurrentlyIPMode) {
+            actions.clear()
+            setupIPModeActions(peerAddress)
+        } else if (!isPasswordMode && isCurrentlyIPMode) {
+            val ipIndex = findActionPositionById(ACTION_ID_PEER_IP)
+            if (ipIndex != -1) {
+                actions[ipIndex].description = peerAddress
+                notifyActionChanged(ipIndex)
+            }
+        }
+    }
+
+    private fun setupPasswordModeActions() {
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(ACTION_ID_PASSWORD_NOTICE)
+                .title(getString(R.string.export_side_step2_password))
+                .infoOnly(true)
+                .enabled(false)
+                .focusable(false)
+                .build()
+        )
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CONTINUE)
+                .title(R.string.export_side_step2_confirm)
+                .enabled(true)
+                .build()
+        )
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CANCEL)
+                .title(android.R.string.cancel)
+                .build()
+        )
+        setActions(actions)
+    }
+
+    private fun setupIPModeActions(peerAddress: String?) {
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(ACTION_ID_PEER_IP)
+                .title(getString(R.string.export_side_step2_advice_ip_only))
+                .description(peerAddress ?: "")
+                .infoOnly(true)
+                .enabled(false)
+                .focusable(false)
+                .build()
+        )
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CONTINUE)
+                .title(R.string.export_side_step2_confirm)
+                .enabled(true)
+                .build()
+        )
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CANCEL)
+                .title(android.R.string.cancel)
+                .build()
+        )
+        setActions(actions)
+    }
+
+    override fun onProvideTheme(): Int = R.style.Theme_Ring_Leanback_GuidedStep_First
+
+    companion object {
+        private const val TAG = "TVAccountExportStep2Fragment"
+        private const val ACTION_ID_PASSWORD_NOTICE = 1L
+        private const val ACTION_ID_PEER_IP = 2L
+        private const val ARG_PEER_ADDRESS = "peerAddress"
+        private const val ARG_ACCOUNT_ID = "accountId"
+        private const val ARG_REGISTERED_NAME = "registeredName"
+
+        fun build(
+            peerAddress: String?, accountId: String, registeredName: String
+        ): TVAccountExportStep2Fragment {
+            return TVAccountExportStep2Fragment().apply {
+                arguments = Bundle().apply {
+                    putString(ARG_PEER_ADDRESS, peerAddress)
+                    putString(ARG_ACCOUNT_ID, accountId)
+                    putString(ARG_REGISTERED_NAME, registeredName)
+                }
+            }
+        }
+    }
+}
+
+class TVGuidedActionsStylist : GuidedActionsStylist() {
+    override fun onBindViewHolder(viewHolder: ViewHolder, action: GuidedAction) {
+        super.onBindViewHolder(viewHolder, action)
+
+        viewHolder.titleView?.apply {
+            maxLines = 5
+            isSingleLine = false
+            ellipsize = null
+        }
+    }
+}
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..423c34139c700ffd6e28f257aba0a5e981bc1088
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt
@@ -0,0 +1,156 @@
+/*
+ *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.app.Activity
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.leanback.app.GuidedStepSupportFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import cx.ring.application.JamiApplication
+import cx.ring.linkdevice.view.ExportSideStep3Fragment
+import cx.ring.linkdevice.viewmodel.AddDeviceExportState
+import cx.ring.linkdevice.viewmodel.ExportSideInputError
+import cx.ring.linkdevice.viewmodel.ExportSideViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import net.jami.services.AccountService
+
+@AndroidEntryPoint
+class TVExportWizard : AppCompatActivity(),
+    TVAccountExportStep1Fragment.OnInputCallback,
+    TVAccountExportStep2Fragment.OnReviewCallback,
+    ExportSideStep3Fragment.OnResultCallback {
+
+    private val exportSideViewModel by lazy {
+        ViewModelProvider(this)[ExportSideViewModel::class.java]
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        JamiApplication.instance?.startDaemon(this)
+
+        if (savedInstanceState == null) {
+            supportFragmentManager.beginTransaction()
+                .replace(android.R.id.content, TVAccountExportStep1Fragment())
+                .commitNow()
+        }
+
+        observeUiState()
+    }
+
+    private fun observeUiState() {
+        lifecycleScope.launch {
+            exportSideViewModel.uiState.collect { state ->
+                try {
+                    when (state) {
+                        is AddDeviceExportState.Init -> showInit(state.error)
+                        is AddDeviceExportState.TokenAvailable ->
+                            throw UnsupportedOperationException()
+                        is AddDeviceExportState.Connecting ->
+                            Log.d(TAG, "State: Connecting")
+                        is AddDeviceExportState.Authenticating ->
+                            showAuthenticating(state.peerAddress)
+                        is AddDeviceExportState.InProgress -> showInProgress()
+                        is AddDeviceExportState.Done -> showDone(state.error)
+                    }
+                } catch (e: Exception) {
+                    Log.e(TAG, "Error while processing UI state.", e)
+                }
+            }
+        }
+    }
+
+    private fun showInit(error: ExportSideInputError?) {
+        (supportFragmentManager.findFragmentById(android.R.id.content)
+            as? TVAccountExportStep1Fragment)?.showError(error)
+    }
+
+    private fun showAuthenticating(peerAddress: String?) {
+        val accountId = intent.getStringExtra(EXTRA_ACCOUNT_ID) ?: ""
+        val registeredName = intent.getStringExtra(EXTRA_REGISTERED_NAME) ?: accountId
+        val fragment = supportFragmentManager
+            .findFragmentById(android.R.id.content) as? TVAccountExportStep2Fragment
+
+        if (fragment == null) {
+            GuidedStepSupportFragment.addAsRoot(
+                this,
+                TVAccountExportStep2Fragment.build(peerAddress, accountId, registeredName),
+                android.R.id.content
+            )
+        } else {
+            fragment.update(peerAddress)
+        }
+    }
+
+    private fun showInProgress() {
+        val fragment = ExportSideStep3Fragment()
+        supportFragmentManager.beginTransaction()
+            .replace(android.R.id.content, fragment)
+            .commitNow()
+
+        fragment.showLoading()
+    }
+
+    private fun showDone(error: AccountService.AuthError?) {
+        val fragment = ExportSideStep3Fragment()
+        supportFragmentManager.beginTransaction()
+            .replace(android.R.id.content, fragment)
+            .commitNow()
+
+        if (error != null) {
+            fragment.showError(error)
+        } else {
+            fragment.showDone()
+        }
+    }
+
+    override fun onAuthenticationUri(authenticationUri: String) {
+        exportSideViewModel.onAuthenticationUri(authenticationUri)
+    }
+
+    override fun onIdentityConfirmation(confirm: Boolean) {
+        if (!confirm) {
+            finish(1)
+        } else {
+            exportSideViewModel.onIdentityConfirmation()
+        }
+    }
+
+    override fun onExit(returnCode: Int) {
+        finish(returnCode)
+    }
+
+    private fun finish(returnCode: Int = 0) {
+        if (returnCode != 0) {
+            Log.w(TAG, "Account exportation failed.")
+            exportSideViewModel.onCancel()
+        }
+        setResult(if (returnCode == 0) Activity.RESULT_OK else Activity.RESULT_CANCELED)
+        finish()
+    }
+
+
+    companion object {
+        private const val TAG = "TVExportWizard"
+        const val EXTRA_ACCOUNT_ID = "account_id"
+        const val EXTRA_REGISTERED_NAME = "registered_name"
+    }
+}
diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
index feb4ff5808e4b705162f3ba799592e7793f0d450..ce0f1e3db3df70742f8449f3f25bdf756629431c 100644
--- a/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
@@ -34,7 +34,7 @@ import androidx.tvprovider.media.tv.ChannelLogoUtils
 import androidx.tvprovider.media.tv.PreviewProgram
 import androidx.tvprovider.media.tv.TvContractCompat
 import cx.ring.R
-import cx.ring.tv.account.TVAccountExport
+import cx.ring.tv.account.TVExportWizard
 import cx.ring.tv.account.TVProfileEditingFragment
 import cx.ring.tv.account.TVShareActivity
 import cx.ring.tv.call.TVCallActivity
@@ -294,12 +294,18 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView {
         }
     }
 
-    override fun showExportDialog(pAccountID: String, hasPassword: Boolean) {
-        val wizard: GuidedStepSupportFragment =
-            TVAccountExport.createInstance(pAccountID, hasPassword)
-        GuidedStepSupportFragment.add(parentFragmentManager, wizard, R.id.main_browse_fragment)
+    override fun showExportDialog(accountId: String, registeredName: String?) {
+        Log.d(TAG, "showExportDialog called with accountId: $accountId, name: $registeredName")
+
+        val intent = Intent(requireContext(), TVExportWizard::class.java).apply {
+            putExtra(TVExportWizard.EXTRA_ACCOUNT_ID, accountId)
+            putExtra(TVExportWizard.EXTRA_REGISTERED_NAME, registeredName)
+        }
+
+        startActivity(intent)
     }
 
+
     override fun showProfileEditing() {
         GuidedStepSupportFragment.add(
             parentFragmentManager,
diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt
index e3eb2547cabe4eb34fb193b0c343a6f18b82af6a..6c9b0f91ff88814bd1814927235de9c62f467b7c 100644
--- a/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt
+++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt
@@ -62,7 +62,7 @@ class MainPresenter @Inject constructor(
 
     fun onExportClicked() {
         val account = accountService.currentAccount ?: return
-        view?.showExportDialog(account.accountId, account.hasPassword())
+        view?.showExportDialog(account.accountId, account.displayUsername)
     }
 
     fun onEditProfileClicked() {
diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt
index 7957b02b75f3e7c188153af3e88712c3f3206e40..3af1b40d03586e1395fcde6e937e33e3755b103b 100644
--- a/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt
+++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt
@@ -26,7 +26,7 @@ interface MainView {
     fun showContactRequests(contactRequests: List<Conversation>)
     fun callContact(accountID: String, ringID: String)
     fun displayAccountInfo(viewModel: HomeNavigationViewModel)
-    fun showExportDialog(pAccountID: String, hasPassword: Boolean)
+    fun showExportDialog(accountId: String, registeredName: String?)
     fun showProfileEditing()
     fun showAccountShare()
     fun showSettings()