diff --git a/jami-android/app/src/main/AndroidManifest.xml b/jami-android/app/src/main/AndroidManifest.xml
index 06f21a7926f9cfe2ff61fda8f3984f8786cf871c..e999e2ac4e4169d64cadc998f5e41977dfc43dfd 100644
--- a/jami-android/app/src/main/AndroidManifest.xml
+++ b/jami-android/app/src/main/AndroidManifest.xml
@@ -312,7 +312,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
         <service android:name=".service.ConnectionService"
             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
-            android:exported="true">
+            android:exported="true"
+            tools:targetApi="28">
             <intent-filter>
                 <action android:name="android.telecom.ConnectionService" />
             </intent-filter>
@@ -377,6 +378,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.TVImportWizard"
+            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/linkdevice/view/ExportSideStep3Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt
index d4c947e0fce7461ef3b76397dfc09c390a9bca02..6f84d0f92d65bc3b932da9fc4eca1754773e9588 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt
@@ -16,7 +16,6 @@
  */
 package cx.ring.linkdevice.view
 
-import cx.ring.linkdevice.viewmodel.AuthError
 import android.content.Context
 import android.os.Bundle
 import android.util.Log
@@ -27,6 +26,7 @@ import android.view.ViewGroup
 import androidx.core.content.ContextCompat
 import cx.ring.R
 import cx.ring.databinding.FragmentExportSideStep3Binding
+import net.jami.services.AccountService.AuthError
 
 class ExportSideStep3Fragment : Fragment() {
     private var _binding: FragmentExportSideStep3Binding? = null
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt
index d51d680cb1e56dfe1cada610c21d8c7082d6e03e..12df4819e18d5e3c23a97fb1207b3c8c2f41c66a 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt
@@ -63,8 +63,8 @@ class ImportSideStep1Fragment : Fragment() {
         binding.share.setOnClickListener {
             ActionHelper.shareAuthenticationToken(requireContext(), currentAuthenticationToken)
         }
-
     }
+
     fun showOutput(token: String) {
         Log.i(TAG, "Show authentication token : $token")
         currentAuthenticationToken = token
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt
index b56bc8d2e33bc74f6d43215705b3c81c24a7a6a1..66945a3f2e080b332adfd2dc5f72ce967f54d145 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt
@@ -16,7 +16,6 @@
  */
 package cx.ring.linkdevice.view
 
-import cx.ring.linkdevice.viewmodel.AuthError
 import android.content.Context
 import android.os.Bundle
 import android.util.Log
@@ -27,6 +26,7 @@ import android.view.ViewGroup
 import androidx.core.content.ContextCompat
 import cx.ring.R
 import cx.ring.databinding.FragmentImportSideStep3Binding
+import net.jami.services.AccountService.AuthError
 
 class ImportSideStep3Fragment : Fragment() {
     private var _binding: FragmentImportSideStep3Binding? = null
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt
index 993b263ace5f8dd3a2ceb330844c32b8812a99fc..5b998481feb67d7c5eaffebc8cbeda3f4b3c8003 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt
@@ -97,7 +97,7 @@ class LinkDeviceImportSideActivity : AppCompatActivity(),
                                 (adapter as ViewPagerAdapter).importSideStep2
                                     .showAuthentication(
                                         it.needPassword,
-                                        it.jamiId,
+                                        it.id,
                                         it.registeredName,
                                         it.error
                                     )
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt
index 1bd395d115c4bfc495f67c31854b6de53f28fd17..9a45ee21afc89028d9fe2e2ab1d5dc7e021af4b2 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import net.jami.services.AccountService
+import net.jami.services.AccountService.AuthError
 import net.jami.services.AccountService.AuthState
 import net.jami.services.AccountService.AuthResult
 import net.jami.utils.Log
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt
index 443a6c657c65ebed52a22df52367eb54f8c98c4f..ced4c5adcd37a3ed7a2d2adf821126e51c63d97f 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt
@@ -30,6 +30,7 @@ import net.jami.model.Account
 import net.jami.model.AccountConfig
 import net.jami.model.ConfigKey
 import net.jami.services.AccountService
+import net.jami.services.AccountService.AuthError
 import net.jami.utils.Log
 import javax.inject.Inject
 import javax.inject.Named
@@ -40,9 +41,9 @@ sealed class AddDeviceImportState {
     data class TokenAvailable(val token: String) : AddDeviceImportState()
     data object Connecting : AddDeviceImportState()
     data class Authenticating(
-        val jamiId: String,
+        val id: String,
         val needPassword: Boolean,
-        val registeredName: String = "",
+        val registeredName: String? = null,
         val error: InputError?
     ) : AddDeviceImportState()
 
@@ -78,9 +79,8 @@ class ImportSideViewModel @Inject constructor(
                 accountService.authResultObservable.filter { it.accountId == account.accountId }
             }
             .observeOn(mUiScheduler)
-            .subscribe { it: AuthResult ->
-                updateDeviceAuthState(it)
-            }.apply { compositeDisposable.add(this) }
+            .subscribe(this::updateDeviceAuthState)
+            .apply { compositeDisposable.add(this) }
     }
 
     fun onAuthentication(password: String = "") {
@@ -109,14 +109,14 @@ class ImportSideViewModel @Inject constructor(
         val authError = details[IMPORT_AUTH_ERROR_KEY]?.let {
             InputError.fromString(it)
         }
-        val jamiId = details[IMPORT_PEER_ID_KEY] ?: throw IllegalStateException("Jami ID not found")
+        val peerId = details[IMPORT_PEER_ID_KEY] ?: throw IllegalStateException("Jami ID not found")
         _tempAccount?.accountId?.apply {
-            accountService.findRegistrationByAddress(this, "", jamiId)
+            accountService.findRegistrationByAddress(this, "", peerId)
                 .observeOn(mUiScheduler)
                 .subscribe { registeredName ->
                     Log.d(TAG, "Registered name: ${registeredName.name}")
                     _uiState.value = AddDeviceImportState.Authenticating(
-                        jamiId = jamiId,
+                        id = peerId,
                         needPassword = needPassword,
                         registeredName = registeredName.name,
                         error = authError
@@ -124,7 +124,7 @@ class ImportSideViewModel @Inject constructor(
                 }.apply { compositeDisposable.add(this) }
         }
         _uiState.value = AddDeviceImportState.Authenticating(
-            jamiId = jamiId,
+            id = peerId,
             needPassword = needPassword,
             error = authError
         )
diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt
index 20bea5d36f168728df6f95643889961514e987eb..0908580636087beaca9fa55ad92a122410ac9594 100644
--- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt
+++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt
@@ -20,29 +20,9 @@ interface AuthStateListener {
     fun onInitSignal() { // Should not be received since there is nothing to do.
         throw UnsupportedOperationException()
     }
-
     fun onTokenAvailableSignal(details: Map<String, String>)
     fun onConnectingSignal()
     fun onAuthenticatingSignal(details: Map<String, String>)
     fun onInProgressSignal()
     fun onDoneSignal(details: Map<String, String>)
 }
-
-enum class AuthError {
-    NETWORK,
-    AUTHENTICATION,
-    TIMEOUT,
-    CANCELED,
-    UNKNOWN;
-
-    companion object {
-        fun fromString(value: String) = when (value) {
-            "network" -> NETWORK
-            "auth_error" -> AUTHENTICATION
-            "timeout" -> TIMEOUT
-            "canceled" -> CANCELED
-            "unknown" -> UNKNOWN
-            else -> UNKNOWN
-        }
-    }
-}
\ No newline at end of file
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..68bcdefb9561f7909845e77e765e88ae1262049d
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt
@@ -0,0 +1,83 @@
+/*
+ *  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.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.graphics.createBitmap
+import androidx.fragment.app.Fragment
+import cx.ring.R
+import cx.ring.databinding.FragmentImportSideStep1Binding
+import cx.ring.utils.QRCodeLoaderUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TVAccountImportStep1Fragment : Fragment() {
+
+    private var _binding: FragmentImportSideStep1Binding? = null
+    private val binding get() = _binding!!
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
+        FragmentImportSideStep1Binding.inflate(inflater, container, false)
+            .apply {
+                _binding = this
+                share.visibility = View.GONE
+                showLoading()
+            }.root
+
+    fun showLoading() {
+        binding.QRContainer.visibility = View.INVISIBLE
+        binding.loadingContainer.visibility = View.VISIBLE
+        binding.connecting.text = getText(R.string.import_side_step1_preparing_device)
+    }
+
+    fun showToken(token: String) {
+        Log.w(TAG, "showToken: $token")
+
+        QRCodeLoaderUtils.loadQRCodeData(
+            token,
+            requireContext().getColor(android.R.color.black),
+            requireContext().getColor(android.R.color.transparent)
+        ) { qrCodeData ->
+            binding.qrImage.setImageBitmap(
+                createBitmap(qrCodeData.width, qrCodeData.height).apply {
+                    setPixels(
+                        qrCodeData.data, 0, qrCodeData.width,
+                        0, 0, qrCodeData.width, qrCodeData.height
+                    )
+                }
+            )
+            binding.code.text = token
+            binding.QRContainer.visibility = View.VISIBLE
+            binding.loadingContainer.visibility = View.INVISIBLE
+            binding.advice.visibility = View.VISIBLE
+        }
+    }
+
+    fun showConnecting() {
+        binding.QRContainer.visibility = View.INVISIBLE
+        binding.loadingContainer.visibility = View.VISIBLE
+        binding.connecting.text = getText(R.string.import_side_step1_connecting)
+    }
+
+    companion object {
+        private const val TAG = "TVAccountImportStep1Fragment"
+    }
+}
\ No newline at end of file
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dfdc1e78be21ef54a38ef691d24bba44f67391f4
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt
@@ -0,0 +1,197 @@
+/*
+ *  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.text.InputType
+import android.util.Log
+import androidx.leanback.app.GuidedStepSupportFragment
+import androidx.leanback.widget.GuidanceStylist
+import androidx.leanback.widget.GuidedAction
+import cx.ring.R
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import net.jami.services.ContactService
+import net.jami.utils.StringUtils
+import javax.inject.Inject
+import javax.inject.Singleton
+
+
+@AndroidEntryPoint
+class TVAccountImportStep2Fragment : GuidedStepSupportFragment() {
+
+    @Inject
+    @Singleton
+    lateinit var contactService: ContactService
+
+    private var mPassword: String = ""
+
+    override fun onCreateGuidance(savedInstanceState: Bundle?): GuidanceStylist.Guidance {
+        val args = arguments ?: return super.onCreateGuidance(savedInstanceState)
+        val id = args.getString("id") ?: ""
+        val name = args.getString("registeredName", id)
+        return GuidanceStylist.Guidance(
+            getString(R.string.import_side_main_title),
+            id,
+            null,
+            AvatarDrawable.Builder()
+                .withId(id)
+                .withName(name)
+                .withCircleCrop(true)
+                .build(requireContext())
+                .apply { setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size)) }
+        )
+    }
+
+    fun update(
+        needPassword: Boolean,
+        id: String,
+        registeredName: String?,
+        error: String?
+    ) {
+        // update avatar:
+        guidanceStylist.iconView?.setImageDrawable(AvatarDrawable.Builder()
+            .withId(id)
+            .withName(registeredName)
+            .withCircleCrop(true)
+            .build(requireContext())
+            .apply { setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size)) })
+
+        // update guidance:
+        guidanceStylist.titleView?.text = registeredName
+
+        // update actions:
+        val position = findActionPositionById(ACTION_ID_CHECK)
+        if (position != -1) {
+            val action = actions[position]
+            action.title = error ?: ""
+            if (error != null) {
+                action.icon = requireContext().getDrawable(R.drawable.ic_error_red)
+            }
+            notifyActionChanged(position)
+        }
+    }
+
+    override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) {
+        val args = arguments ?: return
+        val needPassword = args.getBoolean("needPassword", false)
+        //val error = args.getString("error") ?: ""
+        if (needPassword) {
+            actions.add(
+                GuidedAction.Builder(requireContext())
+                    .id(ACTION_ID_PASSWORD)
+                    .title(R.string.wizard_password_info)
+                    .description(R.string.enter_password)
+                    .descriptionEditInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
+                    .descriptionEditable(true)
+                    .editDescription("")
+                    .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD)
+                    .build()
+            )
+            actions.add(GuidedAction.Builder(requireContext())
+                .id(ACTION_ID_CHECK)
+                .enabled(false)
+                .focusable(false)
+                .infoOnly(true)
+                .build())
+        }
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .id(GuidedAction.ACTION_ID_CONTINUE)
+                .title(R.string.import_side_step2_password_import)
+                .enabled(!needPassword)
+                .build()
+        )
+        actions.add(
+            GuidedAction.Builder(requireContext())
+                .clickAction(GuidedAction.ACTION_ID_CANCEL)
+                .build()
+        )
+    }
+
+    override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long {
+        if (action.id == ACTION_ID_PASSWORD) {
+            updatePasswordAction(action)
+        }
+        return GuidedAction.ACTION_ID_NEXT
+    }
+
+    override fun onGuidedActionEditCanceled(action: GuidedAction) {
+        if (action.id == ACTION_ID_PASSWORD) {
+            updatePasswordAction(action)
+        }
+    }
+
+    private fun updatePasswordAction(action: GuidedAction) {
+        val password = (action.editDescription?.toString() ?: "").apply { mPassword = this }
+        Log.w(TAG, "updatePasswordAction: $password")
+        action.description = if (password.isNotEmpty()) StringUtils.toPassword(password) else getString(R.string.enter_password)
+        notifyActionChanged(findActionPositionById(action.id))
+
+        // update continue action:
+        val continueAction = findActionPositionById(GuidedAction.ACTION_ID_CONTINUE)
+        if (continueAction != -1) {
+            actions[continueAction].isEnabled = password.isNotEmpty()
+            notifyActionChanged(continueAction)
+        }
+    }
+
+    override fun onGuidedActionClicked(action: GuidedAction) {
+        val callback = (activity as? TVImportWizard) ?: return
+        val needPassword = arguments?.getBoolean("needPassword", false) == true
+        when (action.id) {
+            ACTION_ID_PASSWORD -> {
+                /*val password = action.editTitle.toString()
+                callback.onAuthentication(password)*/
+            }
+            GuidedAction.ACTION_ID_CONTINUE -> {
+                if (needPassword) {
+                    callback.onAuthentication(mPassword)
+                } else {
+                    callback.onAuthentication("")
+                }
+            }
+            GuidedAction.ACTION_ID_CANCEL -> {
+                callback.onCancel()
+            }
+        }
+    }
+
+    override fun onProvideTheme(): Int = R.style.Theme_Ring_Leanback_GuidedStep_First
+
+    companion object {
+        const val TAG = "TVAccountImportStep2Fragment"
+        const val ACTION_ID_PASSWORD = 1L
+        const val ACTION_ID_CHECK = 2L
+
+        fun build(
+            needPassword: Boolean,
+            id: String,
+            registeredName: String?,
+            error: String?
+        ): TVAccountImportStep2Fragment =
+            TVAccountImportStep2Fragment().apply {
+                arguments = Bundle().apply {
+                    putBoolean("needPassword", needPassword)
+                    putString("id", id)
+                    putString("registeredName", registeredName)
+                    putString("error", error)
+                }
+            }
+    }
+}
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt
index 474865ef46ef37e7eb98a4116109c8b0e233ee44..b3c76a15f48d3a52959a01e3490cdea9286a0ac8 100644
--- a/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt
@@ -16,6 +16,8 @@
  */
 package cx.ring.tv.account
 
+import android.app.Activity
+import android.content.Intent
 import android.os.Bundle
 import androidx.fragment.app.activityViewModels
 import androidx.leanback.widget.GuidanceStylist.Guidance
@@ -36,8 +38,14 @@ class TVHomeAccountCreationFragment : JamiGuidedStepFragment<HomeAccountCreation
         add(parentFragmentManager, TVJamiAccountCreationFragment())
     }
 
-    override fun goToAccountLink() { // Todo: Legacy code. Implement new UI instead of TVJamiLinkAccountFragment.
-//        add(parentFragmentManager, TVJamiLinkAccountFragment())
+    override fun goToAccountLink() {
+        startActivityForResult(Intent(requireContext(), TVImportWizard::class.java), 56)
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == 56 && resultCode == Activity.RESULT_OK) {
+            activity?.finish()
+        }
     }
 
     override fun goToAccountConnect() {
diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9dcc10d76af1ca438bcb98d1d1f6819d979a2e42
--- /dev/null
+++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt
@@ -0,0 +1,162 @@
+/*
+ *  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.R
+import cx.ring.application.JamiApplication
+import cx.ring.linkdevice.view.ImportSideStep3Fragment
+import cx.ring.linkdevice.view.ImportSideStep3Fragment.OnResultCallback
+import cx.ring.linkdevice.viewmodel.AddDeviceImportState
+import cx.ring.linkdevice.viewmodel.ImportSideViewModel
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import net.jami.services.AccountService
+
+@AndroidEntryPoint
+class TVImportWizard : AppCompatActivity(), OnResultCallback {
+    private val importSideViewModel by lazy { ViewModelProvider(this)[ImportSideViewModel::class.java] }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        JamiApplication.instance?.startDaemon(this)
+        if (savedInstanceState == null) {
+            supportFragmentManager.beginTransaction()
+                .add(android.R.id.content, TVAccountImportStep1Fragment())
+                .commitNow()
+        }
+
+        lifecycleScope.launch {
+            importSideViewModel.uiState.collect {
+                Log.d(TAG, "UI state: $it")
+                when (it) {
+                    is AddDeviceImportState.Init -> {}
+                    is AddDeviceImportState.TokenAvailable -> showToken(it.token)
+                    is AddDeviceImportState.Connecting -> showConnecting()
+                    is AddDeviceImportState.Authenticating -> showAuthenticating(
+                        it.needPassword,
+                        it.id,
+                        it.registeredName,
+                        when (it.error) {
+                            ImportSideViewModel.InputError.BAD_PASSWORD -> getString(R.string.link_device_error_bad_password)
+                            ImportSideViewModel.InputError.UNKNOWN -> getString(R.string.link_device_error_unknown)
+                            else -> null
+                        }
+                    )
+                    is AddDeviceImportState.InProgress -> showInProgress()
+                    is AddDeviceImportState.Done -> showDone(it.error)
+                }
+            }
+        }
+    }
+
+    private fun showToken(token: String) {
+        Log.w(TAG, "showToken: $token")
+        val fragment =
+            supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep1Fragment
+                ?: TVAccountImportStep1Fragment().also {
+                    supportFragmentManager.beginTransaction()
+                        .replace(android.R.id.content, it)
+                        .commitNow()
+                }
+        if (token.isEmpty())
+            fragment.showLoading()
+        else
+            fragment.showToken(token)
+    }
+
+    private fun showConnecting() {
+        val fragment =
+            supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep1Fragment
+        fragment?.showConnecting()
+    }
+
+    fun onAuthentication(password: String) {
+        importSideViewModel.onAuthentication(password)
+    }
+
+    fun onCancel() {
+        //presenter.onCancel()
+    }
+
+    private fun showAuthenticating(
+        needPassword: Boolean,
+        id: String,
+        registeredName: String?,
+        error: String?
+    ) {
+        Log.w(TAG, "showAuthenticating: $needPassword, $id, $registeredName, $error")
+        val fragment =
+            supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep2Fragment
+        if (fragment == null) {
+            GuidedStepSupportFragment.addAsRoot(
+                this,
+                TVAccountImportStep2Fragment.build(needPassword, id, registeredName, error),
+                android.R.id.content
+            )
+        } else {
+            fragment.update(needPassword, id, registeredName, error)
+        }
+    }
+
+    private fun showInProgress() {
+        Log.w(TAG, "showInProgress")
+        val fragment =
+            supportFragmentManager.findFragmentById(android.R.id.content) as? ImportSideStep3Fragment
+                ?: ImportSideStep3Fragment().also {
+                    supportFragmentManager.beginTransaction()
+                        .replace(android.R.id.content, it)
+                        .commitNow()
+                }
+        fragment.showLoading()
+    }
+
+    private fun showDone(error: AccountService.AuthError?) {
+        Log.w(TAG, "showDone $error")
+        val fragment =
+            supportFragmentManager.findFragmentById(android.R.id.content) as? ImportSideStep3Fragment
+                ?: ImportSideStep3Fragment().also {
+                    supportFragmentManager.beginTransaction()
+                        .replace(android.R.id.content, it)
+                        .commitNow()
+                }
+        if (error == null) {
+            fragment.showDone()
+        } else {
+            fragment.showError(error)
+        }
+    }
+
+    override fun onExit(returnCode: Int) {
+        Log.w(TAG, "showExit $returnCode")
+        setResult(when(returnCode) {
+            0 -> RESULT_OK
+            else -> RESULT_CANCELED
+        })
+        finish()
+    }
+
+    companion object {
+        private const val TAG = "TVImportWizard"
+    }
+}
\ No newline at end of file
diff --git a/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt b/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
index bbe5e9fb583288b595a3ba87578d3863ec54ef29..cabb6d95f141ada0dbff2c445e30d8026c36eef3 100644
--- a/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
+++ b/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
@@ -184,6 +184,10 @@ class AvatarDrawable : Drawable {
             this.id = uri.rawUriString
             return this
         }
+        fun withId(id: String): Builder {
+            this.id = id
+            return this
+        }
 
         fun withPhoto(photo: Bitmap?): Builder {
             photos = if (photo == null) null else mutableListOf(photo) // list elements must be mutable
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt
index 5459d3496406f5685a78431eb54d0e2941557bcd..cb0fda7d31fb91c728515d107e78b99463ec2830 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt
@@ -1707,6 +1707,24 @@ class AccountService(
         val operationId: Long? = null
     )
 
+    enum class AuthError {
+        NETWORK,
+        AUTHENTICATION,
+        TIMEOUT,
+        CANCELED,
+        UNKNOWN;
+        companion object {
+            fun fromString(value: String) = when (value) {
+                "network" -> NETWORK
+                "auth_error" -> AUTHENTICATION
+                "timeout" -> TIMEOUT
+                "canceled" -> CANCELED
+                "unknown" -> UNKNOWN
+                else -> UNKNOWN
+            }
+        }
+    }
+
     /**
      * Related to add device feature (import side).
      * Updates the state of the account import.
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt
index b711d6200044193fe1645f89440f56c3268c85eb..5fa57995f3e7ae9afc73b068133afc4da1e06223 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt
@@ -39,7 +39,7 @@ object StringUtils {
 
     fun toPassword(s: String): String {
         if (s.isEmpty()) return ""
-        return String(CharArray(s.length).apply { Arrays.fill(this, '*') })
+        return String(CharArray(s.length).apply { Arrays.fill(this, '●') })
     }
 
     fun toNumber(s: String?): String? = s?.replace("(", "")?.replace(")", "")?.replace("-", "")?.replace(" ", "")