From 9c961bd93173df0c120341d690a8fe123b59310b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Wed, 18 Aug 2021 11:51:11 -0400
Subject: [PATCH] Add Hilt, Kotlin support, swarm fixes

Change-Id: I7c6a81af01911fa5db97bef7677074398df9d3b8
---
 ring-android/app/build.gradle                 |   35 +-
 ring-android/app/proguard-rules.pro           |   10 +-
 ring-android/app/src/main/AndroidManifest.xml |   10 +-
 .../about/AboutBottomSheetDialogFragment.java |   59 -
 .../about/AboutBottomSheetDialogFragment.kt   |   50 +
 .../java/cx/ring/about/AboutFragment.java     |  114 -
 .../main/java/cx/ring/about/AboutFragment.kt  |  103 +
 .../ring/account/AccountEditionFragment.java  |  348 ---
 .../cx/ring/account/AccountEditionFragment.kt |  287 +++
 .../ring/account/AccountWizardActivity.java   |  275 ---
 .../cx/ring/account/AccountWizardActivity.kt  |  241 +++
 .../java/cx/ring/account/DeviceAdapter.java   |  122 --
 .../java/cx/ring/account/DeviceAdapter.kt     |   91 +
 .../account/HomeAccountCreationFragment.java  |  135 --
 .../account/HomeAccountCreationFragment.kt    |  131 ++
 .../account/JamiAccountConnectFragment.java   |    5 +-
 .../account/JamiAccountCreationFragment.java  |  197 --
 .../account/JamiAccountCreationFragment.kt    |  152 ++
 .../account/JamiAccountPasswordFragment.java  |    6 +-
 .../account/JamiAccountSummaryFragment.java   |  832 -------
 .../account/JamiAccountSummaryFragment.kt     |  744 +++++++
 .../account/JamiAccountUsernameFragment.java  |    7 +-
 .../ring/account/JamiLinkAccountFragment.java |  184 --
 .../ring/account/JamiLinkAccountFragment.kt   |  151 ++
 .../JamiLinkAccountPasswordFragment.java      |    5 +-
 .../ring/account/ProfileCreationFragment.java |  248 ---
 .../ring/account/ProfileCreationFragment.kt   |  224 ++
 .../cx/ring/account/RegisterNameDialog.java   |  237 --
 .../cx/ring/account/RegisterNameDialog.kt     |  218 ++
 .../cx/ring/account/RenameDeviceDialog.java   |  113 -
 .../cx/ring/account/RenameDeviceDialog.kt     |  111 +
 .../ring/adapters/ConfParticipantAdapter.java |  123 --
 .../ring/adapters/ConfParticipantAdapter.kt   |  105 +
 .../cx/ring/adapters/ConversationAdapter.java | 1246 -----------
 .../cx/ring/adapters/ConversationAdapter.kt   | 1141 ++++++++++
 .../cx/ring/adapters/SmartListAdapter.java    |  119 -
 .../java/cx/ring/adapters/SmartListAdapter.kt |   99 +
 .../cx/ring/adapters/SmartListDiffUtil.java   |   69 -
 .../cx/ring/adapters/SmartListDiffUtil.kt     |   53 +
 .../cx/ring/application/JamiApplication.java  |  374 ----
 .../cx/ring/application/JamiApplication.kt    |  277 +++
 .../cx/ring/client/AccountSpinnerAdapter.java |  178 --
 .../cx/ring/client/AccountSpinnerAdapter.kt   |  156 ++
 .../java/cx/ring/client/CallActivity.java     |  233 --
 .../main/java/cx/ring/client/CallActivity.kt  |  215 ++
 .../ring/client/ColorChooserBottomSheet.java  |   83 -
 .../cx/ring/client/ColorChooserBottomSheet.kt |   76 +
 .../ring/client/ContactDetailsActivity.java   |  457 ----
 .../cx/ring/client/ContactDetailsActivity.kt  |  439 ++++
 .../cx/ring/client/ConversationActivity.java  |  144 --
 .../cx/ring/client/ConversationActivity.kt    |  124 ++
 .../client/ConversationSelectionActivity.java |  151 --
 .../client/ConversationSelectionActivity.kt   |  108 +
 .../ring/client/EmojiChooserBottomSheet.java  |   97 -
 .../cx/ring/client/EmojiChooserBottomSheet.kt |   83 +
 .../java/cx/ring/client/HomeActivity.java     |  835 -------
 .../main/java/cx/ring/client/HomeActivity.kt  |  792 +++++++
 .../java/cx/ring/client/LogsActivity.java     |  203 --
 .../main/java/cx/ring/client/LogsActivity.kt  |  181 ++
 ...erActivity.java => MediaViewerActivity.kt} |   23 +-
 .../cx/ring/client/MediaViewerFragment.java   |   98 -
 .../cx/ring/client/MediaViewerFragment.kt     |   80 +
 .../java/cx/ring/client/RingtoneActivity.java |  376 ----
 .../java/cx/ring/client/RingtoneActivity.kt   |  354 +++
 .../{ShareActivity.java => ShareActivity.kt}  |   38 +-
 .../contactrequests/BlockListAdapter.java     |   67 -
 .../ring/contactrequests/BlockListAdapter.kt  |   54 +
 .../contactrequests/BlockListFragment.java    |  126 --
 .../ring/contactrequests/BlockListFragment.kt |  114 +
 .../contactrequests/BlockListViewHolder.java  |   45 -
 .../contactrequests/BlockListViewHolder.kt    |   39 +
 .../ContactRequestsFragment.java              |  164 --
 .../ContactRequestsFragment.kt                |  120 ++
 .../JamiInjectionComponent.java               |  239 --
 .../JamiInjectionModule.java                  |   54 -
 .../ServiceInjectionModule.java               |  181 --
 .../ServiceInjectionModule.kt                 |  171 ++
 .../fragments/AccountMigrationFragment.java   |    3 +-
 .../fragments/AdvancedAccountFragment.java    |  145 --
 .../ring/fragments/AdvancedAccountFragment.kt |  143 ++
 .../fragments/AdvancedAccountPresenter.java   |    2 +-
 .../java/cx/ring/fragments/CallFragment.java  | 1601 --------------
 .../java/cx/ring/fragments/CallFragment.kt    | 1480 +++++++++++++
 .../ring/fragments/ContactPickerFragment.java |    9 +-
 .../ring/fragments/ConversationFragment.java  | 1283 -----------
 .../cx/ring/fragments/ConversationFragment.kt | 1190 ++++++++++
 .../fragments/GeneralAccountFragment.java     |    3 +-
 .../fragments/GeneralAccountPresenter.java    |   11 +-
 .../cx/ring/fragments/LinkDeviceFragment.java |    3 +-
 .../fragments/LocationSharingFragment.java    |  628 ------
 .../ring/fragments/LocationSharingFragment.kt |  587 +++++
 .../fragments/MediaPreferenceFragment.java    |    3 +-
 .../fragments/PluginHandlersListFragment.java |   85 -
 .../fragments/PluginHandlersListFragment.kt   |   70 +
 .../cx/ring/fragments/QRCodeFragment.java     |  178 --
 .../java/cx/ring/fragments/QRCodeFragment.kt  |  141 ++
 .../fragments/SIPAccountCreationFragment.java |  196 --
 .../fragments/SIPAccountCreationFragment.kt   |  193 ++
 .../fragments/SecurityAccountFragment.java    |   12 +-
 .../cx/ring/fragments/ShareWithFragment.java  |  196 --
 .../cx/ring/fragments/ShareWithFragment.kt    |  183 ++
 .../cx/ring/fragments/SmartListFragment.java  |  540 -----
 .../cx/ring/fragments/SmartListFragment.kt    |  424 ++++
 .../java/cx/ring/history/DatabaseHelper.java  |  432 ----
 .../java/cx/ring/history/DatabaseHelper.kt    |  438 ++++
 .../java/cx/ring/linkpreview/LinkListener.kt  |   15 +
 .../java/cx/ring/linkpreview/LinkPreview.kt   |   30 +
 .../java/cx/ring/linkpreview/PreviewData.kt   |   12 +
 .../main/java/cx/ring/mvp/BaseFragment.java   |   82 -
 .../cx/ring/mvp/BasePreferenceFragment.java   |    2 +
 .../java/cx/ring/mvp/BaseSupportFragment.java |  102 -
 .../java/cx/ring/mvp/BaseSupportFragment.kt   |   89 +
 .../java/cx/ring/plugins/PluginUtils.java     |    8 +-
 .../RecyclerPicker/RecyclerPicker.java        |   50 +-
 .../RecyclerPicker/RecyclerPickerAdapter.java |   20 +-
 .../RecyclerPickerLayoutManager.java          |   10 +-
 .../RecyclerPicker/RecyclerPickerUtils.java   |   23 -
 .../java/cx/ring/service/BootReceiver.java    |    5 +-
 .../ring/service/CallNotificationService.java |    9 +-
 .../java/cx/ring/service/DRingService.java    |  799 -------
 .../main/java/cx/ring/service/DRingService.kt |  374 ++++
 .../java/cx/ring/service/IDRingService.aidl   |  118 -
 .../ring/service/LocationSharingService.java  |  406 ----
 .../cx/ring/service/LocationSharingService.kt |  366 ++++
 .../java/cx/ring/service/SyncService.java     |    8 +-
 .../cx/ring/services/ContactServiceImpl.java  |  463 ----
 .../cx/ring/services/ContactServiceImpl.kt    |  500 +++++
 .../cx/ring/services/DataTransferService.java |    5 +-
 .../services/DeviceRuntimeServiceImpl.java    |  255 ---
 .../ring/services/DeviceRuntimeServiceImpl.kt |  231 ++
 .../cx/ring/services/HardwareServiceImpl.java |  788 -------
 .../cx/ring/services/HardwareServiceImpl.kt   |  695 ++++++
 .../cx/ring/services/HistoryServiceImpl.java  |  156 --
 .../cx/ring/services/HistoryServiceImpl.kt    |  128 ++
 .../java/cx/ring/services/LogServiceImpl.java |    2 -
 .../services/NotificationServiceImpl.java     |  990 ---------
 .../ring/services/NotificationServiceImpl.kt  | 1068 +++++++++
 .../SharedPreferencesServiceImpl.java         |  221 --
 .../services/SharedPreferencesServiceImpl.kt  |  204 ++
 .../cx/ring/services/VCardServiceImpl.java    |  122 --
 .../java/cx/ring/services/VCardServiceImpl.kt |  105 +
 .../cx/ring/settings/AccountFragment.java     |    5 +-
 .../cx/ring/settings/SettingsFragment.java    |    6 +-
 .../pluginssettings/PathListAdapter.java      |   13 +-
 .../main/java/cx/ring/share/ScanFragment.java |  185 --
 .../main/java/cx/ring/share/ScanFragment.kt   |  180 ++
 .../java/cx/ring/share/ShareFragment.java     |    6 +-
 .../java/cx/ring/tv/about/AboutActivity.java  |   35 -
 .../ring/tv/about/AboutDetailsFragment.java   |   28 +-
 .../cx/ring/tv/account/TVAccountExport.java   |  258 ---
 .../cx/ring/tv/account/TVAccountExport.kt     |  187 ++
 .../cx/ring/tv/account/TVAccountWizard.java   |  235 --
 .../cx/ring/tv/account/TVAccountWizard.kt     |  202 ++
 .../TVHomeAccountCreationFragment.java        |    9 +-
 .../TVJamiAccountCreationFragment.java        |   12 +-
 .../tv/account/TVJamiLinkAccountFragment.java |    5 +-
 .../tv/account/TVProfileCreationFragment.java |  266 ---
 .../tv/account/TVProfileCreationFragment.kt   |  224 ++
 .../tv/account/TVProfileEditingFragment.java  |  236 --
 .../tv/account/TVProfileEditingFragment.kt    |  196 ++
 .../cx/ring/tv/account/TVShareActivity.java   |    3 +-
 .../cx/ring/tv/account/TVShareFragment.java   |  110 -
 .../cx/ring/tv/account/TVShareFragment.kt     |   96 +
 .../java/cx/ring/tv/call/TVCallActivity.java  |  118 -
 .../java/cx/ring/tv/call/TVCallActivity.kt    |   97 +
 .../java/cx/ring/tv/call/TVCallFragment.java  |  828 -------
 .../java/cx/ring/tv/call/TVCallFragment.kt    |  744 +++++++
 .../ring/tv/camera/CustomCameraActivity.java  |  290 ---
 .../cx/ring/tv/camera/CustomCameraActivity.kt |  275 +++
 .../src/main/java/cx/ring/tv/cards/Card.java  |    5 +-
 .../ring/tv/cards/CardPresenterSelector.java  |    7 +-
 .../main/java/cx/ring/tv/cards/CardView.java  |  313 +++
 .../tv/cards/ShadowRowPresenterSelector.java  |   38 +-
 .../cards/contacts/ContactCardPresenter.java  |   31 +-
 .../tv/cards/iconcards/IconCardHelper.java    |   57 +-
 .../tv/cards/iconcards/IconCardPresenter.java |   44 +-
 .../cx/ring/tv/contact/TVContactActivity.java |  162 ++
 .../tv/contact/TVContactDetailPresenter.java  |   70 -
 .../tv/contact/TVContactDetailPresenter.kt    |   58 +
 .../cx/ring/tv/contact/TVContactFragment.java |  248 ---
 .../cx/ring/tv/contact/TVContactFragment.kt   |  207 ++
 .../ring/tv/contact/TVContactPresenter.java   |  131 --
 .../cx/ring/tv/contact/TVContactPresenter.kt  |  108 +
 .../more/TVContactMoreActivity.java}          |   15 +-
 .../tv/contact/more/TVContactMoreFragment.kt  |  126 ++
 .../contact/more/TVContactMorePresenter.java  |   60 +
 .../tv/contact/more/TVContactMoreView.java    |   26 +
 .../conversation/TvConversationFragment.java  |  823 -------
 .../tv/conversation/TvConversationFragment.kt |  729 +++++++
 .../cx/ring/tv/main/BaseBrowseFragment.java   |    4 +-
 .../java/cx/ring/tv/main/HomeActivity.java    |  200 +-
 .../java/cx/ring/tv/main/MainFragment.java    |  496 -----
 .../main/java/cx/ring/tv/main/MainFragment.kt |  423 ++++
 .../java/cx/ring/tv/main/MainPresenter.java   |   13 +-
 .../main/java/cx/ring/tv/main/MainView.java   |    6 +-
 .../ring/tv/search/ContactSearchFragment.java |  152 --
 .../ring/tv/search/ContactSearchFragment.kt   |  125 ++
 .../tv/search/ContactSearchPresenter.java     |   10 +-
 .../cx/ring/tv/search/SearchActivity.java     |    2 +
 .../cx/ring/tv/settings/TVAboutFragment.java  |   89 +
 .../cx/ring/tv/settings/TVSettingsActivity.kt |   33 +
 .../TVSettingsFragment.java                   |   28 +-
 .../cx/ring/tv/views/CustomTitleView.java     |   20 +
 .../tv/views/NonOverlappingFrameLayout.java   |   23 +
 .../java/cx/ring/utils/AndroidFileUtils.java  |  604 ------
 .../java/cx/ring/utils/AndroidFileUtils.kt    |  569 +++++
 .../main/java/cx/ring/utils/BitmapUtils.java  |  185 --
 .../main/java/cx/ring/utils/BitmapUtils.kt    |  162 ++
 .../java/cx/ring/utils/ClipboardHelper.java   |   48 -
 .../java/cx/ring/utils/ClipboardHelper.kt     |   41 +
 .../java/cx/ring/utils/ContentUriHandler.java |  108 -
 .../java/cx/ring/utils/ContentUriHandler.kt   |  108 +
 .../java/cx/ring/utils/ConversationPath.java  |  209 --
 .../java/cx/ring/utils/ConversationPath.kt    |  190 ++
 .../main/java/cx/ring/utils/DeviceUtils.kt    |   38 +
 .../java/cx/ring/utils/JamiGlideModule.java   |   22 -
 .../java/cx/ring/utils/JamiGlideModule.kt     |   15 +
 .../cx/ring/utils/RegisteredNameFilter.java   |   59 -
 .../cx/ring/utils/RegisteredNameFilter.kt     |   67 +
 .../ring/utils/RegisteredNameTextWatcher.java |   80 -
 .../ring/utils/RegisteredNameTextWatcher.kt   |   65 +
 .../src/main/java/cx/ring/utils/Ringer.java   |   84 -
 .../app/src/main/java/cx/ring/utils/Ringer.kt |   67 +
 .../cx/ring/utils/ShadowScrollBehavior.java   |   90 -
 .../cx/ring/utils/TouchClickListener.java     |   54 -
 .../java/cx/ring/utils/TouchClickListener.kt  |   69 +
 .../ring/viewholders/SmartListViewHolder.java |  163 --
 .../ring/viewholders/SmartListViewHolder.kt   |  152 ++
 .../java/cx/ring/views/AvatarDrawable.java    |  689 ------
 .../main/java/cx/ring/views/AvatarDrawable.kt |  695 ++++++
 .../java/cx/ring/views/AvatarFactory.java     |  121 --
 .../main/java/cx/ring/views/AvatarFactory.kt  |  121 ++
 .../drawable/baseline_androidtv_account.xml   |   12 +
 .../drawable/baseline_androidtv_add_user.xml  |   15 +
 .../res/drawable/baseline_androidtv_chat.xml  |    9 +
 .../baseline_androidtv_clearconversation.xml  |   12 +
 .../baseline_androidtv_deletecontact.xml      |   19 +
 .../baseline_androidtv_link_device.xml        |    9 +
 .../baseline_androidtv_message_audio.xml      |    9 +
 .../baseline_androidtv_message_video.xml      |    9 +
 .../drawable/baseline_androidtv_settings.xml  |    9 +
 .../res/drawable/ic_tv_online_indicator.xml   |    5 +-
 .../src/main/res/drawable/tv_header_bg.xml    |    3 +-
 .../app/src/main/res/font/mulish_regular.ttf  |  Bin 0 -> 89244 bytes
 .../app/src/main/res/font/mulish_semibold.ttf |  Bin 0 -> 89340 bytes
 .../app/src/main/res/font/ubuntu_light.ttf    |  Bin 0 -> 361676 bytes
 .../app/src/main/res/font/ubuntu_medium.ttf   |  Bin 0 -> 284424 bytes
 .../app/src/main/res/font/ubuntu_regular.ttf  |  Bin 0 -> 298928 bytes
 .../layout-w720dp-land/tv_activity_about.xml  |   26 -
 .../res/layout-w720dp-land/tv_frag_call.xml   |   28 +
 .../main/res/layout/frag_conversation_tv.xml  |   29 +-
 .../src/main/res/layout/tv_about_layout.xml   |    8 +
 .../src/main/res/layout/tv_activity_about.xml |   26 -
 .../res/layout/tv_activity_contact_more.xml   |    8 +
 .../src/main/res/layout/tv_activity_home.xml  |   43 +-
 .../main/res/layout/tv_activity_settings.xml  |    4 +-
 .../app/src/main/res/layout/tv_frag_call.xml  |   28 +
 .../src/main/res/layout/tv_frag_contact.xml   |   28 +-
 .../app/src/main/res/layout/tv_frag_share.xml |    1 +
 .../app/src/main/res/layout/tv_titleview.xml  |   52 +-
 .../app/src/main/res/values/colors.xml        |    7 +
 .../app/src/main/res/values/strings.xml       |    7 +-
 .../src/main/res/values/strings_account.xml   |   15 +-
 .../app/src/main/res/values/styles.xml        |   79 +-
 .../app/src/main/res/xml/tv_about_pref.xml    |   33 +
 .../main/res/xml/tv_account_general_pref.xml  |   13 +-
 .../src/main/res/xml/tv_contact_more_pref.xml |   16 +
 .../application/JamiApplicationFirebase.java  |   69 -
 .../application/JamiApplicationFirebase.kt    |   65 +
 .../JamiFirebaseMessagingService.java         |   58 -
 .../services/JamiFirebaseMessagingService.kt  |   50 +
 ring-android/build.gradle                     |    8 +-
 .../gradle/wrapper/gradle-wrapper.properties  |    2 +-
 ring-android/libringclient/build.gradle       |    5 +
 .../jami/account/AccountWizardPresenter.java  |    2 +-
 .../account/JamiAccountCreationPresenter.java |  221 --
 .../account/JamiAccountCreationPresenter.kt   |  200 ++
 ...onView.java => JamiAccountCreationView.kt} |   35 +-
 .../account/JamiAccountSummaryPresenter.java  |   27 +-
 .../account/ProfileCreationPresenter.java     |   13 +-
 .../java/net/jami/call/CallPresenter.java     |  765 -------
 .../main/java/net/jami/call/CallPresenter.kt  |  698 ++++++
 .../ContactRequestsPresenter.java             |    6 +-
 .../conversation/ConversationPresenter.java   |  474 ----
 .../conversation/ConversationPresenter.kt     |  434 ++++
 .../jami/conversation/ConversationView.java   |  107 -
 .../net/jami/conversation/ConversationView.kt |   70 +
 .../net/jami/facades/ConversationFacade.java  |  742 -------
 .../src/main/java/net/jami/model/Account.java | 1124 ----------
 .../src/main/java/net/jami/model/Account.kt   |  995 +++++++++
 .../java/net/jami/model/AccountConfig.java    |  104 -
 .../main/java/net/jami/model/AccountConfig.kt |   90 +
 .../src/main/java/net/jami/model/Call.java    |  366 ----
 .../src/main/java/net/jami/model/Call.kt      |  291 +++
 .../main/java/net/jami/model/Conference.java  |  242 ---
 .../main/java/net/jami/model/Conference.kt    |  171 ++
 .../model/{ConfigKey.java => ConfigKey.kt}    |   48 +-
 .../src/main/java/net/jami/model/Contact.java |  361 ----
 .../src/main/java/net/jami/model/Contact.kt   |  245 +++
 .../java/net/jami/model/ContactEvent.java     |  103 -
 .../main/java/net/jami/model/ContactEvent.kt  |   96 +
 .../java/net/jami/model/Conversation.java     |  763 -------
 .../main/java/net/jami/model/Conversation.kt  |  660 ++++++
 .../java/net/jami/model/DataTransfer.java     |  192 --
 .../main/java/net/jami/model/DataTransfer.kt  |  176 ++
 .../main/java/net/jami/model/Interaction.java |  320 ---
 .../main/java/net/jami/model/Interaction.kt   |  232 ++
 .../main/java/net/jami/model/TextMessage.java |   80 -
 .../main/java/net/jami/model/TextMessage.kt   |   73 +
 .../java/net/jami/model/TrustRequest.java     |  119 -
 .../main/java/net/jami/model/TrustRequest.kt  |   78 +
 .../src/main/java/net/jami/model/Uri.java     |  192 --
 .../src/main/java/net/jami/model/Uri.kt       |  167 ++
 .../mvp/{GenericView.java => GenericView.kt}  |   10 +-
 .../main/java/net/jami/mvp/RootPresenter.java |   10 +-
 .../navigation/HomeNavigationPresenter.java   |   13 +-
 .../jami/navigation/HomeNavigationView.java   |    2 +
 ...wModel.java => HomeNavigationViewModel.kt} |   23 +-
 .../net/jami/services/AccountService.java     | 1918 -----------------
 .../java/net/jami/services/AccountService.kt  | 1785 +++++++++++++++
 .../java/net/jami/services/CallService.java   |  837 -------
 .../java/net/jami/services/CallService.kt     |  787 +++++++
 .../net/jami/services/ContactService.java     |  209 --
 .../java/net/jami/services/ContactService.kt  |  183 ++
 .../net/jami/services/ConversationFacade.kt   |  702 ++++++
 .../java/net/jami/services/DaemonService.java |   29 +-
 .../net/jami/services/HardwareService.java    |  266 ---
 .../java/net/jami/services/HardwareService.kt |  226 ++
 .../{LogService.java => LogService.kt}        |   32 +-
 .../jami/services/NotificationService.java    |    5 +-
 .../net/jami/services/PreferencesService.java |   10 +-
 .../java/net/jami/services/VCardService.java  |    3 +-
 .../net/jami/settings/SettingsPresenter.java  |    2 +-
 .../java/net/jami/share/SharePresenter.java   |    5 +-
 .../jami/smartlist/SmartListPresenter.java    |  203 --
 .../net/jami/smartlist/SmartListPresenter.kt  |  171 ++
 .../jami/smartlist/SmartListViewModel.java    |  216 --
 .../net/jami/smartlist/SmartListViewModel.kt  |  171 ++
 .../utils/{FileUtils.java => FileUtils.kt}    |   68 +-
 .../main/java/net/jami/utils/HashUtils.java   |   63 -
 .../main/java/net/jami/utils/HashUtils.kt}    |   45 +-
 .../src/main/java/net/jami/utils/Log.java     |    6 +-
 .../jami/utils/NameLookupInputHandler.java    |    4 +-
 .../{ProfileChunk.java => ProfileChunk.kt}    |   69 +-
 .../main/java/net/jami/utils/StringUtils.java |  156 --
 .../main/java/net/jami/utils/StringUtils.kt   |  150 ++
 .../net/jami/utils/SwigNativeConverter.java   |   57 -
 .../net/jami/utils/SwigNativeConverter.kt     |   45 +
 .../net/jami/utils/{Tuple.java => Tuple.kt}   |   49 +-
 .../main/java/net/jami/utils/VCardUtils.java  |  233 --
 .../main/java/net/jami/utils/VCardUtils.kt    |  231 ++
 351 files changed, 33433 insertions(+), 36657 deletions(-)
 delete mode 100644 ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/about/AboutFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/about/AboutFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/application/JamiApplication.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/CallActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/CallActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/LogsActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/LogsActivity.kt
 rename ring-android/app/src/main/java/cx/ring/client/{MediaViewerActivity.java => MediaViewerActivity.kt} (68%)
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt
 rename ring-android/app/src/main/java/cx/ring/client/{ShareActivity.java => ShareActivity.kt} (57%)
 delete mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt
 delete mode 100755 ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java
 delete mode 100755 ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionModule.java
 delete mode 100755 ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
 create mode 100755 ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/linkpreview/LinkListener.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/linkpreview/LinkPreview.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/linkpreview/PreviewData.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerUtils.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/service/DRingService.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/service/DRingService.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
 delete mode 100644 ring-android/app/src/main/java/cx/ring/service/LocationSharingService.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/service/LocationSharingService.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/share/ScanFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/share/ScanFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/about/AboutActivity.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/cards/CardView.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt
 rename ring-android/app/src/main/java/cx/ring/tv/{account/TVSettingsActivity.java => contact/more/TVContactMoreActivity.java} (69%)
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMorePresenter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreView.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/settings/TVAboutFragment.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsActivity.kt
 rename ring-android/app/src/main/java/cx/ring/tv/{account => settings}/TVSettingsFragment.java (87%)
 create mode 100644 ring-android/app/src/main/java/cx/ring/tv/views/NonOverlappingFrameLayout.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/ConversationPath.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/Ringer.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/Ringer.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/ShadowScrollBehavior.java
 delete mode 100644 ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
 delete mode 100644 ring-android/app/src/main/java/cx/ring/views/AvatarFactory.java
 create mode 100644 ring-android/app/src/main/java/cx/ring/views/AvatarFactory.kt
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_account.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_add_user.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_chat.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_clearconversation.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_deletecontact.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_link_device.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_message_audio.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_message_video.xml
 create mode 100644 ring-android/app/src/main/res/drawable/baseline_androidtv_settings.xml
 create mode 100644 ring-android/app/src/main/res/font/mulish_regular.ttf
 create mode 100644 ring-android/app/src/main/res/font/mulish_semibold.ttf
 create mode 100644 ring-android/app/src/main/res/font/ubuntu_light.ttf
 create mode 100644 ring-android/app/src/main/res/font/ubuntu_medium.ttf
 create mode 100644 ring-android/app/src/main/res/font/ubuntu_regular.ttf
 delete mode 100644 ring-android/app/src/main/res/layout-w720dp-land/tv_activity_about.xml
 create mode 100644 ring-android/app/src/main/res/layout/tv_about_layout.xml
 delete mode 100644 ring-android/app/src/main/res/layout/tv_activity_about.xml
 create mode 100644 ring-android/app/src/main/res/layout/tv_activity_contact_more.xml
 create mode 100644 ring-android/app/src/main/res/xml/tv_about_pref.xml
 create mode 100644 ring-android/app/src/main/res/xml/tv_contact_more_pref.xml
 delete mode 100644 ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.java
 create mode 100644 ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.kt
 delete mode 100644 ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.java
 create mode 100644 ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt
 rename ring-android/libringclient/src/main/java/net/jami/account/{JamiAccountCreationView.java => JamiAccountCreationView.kt} (58%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Account.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Account.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Call.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Call.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Conference.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Conference.kt
 rename ring-android/libringclient/src/main/java/net/jami/model/{ConfigKey.java => ConfigKey.kt} (84%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Contact.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Contact.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Conversation.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Interaction.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Interaction.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/TextMessage.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/TextMessage.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Uri.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/model/Uri.kt
 rename ring-android/libringclient/src/main/java/net/jami/mvp/{GenericView.java => GenericView.kt} (89%)
 rename ring-android/libringclient/src/main/java/net/jami/navigation/{HomeNavigationViewModel.java => HomeNavigationViewModel.kt} (67%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/services/AccountService.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/services/CallService.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/services/CallService.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/services/ContactService.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt
 rename ring-android/libringclient/src/main/java/net/jami/services/{LogService.java => LogService.kt} (65%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt
 rename ring-android/libringclient/src/main/java/net/jami/utils/{FileUtils.java => FileUtils.kt} (50%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.java
 rename ring-android/{app/src/main/java/cx/ring/utils/DeviceUtils.java => libringclient/src/main/java/net/jami/utils/HashUtils.kt} (51%)
 rename ring-android/libringclient/src/main/java/net/jami/utils/{ProfileChunk.java => ProfileChunk.kt} (50%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.kt
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.kt
 rename ring-android/libringclient/src/main/java/net/jami/utils/{Tuple.java => Tuple.kt} (53%)
 delete mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.java
 create mode 100644 ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt

diff --git a/ring-android/app/build.gradle b/ring-android/app/build.gradle
index b2d76d3b9..c9f198ea6 100644
--- a/ring-android/app/build.gradle
+++ b/ring-android/app/build.gradle
@@ -1,4 +1,7 @@
 apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'dagger.hilt.android.plugin'
 
 def buildFirebase = project.hasProperty('buildFirebase') || getGradle().getStartParameter().getTaskRequests().toString().contains('Firebase')
 
@@ -9,14 +12,12 @@ android {
     defaultConfig {
         minSdkVersion 21
         targetSdkVersion 30
-        versionCode 303
-        versionName "20210611-01"
+        versionCode 304
+        versionName "20210721-01"
     }
     sourceSets {
         main {
-            aidl.srcDirs = ['src/main/java']
             jniLibs.srcDir 'src/main/libs'
-            jni.srcDirs = []
         }
     }
 
@@ -73,29 +74,31 @@ android {
         sourceCompatibility = JavaVersion.VERSION_1_8
         targetCompatibility = JavaVersion.VERSION_1_8
     }
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
 }
 
 dependencies {
-    def dagger_version = '2.36'
-
     implementation fileTree(include: '*.jar', dir: 'libs')
     implementation project(':libringclient')
 
-    implementation 'androidx.core:core:1.6.0'
-    implementation "androidx.appcompat:appcompat:1.3.0"
-    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    implementation 'androidx.core:core-ktx:1.6.0'
+    implementation "androidx.appcompat:appcompat:1.3.1"
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
     implementation "androidx.legacy:legacy-support-core-utils:1.0.0"
     implementation "androidx.cardview:cardview:1.0.0"
-    implementation "androidx.preference:preference:1.1.1"
+    implementation "androidx.preference:preference-ktx:1.1.1"
     implementation "androidx.recyclerview:recyclerview:1.2.1"
-    implementation "androidx.leanback:leanback:1.1.0-rc01"
-    implementation "androidx.leanback:leanback-preference:1.1.0-rc01"
+    implementation "androidx.leanback:leanback:1.2.0-alpha01"
+    implementation "androidx.leanback:leanback-preference:1.2.0-alpha01"
     implementation 'androidx.tvprovider:tvprovider:1.0.0'
     implementation "androidx.media:media:1.4.1"
     implementation "androidx.percentlayout:percentlayout:1.0.0"
     implementation "com.google.android.material:material:1.4.0"
     implementation 'com.google.android:flexbox:2.0.1'
-    implementation 'org.osmdroid:osmdroid-android:6.1.10'
+    implementation 'org.osmdroid:osmdroid-android:6.1.11'
     implementation "androidx.sharetarget:sharetarget:1.1.0"
 
     // ORM
@@ -108,12 +111,12 @@ dependencies {
     implementation 'com.rodolfonavalon:ShapeRippleLibrary:1.0.0'
 
     // Dagger dependency injection
-    implementation "com.google.dagger:dagger:$dagger_version"
-    annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
+    implementation("com.google.dagger:hilt-android:$hilt_version")
+    kapt("com.google.dagger:hilt-android-compiler:$hilt_version")
 
     // Glide
     implementation 'com.github.bumptech.glide:glide:4.12.0'
-    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
+    kapt 'com.github.bumptech.glide:compiler:4.12.0'
 
     // RxAndroid
     implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
diff --git a/ring-android/app/proguard-rules.pro b/ring-android/app/proguard-rules.pro
index 683fa81f7..4738dde5b 100644
--- a/ring-android/app/proguard-rules.pro
+++ b/ring-android/app/proguard-rules.pro
@@ -53,9 +53,13 @@
 
 # Glide
 -keep public class * implements com.bumptech.glide.module.GlideModule
--keep public class * extends com.bumptech.glide.module.AppGlideModule
--keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
+-keep class * extends com.bumptech.glide.module.AppGlideModule {
+ <init>(...);
+}
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
   **[] $VALUES;
   public *;
 }
-
+-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
+  *** rewind();
+}
diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml
index 03cccb908..ff781502e 100644
--- a/ring-android/app/src/main/AndroidManifest.xml
+++ b/ring-android/app/src/main/AndroidManifest.xml
@@ -347,11 +347,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             android:icon="@mipmap/ic_launcher"
             android:label="@string/app_name"
             android:theme="@style/Theme.Leanback" />
-        <activity
-            android:name=".tv.about.AboutActivity"
-            android:icon="@mipmap/ic_launcher"
-            android:label="@string/app_name"
-            android:theme="@style/Theme.Leanback" />
         <activity
             android:name=".tv.account.TVShareActivity"
             android:icon="@mipmap/ic_launcher"
@@ -401,7 +396,10 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             </intent-filter>
         </activity>
         <activity
-            android:name=".tv.account.TVSettingsActivity"
+            android:name=".tv.settings.TVSettingsActivity"
+            android:theme="@style/LeanbackPreferences" />
+        <activity
+            android:name=".tv.contact.more.TVContactMoreActivity"
             android:theme="@style/LeanbackPreferences" />
 
         <provider
diff --git a/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.java b/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.java
deleted file mode 100644
index f117b9efe..000000000
--- a/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.about;
-
-import android.app.Dialog;
-import androidx.annotation.NonNull;
-import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import android.view.View;
-
-import cx.ring.R;
-
-public class AboutBottomSheetDialogFragment extends BottomSheetDialogFragment {
-
-    private BottomSheetBehavior.BottomSheetCallback mCallback = new BottomSheetBehavior.BottomSheetCallback() {
-        @Override
-        public void onStateChanged(@NonNull View bottomSheet, int newState) {
-            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
-                dismiss();
-            }
-        }
-
-        @Override
-        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
-
-        }
-    };
-
-    @Override
-    public void setupDialog(Dialog dialog, int style) {
-        View contentView = View.inflate(getContext(), R.layout.dialog_about, null);
-        dialog.setContentView(contentView);
-
-        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
-        CoordinatorLayout.Behavior behavior = params.getBehavior();
-
-        if (behavior instanceof BottomSheetBehavior) {
-            ((BottomSheetBehavior) behavior).addBottomSheetCallback(mCallback);
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.kt b/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.kt
new file mode 100644
index 000000000..d87a1da0b
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/about/AboutBottomSheetDialogFragment.kt
@@ -0,0 +1,50 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.about
+
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
+import android.view.View
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import android.app.Dialog
+import cx.ring.R
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+
+class AboutBottomSheetDialogFragment : BottomSheetDialogFragment() {
+    private val mCallback: BottomSheetCallback = object : BottomSheetCallback() {
+        override fun onStateChanged(bottomSheet: View, newState: Int) {
+            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
+                dismiss()
+            }
+        }
+
+        override fun onSlide(bottomSheet: View, slideOffset: Float) {}
+    }
+
+    override fun setupDialog(dialog: Dialog, style: Int) {
+        val contentView = View.inflate(context, R.layout.dialog_about, null)
+        dialog.setContentView(contentView)
+        val params = (contentView.parent as View).layoutParams as CoordinatorLayout.LayoutParams
+        val behavior = params.behavior
+        if (behavior is BottomSheetBehavior<*>) {
+            behavior.addBottomSheetCallback(mCallback)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/about/AboutFragment.java b/ring-android/app/src/main/java/cx/ring/about/AboutFragment.java
deleted file mode 100644
index 53a550e0a..000000000
--- a/ring-android/app/src/main/java/cx/ring/about/AboutFragment.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.about;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-import com.google.android.material.snackbar.Snackbar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-
-import cx.ring.BuildConfig;
-import cx.ring.R;
-import cx.ring.client.HomeActivity;
-import cx.ring.databinding.FragAboutBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import net.jami.mvp.RootPresenter;
-
-public class AboutFragment extends BaseSupportFragment<RootPresenter> {
-
-    private FragAboutBinding binding;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragAboutBinding.inflate(inflater, container, false);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        setHasOptionsMenu(true);
-        binding.release.setText(getString(R.string.app_release, BuildConfig.VERSION_NAME));
-        binding.logo.setOnClickListener(v ->  openWebsite(getString(R.string.app_website)));
-        binding.sflLogo.setOnClickListener(v -> openWebsite(getString(R.string.savoirfairelinux_website)));
-        binding.contributeContainer.setOnClickListener(v -> openWebsite(getString(R.string.ring_contribute_website)));
-        binding.licenseContainer.setOnClickListener(v -> openWebsite(getString(R.string.gnu_license_website)));
-        binding.emailReportContainer.setOnClickListener(v -> sendFeedbackEmail());
-        binding.credits.setOnClickListener(v -> creditsClicked());
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_about);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) {
-        menu.clear();
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(Menu menu) {
-        menu.clear();
-    }
-
-    private void sendFeedbackEmail() {
-        Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + "jami@gnu.org"));
-        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "[" + getText(R.string.app_name) + " Android - " + BuildConfig.VERSION_NAME + "]");
-        launchSystemIntent(emailIntent, R.string.no_email_app_installed);
-    }
-
-    private void creditsClicked() {
-        BottomSheetDialogFragment dialog = new AboutBottomSheetDialogFragment();
-        dialog.show(getChildFragmentManager(), dialog.getTag());
-    }
-
-    private void openWebsite(String url) {
-        launchSystemIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(url)), R.string.no_browser_app_installed);
-    }
-
-    private void launchSystemIntent(Intent intentToLaunch, @StringRes int missingRes) {
-        try  {
-            startActivity(intentToLaunch);
-        } catch (Exception e) {
-            View rootView = getView();
-            if (rootView != null) {
-                Snackbar.make(rootView, getText(missingRes), Snackbar.LENGTH_SHORT).show();
-            }
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/about/AboutFragment.kt b/ring-android/app/src/main/java/cx/ring/about/AboutFragment.kt
new file mode 100644
index 000000000..567ad7e3b
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/about/AboutFragment.kt
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.about
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.os.Bundle
+import android.view.View
+import cx.ring.R
+import android.view.Menu
+import android.view.MenuInflater
+import android.content.Intent
+import android.net.Uri
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import java.lang.Exception
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.BuildConfig
+import cx.ring.client.HomeActivity
+import cx.ring.databinding.FragAboutBinding
+
+class AboutFragment : Fragment() {
+    private var binding: FragAboutBinding? = null
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        binding = FragAboutBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        setHasOptionsMenu(true)
+        binding!!.apply {
+            release.text = getString(R.string.app_release, BuildConfig.VERSION_NAME)
+            logo.setOnClickListener { openWebsite(getString(R.string.app_website)) }
+            sflLogo.setOnClickListener { openWebsite(getString(R.string.savoirfairelinux_website)) }
+            contributeContainer.setOnClickListener { openWebsite(getString(R.string.ring_contribute_website)) }
+            licenseContainer.setOnClickListener { openWebsite(getString(R.string.gnu_license_website)) }
+            emailReportContainer.setOnClickListener { sendFeedbackEmail() }
+            credits.setOnClickListener { creditsClicked() }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        (requireActivity() as HomeActivity).setToolbarTitle(R.string.menu_item_about)
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        menu.clear()
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        menu.clear()
+    }
+
+    private fun sendFeedbackEmail() {
+        val emailIntent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + "jami@gnu.org"))
+        emailIntent.putExtra(Intent.EXTRA_SUBJECT, "[" + getText(R.string.app_name) + " Android - " + BuildConfig.VERSION_NAME + "]")
+        launchSystemIntent(emailIntent, R.string.no_email_app_installed)
+    }
+
+    private fun creditsClicked() {
+        val dialog: BottomSheetDialogFragment = AboutBottomSheetDialogFragment()
+        dialog.show(childFragmentManager, dialog.tag)
+    }
+
+    private fun openWebsite(url: String) {
+        launchSystemIntent(Intent(Intent.ACTION_VIEW, Uri.parse(url)), R.string.no_browser_app_installed)
+    }
+
+    private fun launchSystemIntent(intentToLaunch: Intent, @StringRes missingRes: Int) {
+        try {
+            startActivity(intentToLaunch)
+        } catch (e: Exception) {
+            val rootView = view
+            if (rootView != null) {
+                Snackbar.make(rootView, getText(missingRes), Snackbar.LENGTH_SHORT).show()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.java b/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.java
deleted file mode 100644
index 2ef3dd9c3..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *          Alexandre Savard <alexandre.savard@savoirfairelinux.com>
- *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *          Loïc Siret <loic.siret@savoirfairelinux.com>
- *          AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.account;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.recyclerview.widget.RecyclerView;
-
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.HomeActivity;
-import cx.ring.contactrequests.BlockListFragment;
-import cx.ring.databinding.FragAccountSettingsBinding;
-import cx.ring.fragments.AdvancedAccountFragment;
-import cx.ring.fragments.GeneralAccountFragment;
-import cx.ring.fragments.MediaPreferenceFragment;
-import cx.ring.fragments.SecurityAccountFragment;
-import cx.ring.interfaces.BackHandlerInterface;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.utils.DeviceUtils;
-
-public class AccountEditionFragment extends BaseSupportFragment<AccountEditionPresenter> implements
-        BackHandlerInterface,
-        AccountEditionView,
-        ViewTreeObserver.OnScrollChangedListener  {
-    private static final String TAG = AccountEditionFragment.class.getSimpleName();
-
-    public static final String ACCOUNT_ID_KEY = AccountEditionFragment.class.getCanonicalName() + "accountid";
-    static final String ACCOUNT_HAS_PASSWORD_KEY = AccountEditionFragment.class.getCanonicalName() + "hasPassword";
-    public static final String ACCOUNT_ID = TAG + "accountID";
-
-    private static final int SCROLL_DIRECTION_UP = -1;
-
-    private FragAccountSettingsBinding mBinding;
-
-    private boolean mIsVisible;
-
-    private String mAccountId;
-    private boolean mAccountIsJami;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        mBinding = FragAccountSettingsBinding.inflate(inflater, container, false);
-        // dependency injection
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return mBinding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mBinding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        setHasOptionsMenu(true);
-        super.onViewCreated(view, savedInstanceState);
-
-        mAccountId = getArguments().getString(ACCOUNT_ID);
-
-        HomeActivity activity = (HomeActivity) getActivity();
-        if (activity != null && DeviceUtils.isTablet(activity)) {
-            activity.setTabletTitle(R.string.navigation_item_account);
-        }
-
-        mBinding.fragmentContainer.getViewTreeObserver().addOnScrollChangedListener(this);
-
-        presenter.init(mAccountId);
-    }
-
-    @Override
-    public void displaySummary(String accountId) {
-        toggleView(accountId, true);
-        FragmentManager fragmentManager = getChildFragmentManager();
-        Fragment existingFragment = fragmentManager.findFragmentByTag(JamiAccountSummaryFragment.TAG);
-        Bundle args = new Bundle();
-        args.putString(ACCOUNT_ID_KEY, accountId);
-        if (existingFragment == null) {
-            JamiAccountSummaryFragment fragment = new JamiAccountSummaryFragment();
-            fragment.setArguments(args);
-            fragmentManager.beginTransaction()
-                    .add(R.id.fragment_container, fragment, JamiAccountSummaryFragment.TAG)
-                    .commit();
-        } else {
-            if (!existingFragment.isStateSaved())
-                existingFragment.setArguments(args);
-            ((JamiAccountSummaryFragment) existingFragment).setAccount(accountId);
-        }
-    }
-
-    @Override
-    public void displaySIPView(String accountId) {
-        toggleView(accountId, false);
-    }
-
-    @Override
-    public void initViewPager(String accountId, boolean isJami) {
-        mBinding.pager.setOffscreenPageLimit(4);
-        mBinding.slidingTabs.setupWithViewPager(mBinding.pager);
-        mBinding.pager.setAdapter(new PreferencesPagerAdapter(getChildFragmentManager(), getActivity(), accountId, isJami));
-        BlockListFragment existingFragment = (BlockListFragment) getChildFragmentManager().findFragmentByTag(BlockListFragment.TAG);
-        if (existingFragment != null) {
-            Bundle args = new Bundle();
-            args.putString(ACCOUNT_ID_KEY, accountId);
-            if (!existingFragment.isStateSaved())
-                existingFragment.setArguments(args);
-            existingFragment.setAccount(accountId);
-        }
-    }
-
-    @Override
-    public void goToBlackList(String accountId) {
-        BlockListFragment blockListFragment = new BlockListFragment();
-        Bundle args = new Bundle();
-        args.putString(ACCOUNT_ID_KEY, accountId);
-        blockListFragment.setArguments(args);
-        getChildFragmentManager().beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .addToBackStack(BlockListFragment.TAG)
-                .replace(R.id.fragment_container, blockListFragment, BlockListFragment.TAG)
-                .commit();
-        mBinding.slidingTabs.setVisibility(View.GONE);
-        mBinding.pager.setVisibility(View.GONE);
-        mBinding.fragmentContainer.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) {
-        menu.clear();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        presenter.bindView(this);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        setBackListenerEnabled(false);
-    }
-
-    public boolean onBackPressed() {
-        if (mBinding == null)
-            return false;
-        mIsVisible = false;
-
-        if (getActivity() instanceof HomeActivity)
-            ((HomeActivity) getActivity()).setToolbarOutlineState(true);
-        if (mBinding.fragmentContainer.getVisibility() != View.VISIBLE) {
-            toggleView(mAccountId, mAccountIsJami);
-            return true;
-        }
-        JamiAccountSummaryFragment summaryFragment = (JamiAccountSummaryFragment) getChildFragmentManager().findFragmentByTag(JamiAccountSummaryFragment.TAG);
-        if (summaryFragment != null && summaryFragment.onBackPressed()) {
-            return true;
-        }
-        return getChildFragmentManager().popBackStackImmediate();
-    }
-
-    private void toggleView(String accountId, boolean isJami) {
-        mAccountId = accountId;
-        mAccountIsJami = isJami;
-        mBinding.slidingTabs.setVisibility(isJami? View.GONE : View.VISIBLE);
-        mBinding.pager.setVisibility(isJami? View.GONE : View.VISIBLE);
-        mBinding.fragmentContainer.setVisibility(isJami? View.VISIBLE : View.GONE);
-        setBackListenerEnabled(isJami);
-    }
-
-    @Override
-    public void exit() {
-        Activity activity = getActivity();
-        if (activity != null)
-            activity.onBackPressed();
-    }
-
-    private void setBackListenerEnabled(boolean enable) {
-        Activity activity = getActivity();
-        if (activity instanceof HomeActivity)
-            ((HomeActivity) activity).setAccountFragmentOnBackPressedListener(enable ? this : null);
-    }
-
-    private static class PreferencesPagerAdapter extends FragmentStatePagerAdapter {
-        private final Context mContext;
-        private final String accountId;
-        private final boolean isJamiAccount;
-
-        PreferencesPagerAdapter(FragmentManager fm, Context mContext, String accountId, boolean isJamiAccount) {
-            super(fm);
-            this.mContext = mContext;
-            this.accountId = accountId;
-            this.isJamiAccount = isJamiAccount;
-        }
-
-        @StringRes
-        private static int getRingPanelTitle(int position) {
-            switch (position) {
-                case 0:
-                    return R.string.account_preferences_basic_tab;
-                case 1:
-                    return R.string.account_preferences_media_tab;
-                case 2:
-                    return R.string.account_preferences_advanced_tab;
-                default:
-                    return -1;
-            }
-        }
-
-        @StringRes
-        private static int getSIPPanelTitle(int position) {
-            switch (position) {
-                case 0:
-                    return R.string.account_preferences_basic_tab;
-                case 1:
-                    return R.string.account_preferences_media_tab;
-                case 2:
-                    return R.string.account_preferences_advanced_tab;
-                case 3:
-                    return R.string.account_preferences_security_tab;
-                default:
-                    return -1;
-            }
-        }
-
-        @Override
-        public int getCount() {
-            return isJamiAccount ? 3 : 4;
-        }
-
-        @NonNull
-        @Override
-        public Fragment getItem(int position) {
-            return isJamiAccount ? getJamiPanel(position) : getSIPPanel(position);
-        }
-
-        @Override
-        public CharSequence getPageTitle(int position) {
-            int resId = isJamiAccount ? getRingPanelTitle(position) : getSIPPanelTitle(position);
-            return mContext.getString(resId);
-        }
-
-        @NonNull
-        private Fragment getJamiPanel(int position) {
-            switch (position) {
-                case 0:
-                    return fragmentWithBundle(new GeneralAccountFragment());
-                case 1:
-                    return fragmentWithBundle(new MediaPreferenceFragment());
-                case 2:
-                    return fragmentWithBundle(new AdvancedAccountFragment());
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-
-        @NonNull
-        private Fragment getSIPPanel(int position) {
-            switch (position) {
-                case 0:
-                    return GeneralAccountFragment.newInstance(accountId);
-                case 1:
-                    return MediaPreferenceFragment.newInstance(accountId);
-                case 2:
-                    return fragmentWithBundle(new AdvancedAccountFragment());
-                case 3:
-                    return fragmentWithBundle(new SecurityAccountFragment());
-                default:
-                    throw new IllegalArgumentException();
-            }
-        }
-
-        private Fragment fragmentWithBundle(Fragment result) {
-            Bundle args = new Bundle();
-            args.putString(ACCOUNT_ID_KEY, accountId);
-            result.setArguments(args);
-            return result;
-        }
-    }
-
-    @Override
-    public void onScrollChanged() {
-        setupElevation();
-    }
-
-    private void setupElevation() {
-        if (mBinding == null || !mIsVisible) {
-            return;
-        }
-        Activity activity = getActivity();
-        if (!(activity instanceof HomeActivity))
-            return;
-        LinearLayout ll = (LinearLayout) mBinding.pager.getChildAt(mBinding.pager.getCurrentItem());
-        if (ll == null) return;
-        RecyclerView rv = (RecyclerView)((FrameLayout) ll.getChildAt(0)).getChildAt(0);
-        if (rv == null) return;
-        HomeActivity homeActivity = (HomeActivity) activity;
-        if (rv.canScrollVertically(SCROLL_DIRECTION_UP)) {
-            mBinding.slidingTabs.setElevation(mBinding.slidingTabs.getResources().getDimension(R.dimen.toolbar_elevation));
-            homeActivity.setToolbarElevation(true);
-            homeActivity.setToolbarOutlineState(false);
-        } else {
-            mBinding.slidingTabs.setElevation(0);
-            homeActivity.setToolbarElevation(false);
-            homeActivity.setToolbarOutlineState(true);
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt b/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt
new file mode 100644
index 000000000..19f84f4ea
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt
@@ -0,0 +1,287 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *          Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Loïc Siret <loic.siret@savoirfairelinux.com>
+ *          AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.account
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.view.*
+import android.view.ViewTreeObserver.OnScrollChangedListener
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.annotation.StringRes
+import androidx.fragment.app.*
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.R
+import cx.ring.client.HomeActivity
+import cx.ring.contactrequests.BlockListFragment
+import cx.ring.databinding.FragAccountSettingsBinding
+import cx.ring.fragments.AdvancedAccountFragment
+import cx.ring.fragments.GeneralAccountFragment
+import cx.ring.fragments.MediaPreferenceFragment
+import cx.ring.fragments.SecurityAccountFragment
+import cx.ring.interfaces.BackHandlerInterface
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.utils.DeviceUtils
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, AccountEditionView>(),
+    BackHandlerInterface, AccountEditionView, OnScrollChangedListener {
+    private var mBinding: FragAccountSettingsBinding? = null
+    private var mIsVisible = false
+    private var mAccountId: String? = null
+    private var mAccountIsJami = false
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        mBinding = FragAccountSettingsBinding.inflate(inflater, container, false)
+        return mBinding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mBinding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        setHasOptionsMenu(true)
+        super.onViewCreated(view, savedInstanceState)
+        mAccountId = requireArguments().getString(ACCOUNT_ID_KEY)
+        val activity = activity as HomeActivity?
+        if (activity != null && DeviceUtils.isTablet(activity)) {
+            activity.setTabletTitle(R.string.navigation_item_account)
+        }
+        mBinding!!.fragmentContainer.viewTreeObserver.addOnScrollChangedListener(this)
+        presenter.init(mAccountId)
+    }
+
+    override fun displaySummary(accountId: String) {
+        toggleView(accountId, true)
+        val fragmentManager = childFragmentManager
+        val existingFragment = fragmentManager.findFragmentByTag(JamiAccountSummaryFragment.TAG)
+        val args = Bundle()
+        args.putString(ACCOUNT_ID_KEY, accountId)
+        if (existingFragment == null) {
+            val fragment = JamiAccountSummaryFragment()
+            fragment.arguments = args
+            fragmentManager.beginTransaction()
+                .add(R.id.fragment_container, fragment, JamiAccountSummaryFragment.TAG)
+                .commit()
+        } else {
+            if (!existingFragment.isStateSaved) existingFragment.arguments = args
+            (existingFragment as JamiAccountSummaryFragment).setAccount(accountId)
+        }
+    }
+
+    override fun displaySIPView(accountId: String) {
+        toggleView(accountId, false)
+    }
+
+    override fun initViewPager(accountId: String, isJami: Boolean) {
+        mBinding!!.pager.offscreenPageLimit = 4
+        mBinding!!.slidingTabs.setupWithViewPager(mBinding!!.pager)
+        mBinding!!.pager.adapter =
+            PreferencesPagerAdapter(childFragmentManager, activity, accountId, isJami)
+        val existingFragment =
+            childFragmentManager.findFragmentByTag(BlockListFragment.TAG) as BlockListFragment?
+        if (existingFragment != null) {
+            val args = Bundle()
+            args.putString(ACCOUNT_ID_KEY, accountId)
+            if (!existingFragment.isStateSaved) existingFragment.arguments = args
+            existingFragment.setAccount(accountId)
+        }
+    }
+
+    override fun goToBlackList(accountId: String) {
+        val blockListFragment = BlockListFragment()
+        val args = Bundle()
+        args.putString(ACCOUNT_ID_KEY, accountId)
+        blockListFragment.arguments = args
+        childFragmentManager.beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .addToBackStack(BlockListFragment.TAG)
+            .replace(R.id.fragment_container, blockListFragment, BlockListFragment.TAG)
+            .commit()
+        mBinding!!.slidingTabs.visibility = View.GONE
+        mBinding!!.pager.visibility = View.GONE
+        mBinding!!.fragmentContainer.visibility = View.VISIBLE
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        menu.clear()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        presenter.bindView(this)
+    }
+
+    override fun onPause() {
+        super.onPause()
+        setBackListenerEnabled(false)
+    }
+
+    override fun onBackPressed(): Boolean {
+        if (mBinding == null) return false
+        mIsVisible = false
+        if (activity is HomeActivity) (activity as HomeActivity?)!!.setToolbarOutlineState(true)
+        if (mBinding!!.fragmentContainer.visibility != View.VISIBLE) {
+            toggleView(mAccountId, mAccountIsJami)
+            return true
+        }
+        val summaryFragment =
+            childFragmentManager.findFragmentByTag(JamiAccountSummaryFragment.TAG) as JamiAccountSummaryFragment?
+        return if (summaryFragment != null && summaryFragment.onBackPressed()) {
+            true
+        } else childFragmentManager.popBackStackImmediate()
+    }
+
+    private fun toggleView(accountId: String?, isJami: Boolean) {
+        mAccountId = accountId
+        mAccountIsJami = isJami
+        mBinding!!.slidingTabs.visibility = if (isJami) View.GONE else View.VISIBLE
+        mBinding!!.pager.visibility = if (isJami) View.GONE else View.VISIBLE
+        mBinding!!.fragmentContainer.visibility = if (isJami) View.VISIBLE else View.GONE
+        setBackListenerEnabled(isJami)
+    }
+
+    override fun exit() {
+        val activity: Activity? = activity
+        activity?.onBackPressed()
+    }
+
+    private fun setBackListenerEnabled(enable: Boolean) {
+        val activity: Activity? = activity
+        if (activity is HomeActivity) activity.setAccountFragmentOnBackPressedListener(if (enable) this else null)
+    }
+
+    private class PreferencesPagerAdapter internal constructor(
+        fm: FragmentManager?,
+        private val mContext: Context?,
+        private val accountId: String,
+        private val isJamiAccount: Boolean
+    ) : FragmentStatePagerAdapter(
+        fm!!
+    ) {
+        override fun getCount(): Int {
+            return if (isJamiAccount) 3 else 4
+        }
+
+        override fun getItem(position: Int): Fragment {
+            return if (isJamiAccount) getJamiPanel(position) else getSIPPanel(position)
+        }
+
+        override fun getPageTitle(position: Int): CharSequence? {
+            val resId =
+                if (isJamiAccount) getRingPanelTitle(position) else getSIPPanelTitle(position)
+            return mContext!!.getString(resId)
+        }
+
+        private fun getJamiPanel(position: Int): Fragment {
+            return when (position) {
+                0 -> fragmentWithBundle(GeneralAccountFragment())
+                1 -> fragmentWithBundle(MediaPreferenceFragment())
+                2 -> fragmentWithBundle(AdvancedAccountFragment())
+                else -> throw IllegalArgumentException()
+            }
+        }
+
+        private fun getSIPPanel(position: Int): Fragment {
+            return when (position) {
+                0 -> GeneralAccountFragment.newInstance(accountId)
+                1 -> MediaPreferenceFragment.newInstance(accountId)
+                2 -> fragmentWithBundle(AdvancedAccountFragment())
+                3 -> fragmentWithBundle(SecurityAccountFragment())
+                else -> throw IllegalArgumentException()
+            }
+        }
+
+        private fun fragmentWithBundle(result: Fragment): Fragment {
+            val args = Bundle()
+            args.putString(ACCOUNT_ID_KEY, accountId)
+            result.arguments = args
+            return result
+        }
+
+        companion object {
+            @StringRes
+            private fun getRingPanelTitle(position: Int): Int {
+                return when (position) {
+                    0 -> R.string.account_preferences_basic_tab
+                    1 -> R.string.account_preferences_media_tab
+                    2 -> R.string.account_preferences_advanced_tab
+                    else -> -1
+                }
+            }
+
+            @StringRes
+            private fun getSIPPanelTitle(position: Int): Int {
+                return when (position) {
+                    0 -> R.string.account_preferences_basic_tab
+                    1 -> R.string.account_preferences_media_tab
+                    2 -> R.string.account_preferences_advanced_tab
+                    3 -> R.string.account_preferences_security_tab
+                    else -> -1
+                }
+            }
+        }
+    }
+
+    override fun onScrollChanged() {
+        setupElevation()
+    }
+
+    private fun setupElevation() {
+        if (mBinding == null || !mIsVisible) {
+            return
+        }
+        val activity: FragmentActivity = activity as? HomeActivity ?: return
+        val ll = mBinding!!.pager.getChildAt(mBinding!!.pager.currentItem) as LinearLayout
+        val rv = (ll.getChildAt(0) as FrameLayout).getChildAt(0) as RecyclerView
+        val homeActivity = activity as HomeActivity
+        if (rv.canScrollVertically(SCROLL_DIRECTION_UP)) {
+            mBinding!!.slidingTabs.elevation =
+                mBinding!!.slidingTabs.resources.getDimension(R.dimen.toolbar_elevation)
+            homeActivity.setToolbarElevation(true)
+            homeActivity.setToolbarOutlineState(false)
+        } else {
+            mBinding!!.slidingTabs.elevation = 0f
+            homeActivity.setToolbarElevation(false)
+            homeActivity.setToolbarOutlineState(true)
+        }
+    }
+
+    companion object {
+        private val TAG = AccountEditionFragment::class.simpleName
+        @JvmField
+        val ACCOUNT_ID_KEY = AccountEditionFragment::class.qualifiedName + "accountid"
+        @JvmField
+        val ACCOUNT_HAS_PASSWORD_KEY =
+            AccountEditionFragment::class.qualifiedName + "hasPassword"
+        private const val SCROLL_DIRECTION_UP = -1
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.java b/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.java
deleted file mode 100644
index 7a695a0f7..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.account;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.appcompat.app.AlertDialog;
-
-import android.text.TextUtils;
-import android.widget.Toast;
-
-import java.io.File;
-import java.util.List;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.HomeActivity;
-import cx.ring.fragments.AccountMigrationFragment;
-import cx.ring.fragments.SIPAccountCreationFragment;
-
-import net.jami.account.AccountWizardPresenter;
-import net.jami.account.AccountWizardView;
-import net.jami.model.Account;
-import net.jami.model.AccountConfig;
-import cx.ring.mvp.BaseActivity;
-import net.jami.mvp.AccountCreationModel;
-import net.jami.utils.VCardUtils;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class AccountWizardActivity extends BaseActivity<AccountWizardPresenter> implements AccountWizardView {
-    static final String TAG = AccountWizardActivity.class.getName();
-
-    private ProgressDialog mProgress = null;
-    private String mAccountType;
-    private AlertDialog mAlertDialog;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        // dependency injection
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        super.onCreate(savedInstanceState);
-
-        JamiApplication.getInstance().startDaemon();
-        setContentView(R.layout.activity_wizard);
-
-        String accountToMigrate = null;
-        Intent intent = getIntent();
-        if (intent != null) {
-            mAccountType = intent.getAction();
-            Uri path = intent.getData();
-            if (path != null) {
-                accountToMigrate = path.getLastPathSegment();
-            }
-        }
-        if (mAccountType == null) {
-            mAccountType = AccountConfig.ACCOUNT_TYPE_RING;
-        }
-
-        if (savedInstanceState == null) {
-            if (accountToMigrate != null) {
-                Bundle args = new Bundle();
-                args.putString(AccountMigrationFragment.ACCOUNT_ID, getIntent().getData().getLastPathSegment());
-                Fragment fragment = new AccountMigrationFragment();
-                fragment.setArguments(args);
-                FragmentManager fragmentManager = getSupportFragmentManager();
-                fragmentManager
-                        .beginTransaction()
-                        .replace(R.id.wizard_container, fragment)
-                        .commit();
-            } else {
-                presenter.init(getIntent().getAction() != null ? getIntent().getAction() : AccountConfig.ACCOUNT_TYPE_RING);
-            }
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mProgress != null) {
-            mProgress.dismiss();
-            mProgress = null;
-        }
-        if (mAlertDialog != null) {
-            mAlertDialog.setOnDismissListener(null);
-            mAlertDialog.dismiss();
-            mAlertDialog = null;
-        }
-        super.onDestroy();
-    }
-
-    @Override
-    public Single<VCard> saveProfile(final Account account, final AccountCreationModel accountCreationModel) {
-        File filedir = getFilesDir();
-        return accountCreationModel.toVCard()
-                .flatMap(vcard -> {
-                    account.resetProfile();
-                    return VCardUtils.saveLocalProfileToDisk(vcard, account.getAccountID(), filedir);
-                })
-                .subscribeOn(Schedulers.io());
-    }
-
-    public void createAccount(AccountCreationModel accountCreationModel) {
-        if (!TextUtils.isEmpty(accountCreationModel.getManagementServer())) {
-            presenter.initJamiAccountConnect(accountCreationModel,
-                    getText(R.string.ring_account_default_name).toString());
-        } else if (accountCreationModel.isLink()) {
-            presenter.initJamiAccountLink(accountCreationModel,
-                    getText(R.string.ring_account_default_name).toString());
-        } else {
-            presenter.initJamiAccountCreation(accountCreationModel,
-                    getText(R.string.ring_account_default_name).toString());
-        }
-    }
-
-    @Override
-    public void goToHomeCreation() {
-        Fragment fragment = new HomeAccountCreationFragment();
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        fragmentManager.beginTransaction()
-                .replace(R.id.wizard_container, fragment, HomeAccountCreationFragment.TAG)
-                .commit();
-    }
-
-    @Override
-    public void goToSipCreation() {
-        Fragment fragment = new SIPAccountCreationFragment();
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        fragmentManager.beginTransaction()
-                .replace(R.id.wizard_container, fragment, SIPAccountCreationFragment.TAG)
-                .commit();
-    }
-
-    @Override
-    public void goToProfileCreation(AccountCreationModel model) {
-        List<Fragment> fragments = getSupportFragmentManager().getFragments();
-        if (fragments.size() > 0) {
-            Fragment fragment =fragments.get(0);
-            if (fragment instanceof JamiLinkAccountFragment) {
-                ((JamiLinkAccountFragment) fragment).scrollPagerFragment(model);
-            } else if (fragment instanceof JamiAccountConnectFragment) {
-                profileCreated(model, false);
-            }
-        }
-    }
-
-    @Override
-    public void onBackPressed() {
-        Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.wizard_container);
-        if (fragment instanceof ProfileCreationFragment)
-            finish();
-        else
-            super.onBackPressed();
-    }
-
-    @Override
-    public void displayProgress(final boolean display) {
-        if (display) {
-            mProgress = new ProgressDialog(AccountWizardActivity.this);
-            mProgress.setTitle(R.string.dialog_wait_create);
-            mProgress.setMessage(getString(R.string.dialog_wait_create_details));
-            mProgress.setCancelable(false);
-            mProgress.setCanceledOnTouchOutside(false);
-            mProgress.show();
-        } else {
-            if (mProgress != null) {
-                if (mProgress.isShowing()) {
-                    mProgress.dismiss();
-                }
-                mProgress = null;
-            }
-        }
-    }
-
-    @Override
-    public void displayCreationError() {
-        Toast.makeText(AccountWizardActivity.this, "Error creating account", Toast.LENGTH_SHORT).show();
-    }
-
-    @Override
-    public void blockOrientation() {
-        //orientation is locked during the create of account to avoid the destruction of the thread
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-    }
-
-    @Override
-    public void finish(final boolean affinity) {
-        if (affinity) {
-            startActivity(new Intent(AccountWizardActivity.this, HomeActivity.class));
-            finish();
-        } else {
-            finishAffinity();
-        }
-    }
-
-    @Override
-    public void displayGenericError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new MaterialAlertDialogBuilder(AccountWizardActivity.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_cannot_be_found_title)
-                .setMessage(R.string.account_export_end_decryption_message)
-                .show();
-    }
-
-    @Override
-    public void displayNetworkError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new MaterialAlertDialogBuilder(AccountWizardActivity.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_no_network_title)
-                .setMessage(R.string.account_no_network_message)
-                .show();
-    }
-
-    @Override
-    public void displayCannotBeFoundError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new MaterialAlertDialogBuilder(AccountWizardActivity.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_cannot_be_found_title)
-                .setMessage(R.string.account_cannot_be_found_message)
-                .setOnDismissListener(dialogInterface -> getSupportFragmentManager().popBackStack())
-                .show();
-    }
-
-    @Override
-    public void displaySuccessDialog() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        setResult(Activity.RESULT_OK, new Intent());
-        //unlock the screen orientation
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
-        presenter.successDialogClosed();
-    }
-
-    public void profileCreated(AccountCreationModel accountCreationModel, boolean saveProfile) {
-        presenter.profileCreated(accountCreationModel, saveProfile);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt b/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt
new file mode 100644
index 000000000..058a4d0c6
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt
@@ -0,0 +1,241 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.account
+
+import android.app.ProgressDialog
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.text.TextUtils
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.Fragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.application.JamiApplication.Companion.instance
+import cx.ring.client.HomeActivity
+import cx.ring.fragments.AccountMigrationFragment
+import cx.ring.fragments.SIPAccountCreationFragment
+import cx.ring.mvp.BaseActivity
+import dagger.hilt.android.AndroidEntryPoint
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.account.AccountWizardPresenter
+import net.jami.account.AccountWizardView
+import net.jami.model.Account
+import net.jami.model.AccountConfig
+import net.jami.mvp.AccountCreationModel
+import net.jami.utils.VCardUtils
+
+@AndroidEntryPoint
+class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWizardView {
+    private var mProgress: ProgressDialog? = null
+    private var mAccountType: String? = null
+    private var mAlertDialog: AlertDialog? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        instance?.startDaemon()
+        setContentView(R.layout.activity_wizard)
+        var accountToMigrate: String? = null
+        val intent = intent
+        if (intent != null) {
+            mAccountType = intent.action
+            val path = intent.data
+            if (path != null) {
+                accountToMigrate = path.lastPathSegment
+            }
+        }
+        if (mAccountType == null) {
+            mAccountType = AccountConfig.ACCOUNT_TYPE_RING
+        }
+        if (savedInstanceState == null) {
+            if (accountToMigrate != null) {
+                val args = Bundle()
+                args.putString(AccountMigrationFragment.ACCOUNT_ID, getIntent().data!!.lastPathSegment)
+                val fragment: Fragment = AccountMigrationFragment()
+                fragment.arguments = args
+                val fragmentManager = supportFragmentManager
+                fragmentManager
+                    .beginTransaction()
+                    .replace(R.id.wizard_container, fragment)
+                    .commit()
+            } else {
+                presenter.init(if (getIntent().action != null) getIntent().action else AccountConfig.ACCOUNT_TYPE_RING)
+            }
+        }
+    }
+
+    override fun onDestroy() {
+        if (mProgress != null) {
+            mProgress!!.dismiss()
+            mProgress = null
+        }
+        if (mAlertDialog != null) {
+            mAlertDialog!!.setOnDismissListener(null)
+            mAlertDialog!!.dismiss()
+            mAlertDialog = null
+        }
+        super.onDestroy()
+    }
+
+    override fun saveProfile(account: Account, accountCreationModel: AccountCreationModel): Single<VCard> {
+        val filedir = filesDir
+        return accountCreationModel.toVCard()
+            .flatMap { vcard: VCard ->
+                account.resetProfile()
+                VCardUtils.saveLocalProfileToDisk(vcard, account.accountID, filedir)
+            }
+            .subscribeOn(Schedulers.io())
+    }
+
+    fun createAccount(accountCreationModel: AccountCreationModel) {
+        if (!TextUtils.isEmpty(accountCreationModel.managementServer)) {
+            presenter.initJamiAccountConnect(accountCreationModel, getText(R.string.ring_account_default_name).toString())
+        } else if (accountCreationModel.isLink) {
+            presenter.initJamiAccountLink(accountCreationModel, getText(R.string.ring_account_default_name).toString())
+        } else {
+            presenter.initJamiAccountCreation(accountCreationModel, getText(R.string.ring_account_default_name).toString())
+        }
+    }
+
+    override fun goToHomeCreation() {
+        val fragmentManager = supportFragmentManager
+        fragmentManager.beginTransaction()
+            .replace(R.id.wizard_container, HomeAccountCreationFragment(), HomeAccountCreationFragment.TAG)
+            .commit()
+    }
+
+    override fun goToSipCreation() {
+        val fragment: Fragment = SIPAccountCreationFragment()
+        val fragmentManager = supportFragmentManager
+        fragmentManager.beginTransaction()
+            .replace(R.id.wizard_container, fragment, SIPAccountCreationFragment.TAG)
+            .commit()
+    }
+
+    override fun goToProfileCreation(model: AccountCreationModel) {
+        val fragments = supportFragmentManager.fragments
+        if (fragments.size > 0) {
+            val fragment = fragments[0]
+            if (fragment is JamiLinkAccountFragment) {
+                fragment.scrollPagerFragment(model)
+            } else if (fragment is JamiAccountConnectFragment) {
+                profileCreated(model, false)
+            }
+        }
+    }
+
+    override fun onBackPressed() {
+        val fragment = supportFragmentManager.findFragmentById(R.id.wizard_container)
+        if (fragment is ProfileCreationFragment) finish() else super.onBackPressed()
+    }
+
+    override fun displayProgress(display: Boolean) {
+        if (display) {
+            mProgress = ProgressDialog(this@AccountWizardActivity).apply {
+                setTitle(R.string.dialog_wait_create)
+                setMessage(getString(R.string.dialog_wait_create_details))
+                setCancelable(false)
+                setCanceledOnTouchOutside(false)
+                show()
+            }
+        } else {
+            mProgress?.apply {
+                if (isShowing) dismiss()
+                mProgress = null
+            }
+        }
+    }
+
+    override fun displayCreationError() {
+        Toast.makeText(this@AccountWizardActivity, "Error creating account", Toast.LENGTH_SHORT)
+            .show()
+    }
+
+    override fun blockOrientation() {
+        //orientation is locked during the create of account to avoid the destruction of the thread
+        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
+    }
+
+    override fun finish(affinity: Boolean) {
+        if (affinity) {
+            startActivity(Intent(this@AccountWizardActivity, HomeActivity::class.java))
+            finish()
+        } else {
+            finishAffinity()
+        }
+    }
+
+    override fun displayGenericError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = MaterialAlertDialogBuilder(this@AccountWizardActivity)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_cannot_be_found_title)
+            .setMessage(R.string.account_export_end_decryption_message)
+            .show()
+    }
+
+    override fun displayNetworkError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = MaterialAlertDialogBuilder(this@AccountWizardActivity)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_no_network_title)
+            .setMessage(R.string.account_no_network_message)
+            .show()
+    }
+
+    override fun displayCannotBeFoundError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = MaterialAlertDialogBuilder(this@AccountWizardActivity)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_cannot_be_found_title)
+            .setMessage(R.string.account_cannot_be_found_message)
+            .setOnDismissListener { dialogInterface: DialogInterface? -> supportFragmentManager.popBackStack() }
+            .show()
+    }
+
+    override fun displaySuccessDialog() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        setResult(RESULT_OK, Intent())
+        //unlock the screen orientation
+        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+        presenter.successDialogClosed()
+    }
+
+    fun profileCreated(accountCreationModel: AccountCreationModel?, saveProfile: Boolean) {
+        presenter.profileCreated(accountCreationModel, saveProfile)
+    }
+
+    companion object {
+        val TAG = AccountWizardActivity::class.java.name
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.java b/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.java
deleted file mode 100644
index 7520e6121..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.account;
-
-import android.content.Context;
-import android.graphics.Typeface;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.StyleSpan;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import cx.ring.R;
-import cx.ring.views.TwoButtonEditText;
-
-public class DeviceAdapter extends BaseAdapter {
-    private final Context mContext;
-    private final ArrayList<Map.Entry<String, String>> mDevices = new ArrayList<>();
-
-    private String mCurrentDeviceId;
-    private DeviceRevocationListener mListener;
-
-    public DeviceAdapter(Context c, Map<String, String> devices, String currentDeviceId,
-                         DeviceRevocationListener listener) {
-        mContext = c;
-        setData(devices, currentDeviceId);
-        mListener = listener;
-    }
-
-    public void setData(Map<String, String> devices, String currentDeviceId) {
-        mDevices.clear();
-        mCurrentDeviceId = currentDeviceId;
-        if (devices != null && !devices.isEmpty()) {
-            mDevices.ensureCapacity(devices.size());
-            mDevices.addAll(devices.entrySet());
-        }
-        for (int i = 0; i < mDevices.size(); i++) {
-            if(mDevices.get(i).getKey().contentEquals(mCurrentDeviceId)) {
-                mDevices.remove(mDevices.get(i));
-            }
-        }
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public int getCount() {
-        return mDevices.size();
-    }
-
-    @Override
-    public Object getItem(int i) {
-        return mDevices.get(i);
-    }
-
-    @Override
-    public long getItemId(int i) {
-        return 0;
-    }
-
-    @Override
-    public View getView(final int i, View view, ViewGroup parent) {
-        if (view == null) {
-            view = LayoutInflater.from(mContext).inflate(R.layout.item_device, parent, false);
-        }
-        boolean isCurrentDevice = mDevices.get(i).getKey().contentEquals(mCurrentDeviceId);
-
-        TwoButtonEditText devId = view.findViewById(R.id.txt_device_id);
-        TextView thisDevice = view.findViewById(R.id.txt_device_thisflag);
-        devId.setText(mDevices.get(i).getValue());
-        String hint = mDevices.get(i).getKey();
-        hint = hint.substring(0, (int) (hint.length() * 0.66));
-        devId.setHint(hint);
-
-        if (isCurrentDevice) {
-            thisDevice.setVisibility(View.VISIBLE);
-            devId.setLeftDrawable(R.drawable.baseline_edit_twoton_24dp);
-            devId.setLeftDrawableOnClickListener(view1 -> {
-                if (mListener != null) {
-                    mListener.onDeviceRename();
-                }
-            });
-        } else {
-            thisDevice.setVisibility(View.GONE);
-            devId.setLeftDrawable(R.drawable.baseline_cancel_24);
-            devId.setLeftDrawableOnClickListener(view12 -> {
-                if (mListener != null) {
-                    mListener.onDeviceRevocationAsked(mDevices.get(i).getKey());
-                }
-            });
-        }
-
-        return view;
-    }
-
-    public interface DeviceRevocationListener {
-        void onDeviceRevocationAsked(String deviceId);
-
-        void onDeviceRename();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.kt b/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.kt
new file mode 100644
index 000000000..f7807595f
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/DeviceAdapter.kt
@@ -0,0 +1,91 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.account
+
+import android.content.Context
+import android.widget.BaseAdapter
+import android.view.ViewGroup
+import android.view.LayoutInflater
+import android.view.View
+import cx.ring.R
+import cx.ring.views.TwoButtonEditText
+import android.widget.TextView
+import java.util.ArrayList
+
+class DeviceAdapter(
+    private val mContext: Context, devices: Map<String, String>?,
+    currentDeviceId: String?,
+    private val mListener: DeviceRevocationListener
+) : BaseAdapter() {
+    private val mDevices = ArrayList<Map.Entry<String, String>>()
+    private var mCurrentDeviceId: String? = null
+
+    fun setData(devices: Map<String, String>?, currentDeviceId: String?) {
+        mDevices.clear()
+        mCurrentDeviceId = currentDeviceId
+        if (devices != null && devices.isNotEmpty()) {
+            mDevices.ensureCapacity(devices.size)
+            mDevices.addAll(devices.entries)
+        }
+        mDevices.removeAll { d ->d.key == mCurrentDeviceId }
+        notifyDataSetChanged()
+    }
+
+    override fun getCount(): Int {
+        return mDevices.size
+    }
+
+    override fun getItem(i: Int): Any {
+        return mDevices[i]
+    }
+
+    override fun getItemId(i: Int): Long {
+        return 0
+    }
+
+    override fun getView(i: Int, convertView: View?, parent: ViewGroup): View {
+        val view = convertView ?: LayoutInflater.from(mContext).inflate(R.layout.item_device, parent, false)
+        val isCurrentDevice = mDevices[i].key.contentEquals(mCurrentDeviceId)
+        val devId: TwoButtonEditText = view.findViewById(R.id.txt_device_id)
+        val thisDevice = view.findViewById<TextView>(R.id.txt_device_thisflag)
+        devId.setText(mDevices[i].value)
+        var hint = mDevices[i].key
+        hint = hint.substring(0, (hint.length * 0.66).toInt())
+        devId.setHint(hint)
+        if (isCurrentDevice) {
+            thisDevice.visibility = View.VISIBLE
+            devId.setLeftDrawable(R.drawable.baseline_edit_twoton_24dp)
+            devId.setLeftDrawableOnClickListener { mListener.onDeviceRename() }
+        } else {
+            thisDevice.visibility = View.GONE
+            devId.setLeftDrawable(R.drawable.baseline_cancel_24)
+            devId.setLeftDrawableOnClickListener { mListener.onDeviceRevocationAsked(mDevices[i].key) }
+        }
+        return view
+    }
+
+    interface DeviceRevocationListener {
+        fun onDeviceRevocationAsked(deviceId: String?)
+        fun onDeviceRename()
+    }
+
+    init {
+        setData(devices, currentDeviceId)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.java
deleted file mode 100644
index a5352ba5a..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.account;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.account.HomeAccountCreationPresenter;
-import net.jami.account.HomeAccountCreationView;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.FragAccHomeCreateBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.utils.AndroidFileUtils;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-
-public class HomeAccountCreationFragment extends BaseSupportFragment<HomeAccountCreationPresenter> implements HomeAccountCreationView {
-    private static final int ARCHIVE_REQUEST_CODE = 42;
-
-    public static final String TAG = HomeAccountCreationFragment.class.getSimpleName();
-
-    private FragAccHomeCreateBinding binding;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragAccHomeCreateBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        setRetainInstance(true);
-        binding.ringAddAccount.setOnClickListener(v -> presenter.clickOnLinkAccount());
-        binding.ringCreateBtn.setOnClickListener(v -> presenter.clickOnCreateAccount());
-        binding.accountConnectServer.setOnClickListener(v -> presenter.clickOnConnectAccount());
-        binding.ringImportAccount.setOnClickListener(v -> performFileSearch());
-    }
-
-    @Override
-    public void goToAccountCreation() {
-        Fragment fragment = new JamiAccountCreationFragment();
-        replaceFragmentWithSlide(fragment, R.id.wizard_container);
-    }
-
-    @Override
-    public void goToAccountLink() {
-        AccountCreationModelImpl ringAccountViewModel = new AccountCreationModelImpl();
-        ringAccountViewModel.setLink(true);
-        Fragment fragment = JamiLinkAccountFragment.newInstance(ringAccountViewModel);
-        replaceFragmentWithSlide(fragment, R.id.wizard_container);
-    }
-
-    @Override
-    public void goToAccountConnect() {
-        AccountCreationModelImpl ringAccountViewModel = new AccountCreationModelImpl();
-        ringAccountViewModel.setLink(true);
-        Fragment fragment = JamiAccountConnectFragment.newInstance(ringAccountViewModel);
-        replaceFragmentWithSlide(fragment, R.id.wizard_container);
-    }
-
-    private void performFileSearch() {
-        try {
-            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT)
-                    .addCategory(Intent.CATEGORY_OPENABLE)
-                    .setType("*/*");
-            startActivityForResult(intent, ARCHIVE_REQUEST_CODE);
-        } catch (Exception e) {
-            View v = getView();
-            if (v != null)
-                Snackbar.make(v, "No file browser available on this device", Snackbar.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
-        if (requestCode == ARCHIVE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
-            if (resultData != null) {
-                Uri uri = resultData.getData();
-                if (uri != null) {
-                    AndroidFileUtils.getCacheFile(requireContext(), uri)
-                            .observeOn(AndroidSchedulers.mainThread())
-                            .subscribe(file -> {
-                                AccountCreationModelImpl ringAccountViewModel = new AccountCreationModelImpl();
-                                ringAccountViewModel.setLink(true);
-                                ringAccountViewModel.setArchive(file);
-                                Fragment fragment = JamiLinkAccountFragment.newInstance(ringAccountViewModel);
-                                replaceFragmentWithSlide(fragment, R.id.wizard_container);
-                            }, e-> {
-                                View v = getView();
-                                if (v != null)
-                                    Snackbar.make(v, "Can't import archive: " + e.getMessage(), Snackbar.LENGTH_LONG).show();
-                            });
-                }
-            }
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt
new file mode 100644
index 000000000..de60c5126
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt
@@ -0,0 +1,131 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.account
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.account.JamiLinkAccountFragment.Companion.newInstance
+import cx.ring.databinding.FragAccHomeCreateBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.utils.AndroidFileUtils.getCacheFile
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import net.jami.account.HomeAccountCreationPresenter
+import net.jami.account.HomeAccountCreationView
+import java.io.File
+
+@AndroidEntryPoint
+class HomeAccountCreationFragment :
+    BaseSupportFragment<HomeAccountCreationPresenter, HomeAccountCreationView>(),
+    HomeAccountCreationView {
+    private var binding: FragAccHomeCreateBinding? = null
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        binding = FragAccHomeCreateBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        retainInstance = true
+        binding!!.ringAddAccount.setOnClickListener { v: View? -> presenter.clickOnLinkAccount() }
+        binding!!.ringCreateBtn.setOnClickListener { v: View? -> presenter.clickOnCreateAccount() }
+        binding!!.accountConnectServer.setOnClickListener { v: View? -> presenter.clickOnConnectAccount() }
+        binding!!.ringImportAccount.setOnClickListener { v: View? -> performFileSearch() }
+    }
+
+    override fun goToAccountCreation() {
+        val fragment: Fragment = JamiAccountCreationFragment()
+        replaceFragmentWithSlide(fragment, R.id.wizard_container)
+    }
+
+    override fun goToAccountLink() {
+        val ringAccountViewModel = AccountCreationModelImpl()
+        ringAccountViewModel.isLink = true
+        val fragment: Fragment = newInstance(ringAccountViewModel)
+        replaceFragmentWithSlide(fragment, R.id.wizard_container)
+    }
+
+    override fun goToAccountConnect() {
+        val ringAccountViewModel = AccountCreationModelImpl()
+        ringAccountViewModel.isLink = true
+        val fragment: Fragment = JamiAccountConnectFragment.newInstance(ringAccountViewModel)
+        replaceFragmentWithSlide(fragment, R.id.wizard_container)
+    }
+
+    private fun performFileSearch() {
+        try {
+            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+                .addCategory(Intent.CATEGORY_OPENABLE)
+                .setType("*/*")
+            startActivityForResult(intent, ARCHIVE_REQUEST_CODE)
+        } catch (e: Exception) {
+            val v = view
+            if (v != null) Snackbar.make(
+                v,
+                "No file browser available on this device",
+                Snackbar.LENGTH_SHORT
+            ).show()
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+        if (requestCode == ARCHIVE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+            if (resultData != null) {
+                val uri = resultData.data
+                if (uri != null) {
+                    getCacheFile(requireContext(), uri)
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .subscribe({ file: File? ->
+                            val ringAccountViewModel = AccountCreationModelImpl()
+                            ringAccountViewModel.isLink = true
+                            ringAccountViewModel.archive = file
+                            val fragment: Fragment = newInstance(ringAccountViewModel)
+                            replaceFragmentWithSlide(fragment, R.id.wizard_container)
+                        }) { e: Throwable ->
+                            val v = view
+                            if (v != null)
+                                Snackbar.make(v, "Can't import archive: " + e.message, Snackbar.LENGTH_LONG).show()
+                        }
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val ARCHIVE_REQUEST_CODE = 42
+        val TAG = HomeAccountCreationFragment::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java
index 88ce30f24..ded0fab55 100644
--- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java
@@ -38,8 +38,10 @@ import net.jami.account.JamiAccountConnectPresenter;
 import net.jami.account.JamiConnectAccountView;
 import net.jami.mvp.AccountCreationModel;
 import cx.ring.mvp.BaseSupportFragment;
+import dagger.hilt.android.AndroidEntryPoint;
 
-public class JamiAccountConnectFragment extends BaseSupportFragment<JamiAccountConnectPresenter> implements JamiConnectAccountView {
+@AndroidEntryPoint
+public class JamiAccountConnectFragment extends BaseSupportFragment<JamiAccountConnectPresenter, JamiConnectAccountView> implements JamiConnectAccountView {
     public static final String TAG = JamiAccountConnectFragment.class.getSimpleName();
 
     private AccountCreationModel model;
@@ -55,7 +57,6 @@ public class JamiAccountConnectFragment extends BaseSupportFragment<JamiAccountC
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         mBinding = FragAccJamiConnectBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return mBinding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.java
deleted file mode 100644
index 9d6e1f8d2..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.account;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-
-import cx.ring.databinding.FragAccJamiCreateBinding;
-import net.jami.mvp.AccountCreationModel;
-
-import cx.ring.views.WizardViewPager;
-
-public class JamiAccountCreationFragment extends Fragment {
-
-    private static final int NUM_PAGES = 3;
-
-    private FragAccJamiCreateBinding mBinding;
-    private Fragment mCurrentFragment;
-
-    private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
-        @Override
-        public void handleOnBackPressed() {
-            if (mCurrentFragment instanceof ProfileCreationFragment) {
-                ProfileCreationFragment fragment = (ProfileCreationFragment) mCurrentFragment;
-                ((AccountWizardActivity) getActivity()).profileCreated(fragment.getModel(), false);
-                return;
-            }
-            mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() - 1);
-        }
-    };
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        mBinding = FragAccJamiCreateBinding.inflate(inflater, container, false);
-        return mBinding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mBinding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        setRetainInstance(true);
-
-        ScreenSlidePagerAdapter pagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
-        mBinding.pager.setAdapter(pagerAdapter);
-        mBinding.pager.disableScroll(true);
-        mBinding.pager.setOffscreenPageLimit(1);
-        mBinding.indicator.setupWithViewPager(mBinding.pager, true);
-
-        mBinding.pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
-            @Override
-            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-            }
-
-            @Override
-            public void onPageSelected(int position) {
-                mCurrentFragment = pagerAdapter.getRegisteredFragment(position);
-                boolean enable = mCurrentFragment instanceof JamiAccountPasswordFragment || mCurrentFragment instanceof ProfileCreationFragment;
-                onBackPressedCallback.setEnabled(enable);
-            }
-
-            @Override
-            public void onPageScrollStateChanged(int state) {
-
-            }
-        });
-
-        LinearLayout tabStrip = ((LinearLayout) mBinding.indicator.getChildAt(0));
-        for(int i = 0; i < tabStrip.getChildCount(); i++) {
-            tabStrip.getChildAt(i).setOnTouchListener((v, event) -> true);
-        }
-    }
-
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
-    }
-
-    public void scrollPagerFragment(AccountCreationModel accountCreationModel) {
-        if (accountCreationModel == null) {
-            mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() - 1);
-            return;
-        }
-        mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() + 1);
-        for (Fragment fragment : getChildFragmentManager().getFragments()) {
-            if (fragment instanceof JamiAccountPasswordFragment) {
-                ((JamiAccountPasswordFragment) fragment).setUsername(accountCreationModel.getUsername());
-            }
-        }
-    }
-
-    private static class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
-
-        SparseArray<Fragment> mRegisteredFragments = new SparseArray<>();
-        private int mCurrentPosition = -1;
-
-        public ScreenSlidePagerAdapter(FragmentManager fm) {
-            super(fm);
-        }
-
-        @NonNull
-        @Override
-        public Fragment getItem(int position) {
-            Fragment fragment = null;
-            AccountCreationModelImpl ringAccountViewModel = new AccountCreationModelImpl();
-            switch (position) {
-                case 0:
-                    fragment = JamiAccountUsernameFragment.newInstance(ringAccountViewModel);
-                    break;
-                case 1:
-                    fragment = JamiAccountPasswordFragment.newInstance(ringAccountViewModel);
-                    break;
-                case 2:
-                    fragment = ProfileCreationFragment.newInstance(ringAccountViewModel);
-                    break;
-            }
-            return fragment;
-        }
-
-        @Override
-        public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
-            super.setPrimaryItem(container, position, object);
-
-            if (position != mCurrentPosition && container instanceof WizardViewPager) {
-                Fragment fragment = (Fragment) object;
-                WizardViewPager pager = (WizardViewPager) container;
-
-                if (fragment.getView() != null) {
-                    mCurrentPosition = position;
-                    pager.measureCurrentView(fragment.getView());
-                }
-            }
-        }
-
-        @NonNull
-        @Override
-        public Object instantiateItem(@NonNull ViewGroup container, int position) {
-            Fragment fragment = (Fragment) super.instantiateItem(container, position);
-            mRegisteredFragments.put(position, fragment);
-            return super.instantiateItem(container, position);
-        }
-
-        @Override
-        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
-            mRegisteredFragments.remove(position);
-            super.destroyItem(container, position, object);
-        }
-
-        @Override
-        public int getCount() {
-            return NUM_PAGES;
-        }
-
-        public Fragment getRegisteredFragment(int position) {
-            return mRegisteredFragments.get(position);
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt
new file mode 100644
index 000000000..224be8c8c
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt
@@ -0,0 +1,152 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.account
+
+import android.content.Context
+import android.os.Bundle
+import android.util.SparseArray
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import androidx.viewpager.widget.ViewPager.OnPageChangeListener
+import cx.ring.databinding.FragAccJamiCreateBinding
+import cx.ring.views.WizardViewPager
+import net.jami.mvp.AccountCreationModel
+
+class JamiAccountCreationFragment : Fragment() {
+    private var mBinding: FragAccJamiCreateBinding? = null
+    private var mCurrentFragment: Fragment? = null
+    private val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(false) {
+            override fun handleOnBackPressed() {
+                if (mCurrentFragment is ProfileCreationFragment) {
+                    val fragment = mCurrentFragment as ProfileCreationFragment
+                    (activity as AccountWizardActivity?)?.profileCreated(fragment.model, false)
+                    return
+                }
+                mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1
+            }
+        }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View {
+        return FragAccJamiCreateBinding.inflate(inflater, container, false).apply { mBinding = this }.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mBinding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        retainInstance = true
+        val pagerAdapter = ScreenSlidePagerAdapter(childFragmentManager)
+        mBinding!!.pager.adapter = pagerAdapter
+        mBinding!!.pager.disableScroll(true)
+        mBinding!!.pager.offscreenPageLimit = 1
+        mBinding!!.indicator.setupWithViewPager(mBinding!!.pager, true)
+        mBinding!!.pager.addOnPageChangeListener(object : OnPageChangeListener {
+            override fun onPageScrolled(position: Int,positionOffset: Float, positionOffsetPixels: Int) {}
+
+            override fun onPageSelected(position: Int) {
+                mCurrentFragment = pagerAdapter.getRegisteredFragment(position)
+                val enable = mCurrentFragment is JamiAccountPasswordFragment || mCurrentFragment is ProfileCreationFragment
+                onBackPressedCallback.isEnabled = enable
+            }
+
+            override fun onPageScrollStateChanged(state: Int) {}
+        })
+        val tabStrip = mBinding!!.indicator.getChildAt(0) as LinearLayout
+        for (i in 0 until tabStrip.childCount) {
+            tabStrip.getChildAt(i).setOnTouchListener { v, event -> true }
+        }
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+    }
+
+    fun scrollPagerFragment(accountCreationModel: AccountCreationModel?) {
+        if (accountCreationModel == null) {
+            mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1
+            return
+        }
+        mBinding!!.pager.currentItem = mBinding!!.pager.currentItem + 1
+        for (fragment in childFragmentManager.fragments) {
+            if (fragment is JamiAccountPasswordFragment) {
+                fragment.setUsername(accountCreationModel.username)
+            }
+        }
+    }
+
+    private class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
+        var mRegisteredFragments = SparseArray<Fragment>()
+        private var mCurrentPosition = -1
+        override fun getItem(position: Int): Fragment {
+            var fragment: Fragment? = null
+            val ringAccountViewModel = AccountCreationModelImpl()
+            when (position) {
+                0 -> fragment = JamiAccountUsernameFragment.newInstance(ringAccountViewModel)
+                1 -> fragment = JamiAccountPasswordFragment.newInstance(ringAccountViewModel)
+                2 -> fragment = ProfileCreationFragment.newInstance(ringAccountViewModel)
+            }
+            return fragment!!
+        }
+
+        override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
+            super.setPrimaryItem(container, position, `object`)
+            if (position != mCurrentPosition && container is WizardViewPager) {
+                val fragment = `object` as Fragment
+                if (fragment.view != null) {
+                    mCurrentPosition = position
+                    container.measureCurrentView(fragment.view)
+                }
+            }
+        }
+
+        override fun instantiateItem(container: ViewGroup, position: Int): Any {
+            val fragment = super.instantiateItem(container, position) as Fragment
+            mRegisteredFragments.put(position, fragment)
+            return super.instantiateItem(container, position)
+        }
+
+        override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
+            mRegisteredFragments.remove(position)
+            super.destroyItem(container, position, `object`)
+        }
+
+        override fun getCount(): Int {
+            return NUM_PAGES
+        }
+
+        fun getRegisteredFragment(position: Int): Fragment? {
+            return mRegisteredFragments[position]
+        }
+    }
+
+    companion object {
+        private const val NUM_PAGES = 3
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java
index 6aef62e1b..d818e7d23 100644
--- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java
@@ -34,15 +34,16 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import cx.ring.R;
-import cx.ring.application.JamiApplication;
 import cx.ring.databinding.FragAccJamiPasswordBinding;
 
 import net.jami.account.JamiAccountCreationPresenter;
 import net.jami.account.JamiAccountCreationView;
 import net.jami.mvp.AccountCreationModel;
 import cx.ring.mvp.BaseSupportFragment;
+import dagger.hilt.android.AndroidEntryPoint;
 
-public class JamiAccountPasswordFragment extends BaseSupportFragment<JamiAccountCreationPresenter>
+@AndroidEntryPoint
+public class JamiAccountPasswordFragment extends BaseSupportFragment<JamiAccountCreationPresenter, JamiAccountCreationView>
         implements JamiAccountCreationView {
 
     private static final String KEY_MODEL = "model";
@@ -71,7 +72,6 @@ public class JamiAccountPasswordFragment extends BaseSupportFragment<JamiAccount
             model = (AccountCreationModelImpl) savedInstanceState.getSerializable(KEY_MODEL);
         }
         binding = FragAccJamiPasswordBinding.inflate(inflater, container, false);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.java
deleted file mode 100644
index 7e9043ee0..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.java
+++ /dev/null
@@ -1,832 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *          Loïc Siret <loic.siret@savoirfairelinux.com>
- *          Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.account;
-
-import android.Manifest;
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.FileProvider;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.account.JamiAccountSummaryPresenter;
-import net.jami.account.JamiAccountSummaryView;
-import net.jami.model.Account;
-import net.jami.utils.StringUtils;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.HomeActivity;
-import cx.ring.contactrequests.BlockListFragment;
-import cx.ring.databinding.FragAccSummaryBinding;
-import cx.ring.fragments.AdvancedAccountFragment;
-import cx.ring.fragments.GeneralAccountFragment;
-import cx.ring.fragments.LinkDeviceFragment;
-import cx.ring.fragments.MediaPreferenceFragment;
-import cx.ring.fragments.QRCodeFragment;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.settings.AccountFragment;
-import cx.ring.settings.SettingsFragment;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.views.AvatarDrawable;
-import cx.ring.views.SwitchButton;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class JamiAccountSummaryFragment extends BaseSupportFragment<JamiAccountSummaryPresenter> implements
-        RegisterNameDialog.RegisterNameDialogListener,
-        JamiAccountSummaryView, ChangePasswordDialog.PasswordChangedListener,
-        BackupAccountDialog.UnlockAccountListener,
-        ViewTreeObserver.OnScrollChangedListener,
-        RenameDeviceDialog.RenameDeviceListener,
-        DeviceAdapter.DeviceRevocationListener,
-        ConfirmRevocationDialog.ConfirmRevocationListener {
-
-    public static final String TAG = JamiAccountSummaryFragment.class.getSimpleName();
-    private static final String FRAGMENT_DIALOG_REVOCATION = TAG + ".dialog.deviceRevocation";
-    private static final String FRAGMENT_DIALOG_RENAME = TAG + ".dialog.deviceRename";
-
-    private static final String FRAGMENT_DIALOG_PASSWORD = TAG + ".dialog.changePassword";
-    private static final String FRAGMENT_DIALOG_BACKUP = TAG + ".dialog.backup";
-    private static final int WRITE_REQUEST_CODE = 43;
-    private static final int SCROLL_DIRECTION_UP = -1;
-
-    private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) {
-        @Override
-        public void handleOnBackPressed() {
-            if (mBinding.fragment.getVisibility() == View.VISIBLE) {
-                mBinding.fragment.setVisibility(View.GONE);
-                mOnBackPressedCallback.setEnabled(false);
-                getChildFragmentManager().popBackStack();
-            }
-        }
-    };
-
-    private ProgressDialog mWaitDialog;
-    private boolean mAccountHasPassword = true;
-    private String mBestName = "";
-    private String mAccountId = "";
-    private File mCacheArchive = null;
-    private ImageView mProfilePhoto;
-    private Bitmap mSourcePhoto;
-    private Uri tmpProfilePhotoUri;
-    private DeviceAdapter mDeviceAdapter;
-
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-    private final CompositeDisposable mProfileDisposable = new CompositeDisposable();
-    private FragAccSummaryBinding mBinding;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        mBinding = FragAccSummaryBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        mDisposableBag.add(mProfileDisposable);
-        return mBinding.getRoot();
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        requireActivity().getOnBackPressedDispatcher().addCallback(this, mOnBackPressedCallback);
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mDisposableBag.clear();
-        mBinding = null;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mDisposableBag.dispose();
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        if (getArguments() != null) {
-            mAccountId = getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY);
-            if (mAccountId != null) {
-                presenter.setAccountId(mAccountId);
-            }
-        }
-
-        mBinding.scrollview.getViewTreeObserver().addOnScrollChangedListener(this);
-        mBinding.linkNewDevice.setOnClickListener(v -> showWizard(mAccountId));
-        mBinding.linkedDevices.setRightDrawableOnClickListener(v -> onDeviceRename());
-        mBinding.registerName.setOnClickListener(v -> showUsernameRegistrationPopup());
-
-        List<SettingItem> items = new ArrayList<>(4);
-        items.add(new SettingItem(R.string.account, R.drawable.baseline_account_card_details, () -> presenter.goToAccount()));
-        items.add(new SettingItem(R.string.account_preferences_media_tab, R.drawable.outline_file_copy_24, () -> presenter.goToMedia()));
-        items.add(new SettingItem(R.string.notif_channel_messages, R.drawable.baseline_chat_24, () -> presenter.goToSystem()));
-        items.add(new SettingItem(R.string.account_preferences_advanced_tab, R.drawable.round_check_circle_24, () -> presenter.goToAdvanced()));
-
-        SettingsAdapter adapter = new SettingsAdapter(view.getContext(), R.layout.item_setting, items);
-        mBinding.settingsList.setOnItemClickListener((adapterView, v, i, l) -> adapter.getItem(i).onClick());
-        mBinding.settingsList.setAdapter(adapter);
-
-        int totalHeight = 0;
-        for (int i = 0; i < adapter.getCount(); i++) {
-            View listItem = adapter.getView(i, null, mBinding.settingsList);
-            listItem.measure(0, 0);
-            totalHeight += listItem.getMeasuredHeight();
-        }
-
-        ViewGroup.LayoutParams par = mBinding.settingsList.getLayoutParams();
-        par.height = totalHeight + (mBinding.settingsList.getDividerHeight() * (adapter.getCount() - 1));
-        mBinding.settingsList.setLayoutParams(par);
-        mBinding.settingsList.requestLayout();
-
-        mBinding.chipMore.setOnClickListener(v -> {
-            if (mBinding.devicesList.getVisibility() == View.GONE) {
-                expand(mBinding.devicesList);
-            } else {
-                collapse(mBinding.devicesList);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        ((HomeActivity) requireActivity()).showAccountStatus(true);
-        ((HomeActivity) requireActivity()).getSwitchButton().setOnCheckedChangeListener((buttonView, isChecked) -> presenter.enableAccount(isChecked));
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        ((HomeActivity) requireActivity()).showAccountStatus(false);
-        ((HomeActivity) requireActivity()).getSwitchButton().setOnCheckedChangeListener(null);
-    }
-
-    public void setAccount(String accountId) {
-        if (presenter != null)
-            presenter.setAccountId(accountId);
-    }
-
-    @Override
-    public void updateUserView(Account account) {
-        Context context = getContext();
-        if (context == null || account == null)
-            return;
-
-        mProfileDisposable.clear();
-        mProfileDisposable.add(AvatarDrawable.load(context, account)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(avatar -> {
-                    if (mBinding != null) {
-                        mBinding.userPhoto.setImageDrawable(avatar);
-                        mBinding.username.setText(account.getLoadedProfile().blockingGet().first);
-                    }
-                }, e -> Log.e(TAG, "Error loading avatar", e)));
-    }
-
-    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
-        switch (requestCode) {
-            case WRITE_REQUEST_CODE:
-                if (resultCode == Activity.RESULT_OK) {
-                    if (resultData != null) {
-                        Uri uri = resultData.getData();
-                        if (uri != null) {
-                            if (mCacheArchive != null) {
-                                AndroidFileUtils.moveToUri(requireContext().getContentResolver(), mCacheArchive, uri)
-                                        .observeOn(AndroidSchedulers.mainThread())
-                                        .subscribe(() -> {}, e -> {
-                                            View v = getView();
-                                            if (v != null)
-                                                Snackbar.make(v, "Can't export archive: " + e.getMessage(), Snackbar.LENGTH_LONG).show();
-                                        });
-                            }
-                        }
-                    }
-                }
-            break;
-            case HomeActivity.REQUEST_CODE_PHOTO:
-                if (resultCode == Activity.RESULT_OK) {
-                    if (tmpProfilePhotoUri == null) {
-                        if (resultData != null)
-                            updatePhoto(Single.just((Bitmap) resultData.getExtras().get("data")));
-                    } else {
-                        updatePhoto(tmpProfilePhotoUri);
-                    }
-                }
-                tmpProfilePhotoUri = null;
-                break;
-            case HomeActivity.REQUEST_CODE_GALLERY:
-                if (resultCode == Activity.RESULT_OK && resultData != null) {
-                    updatePhoto(resultData.getData());
-                }
-                break;
-        }
-    }
-
-    @Override
-    public void accountChanged(@NonNull final Account account) {
-        updateUserView(account);
-        mBinding.userPhoto.setOnClickListener(v -> profileContainerClicked(account));
-        mBinding.linkedDevices.setText(account.getDeviceName());
-        setLinkedDevicesAdapter(account);
-        mAccountHasPassword = account.hasPassword();
-
-        ((HomeActivity) requireActivity()).getSwitchButton().setCheckedSilent(account.isEnabled());
-        mBinding.accountAliasTxt.setText(getString(R.string.profile));
-        mBinding.identity.setText(account.getUsername());
-        mAccountId = account.getAccountID();
-        mBestName = account.getRegisteredName();
-        if (mBestName.isEmpty()) {
-            mBestName = account.getDisplayUsername();
-            if (mBestName.isEmpty()) {
-                mBestName = account.getUsername();
-            }
-        }
-        mBestName = mBestName + ".gz";
-        String username = account.getRegisteredName();
-        boolean currentRegisteredName = account.registeringUsername;
-        boolean hasRegisteredName = !currentRegisteredName && username != null && !username.isEmpty();
-        mBinding.groupRegisteringName.setVisibility(currentRegisteredName ? View.VISIBLE : View.GONE);
-        mBinding.btnShare.setOnClickListener(v -> shareAccount(hasRegisteredName? username : account.getUsername()));
-        mBinding.registerName.setVisibility(hasRegisteredName ? View.GONE : View.VISIBLE);
-        mBinding.registeredName.setText(hasRegisteredName ? username : getResources().getString(R.string.no_registered_name_for_account));
-        mBinding.btnQr.setOnClickListener(v -> QRCodeFragment.newInstance(QRCodeFragment.INDEX_CODE).show(getParentFragmentManager(), QRCodeFragment.TAG));
-        mBinding.username.setOnFocusChangeListener((v, hasFocus) -> {
-            Editable name = mBinding.username.getText();
-            if (!hasFocus && !TextUtils.isEmpty(name)) {
-                presenter.saveVCardFormattedName(name.toString());
-            }
-        });
-
-        setSwitchStatus(account);
-    }
-
-    public boolean onBackPressed() {
-        return false;
-    }
-
-    private void showWizard(String accountId) {
-        LinkDeviceFragment.newInstance(accountId).show(getParentFragmentManager(), LinkDeviceFragment.TAG);
-    }
-
-    @Override
-    public void showNetworkError() {
-        dismissWaitDialog();
-        new MaterialAlertDialogBuilder(requireContext())
-                .setTitle(R.string.account_export_end_network_title)
-                .setMessage(R.string.account_export_end_network_message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    @Override
-    public void showPasswordError() {
-        dismissWaitDialog();
-    }
-
-    @Override
-    public void showGenericError() {
-        dismissWaitDialog();
-        new MaterialAlertDialogBuilder(requireContext())
-                .setTitle(R.string.account_export_end_error_title)
-                .setMessage(R.string.account_export_end_error_message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    @Override
-    public void showPIN(String pin) {
-
-    }
-
-    private void profileContainerClicked(Account account) {
-        LayoutInflater inflater = LayoutInflater.from(getActivity());
-
-        ViewGroup view = (ViewGroup) inflater.inflate(R.layout.dialog_profile, null);
-        mProfilePhoto = view.findViewById(R.id.profile_photo);
-        mDisposableBag.add(AvatarDrawable.load(inflater.getContext(), account)
-                .subscribe(a -> mProfilePhoto.setImageDrawable(a)));
-
-        ImageButton cameraView = view.findViewById(R.id.camera);
-        cameraView.setOnClickListener(v -> presenter.cameraClicked());
-
-        ImageButton gallery = view.findViewById(R.id.gallery);
-        gallery.setOnClickListener(v -> presenter.galleryClicked());
-
-        new MaterialAlertDialogBuilder(requireContext())
-                .setTitle(R.string.profile)
-                .setView(view)
-                .setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel())
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> {
-                    if (mSourcePhoto != null) {
-                        presenter.saveVCard(mBinding.username.getText().toString(), Single.just(mSourcePhoto).map(BitmapUtils::bitmapToPhoto));
-                        mSourcePhoto = null;
-                    }
-                })
-                .show();
-    }
-
-    public void onClickExport() {
-        if (mAccountHasPassword) {
-            onBackupAccount();
-        } else {
-            onUnlockAccount(mAccountId, "");
-        }
-    }
-
-    private void showUsernameRegistrationPopup() {
-        Bundle args = new Bundle();
-        args.putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId);
-        args.putBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword);
-        RegisterNameDialog registrationDialog = new RegisterNameDialog();
-        registrationDialog.setArguments(args);
-        registrationDialog.setListener(this);
-        registrationDialog.show(getParentFragmentManager(), TAG);
-    }
-
-    @Override
-    public void onRegisterName(String name, String password) {
-        presenter.registerName(name, password);
-    }
-
-    @Override
-    public void showExportingProgressDialog() {
-        mWaitDialog = ProgressDialog.show(getActivity(),
-                getString(R.string.export_account_wait_title),
-                getString(R.string.export_account_wait_message));
-    }
-
-    @Override
-    public void showPasswordProgressDialog() {
-        mWaitDialog = ProgressDialog.show(getActivity(),
-                getString(R.string.export_account_wait_title),
-                getString(R.string.account_password_change_wait_message));
-    }
-
-    private void dismissWaitDialog() {
-        if (mWaitDialog != null) {
-            mWaitDialog.dismiss();
-            mWaitDialog = null;
-        }
-    }
-
-    @Override
-    public void passwordChangeEnded(boolean ok) {
-        dismissWaitDialog();
-        if (!ok) {
-            new MaterialAlertDialogBuilder(requireContext())
-                    .setTitle(R.string.account_device_revocation_wrong_password)
-                    .setMessage(R.string.account_export_end_decryption_message)
-                    .setPositiveButton(android.R.string.ok, null)
-                    .show();
-        }
-    }
-
-    private void createFile(String mimeType, String fileName) {
-        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType(mimeType);
-        intent.putExtra(Intent.EXTRA_TITLE, fileName);
-        startActivityForResult(intent, WRITE_REQUEST_CODE);
-    }
-
-    @Override
-    public void displayCompleteArchive(File dest)  {
-        String type = AndroidFileUtils.getMimeType(dest.getAbsolutePath());
-        mCacheArchive = dest;
-        dismissWaitDialog();
-        createFile(type, mBestName);
-    }
-
-    private void onBackupAccount() {
-        BackupAccountDialog dialog = new BackupAccountDialog();
-        Bundle args = new Bundle();
-        args.putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId);
-        dialog.setArguments(args);
-        dialog.setListener(this);
-        dialog.show(getParentFragmentManager(), FRAGMENT_DIALOG_BACKUP);
-    }
-
-    public void onPasswordChangeAsked() {
-        ChangePasswordDialog dialog = new ChangePasswordDialog();
-        Bundle args = new Bundle();
-        args.putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId);
-        args.putBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword);
-        dialog.setArguments(args);
-        dialog.setListener(this);
-        dialog.show(getParentFragmentManager(), FRAGMENT_DIALOG_PASSWORD);
-    }
-
-    @Override
-    public void onPasswordChanged(String oldPassword, String newPassword) {
-        presenter.changePassword(oldPassword, newPassword);
-    }
-
-    @Override
-    public void onUnlockAccount(String accountId, String password) {
-        Context context = requireContext();
-        File cacheDir = new File(AndroidFileUtils.getTempShareDir(context), "archives");
-        cacheDir.mkdirs();
-        if (!cacheDir.canWrite())
-            Log.w(TAG, "Can't write to: " + cacheDir);
-        File dest = new File(cacheDir, mBestName);
-        if (dest.exists())
-            dest.delete();
-        presenter.downloadAccountsArchive(dest, password);
-    }
-
-    @Override
-    public void gotToImageCapture() {
-        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-        try {
-            Context context = requireContext();
-            File file = AndroidFileUtils.createImageFile(context);
-            Uri uri = FileProvider.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file);
-            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
-                    .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
-                    .putExtra("android.intent.extras.CAMERA_FACING", 1)
-                    .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
-                    .putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
-            tmpProfilePhotoUri = uri;
-            startActivityForResult(intent, HomeActivity.REQUEST_CODE_PHOTO);
-        } catch (Exception e) {
-            Toast.makeText(requireContext(), "Error starting camera: " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
-            Log.e(TAG, "Can't create temp file", e);
-        }
-    }
-
-    @Override
-    public void askCameraPermission() {
-        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, HomeActivity.REQUEST_PERMISSION_CAMERA);
-    }
-
-    @Override
-    public void goToGallery() {
-        try {
-            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-            startActivityForResult(intent, HomeActivity.REQUEST_CODE_GALLERY);
-        } catch (Exception e) {
-            Toast.makeText(requireContext(), R.string.gallery_error_message, Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void askGalleryPermission() {
-        requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, HomeActivity.REQUEST_PERMISSION_READ_STORAGE);
-    }
-
-    private void updatePhoto(Uri uriImage) {
-        updatePhoto(AndroidFileUtils.loadBitmap(requireContext(), uriImage));
-    }
-
-    private void updatePhoto(Single<Bitmap> image) {
-        Account account = presenter.getAccount();
-        if (account == null)
-            return;
-        mDisposableBag.add(image.subscribeOn(Schedulers.io())
-                .map(img -> {
-                    mSourcePhoto = img;
-                    return new AvatarDrawable.Builder()
-                            .withPhoto(img)
-                            .withNameData(null, account.getRegisteredName())
-                            .withId(account.getUri())
-                            .withCircleCrop(true)
-                            .build(getContext());
-                })
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(avatar -> mProfilePhoto.setImageDrawable(avatar), e-> Log.e(TAG, "Error loading image", e)));
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        switch (requestCode) {
-            case HomeActivity.REQUEST_PERMISSION_CAMERA:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.cameraClicked();
-                }
-                break;
-            case HomeActivity.REQUEST_PERMISSION_READ_STORAGE:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.galleryClicked();
-                }
-                break;
-        }
-    }
-
-    @Override
-    public void onScrollChanged() {
-        if (mBinding != null) {
-            Activity activity = getActivity();
-            if (activity instanceof HomeActivity)
-                ((HomeActivity) activity).setToolbarElevation(mBinding.scrollview.canScrollVertically(SCROLL_DIRECTION_UP));
-        }
-    }
-
-    @Override
-    public void setSwitchStatus(Account account) {
-        SwitchButton switchButton = ((HomeActivity) requireActivity()).getSwitchButton();
-        int color = R.color.red_400;
-        String status;
-
-        if (account.isEnabled()) {
-            if (account.isTrying()) {
-                color = R.color.orange_400;
-                switchButton.showImage(true);
-                switchButton.startImageAnimation();
-            } else if (account.needsMigration()) {
-                status = getString(R.string.account_update_needed);
-                switchButton.showImage(false);
-                switchButton.setStatus(status);
-            } else if (account.isInError()) {
-                status = getString(R.string.account_status_connection_error);
-                switchButton.showImage(false);
-                switchButton.setStatus(status);
-            } else if (account.isRegistered()) {
-                status = getString(R.string.account_status_online);
-                color = R.color.green_400;
-                switchButton.showImage(false);
-                switchButton.setStatus(status);
-            } else if (!account.isRegistered()){
-                color = R.color.grey_400;
-                status = getString(R.string.account_status_offline);
-                switchButton.showImage(false);
-                switchButton.setStatus(status);
-            } else {
-                status = getString(R.string.account_status_error);
-                switchButton.showImage(false);
-                switchButton.setStatus(status);
-            }
-        } else {
-            color = R.color.grey_400;
-            status = getString(R.string.account_status_offline);
-            switchButton.showImage(false);
-            switchButton.setStatus(status);
-        }
-
-        switchButton.setBackColor(ContextCompat.getColor(requireContext(), color));
-    }
-
-    @Override
-    public void showRevokingProgressDialog() {
-        mWaitDialog = ProgressDialog.show(getActivity(),
-                getString(R.string.revoke_device_wait_title),
-                getString(R.string.revoke_device_wait_message));
-    }
-
-    @Override
-    public void deviceRevocationEnded(final String device, final int status) {
-        dismissWaitDialog();
-        int message, title = R.string.account_device_revocation_error_title;
-        switch (status) {
-            case 0:
-                title = R.string.account_device_revocation_success_title;
-                message = R.string.account_device_revocation_success;
-                break;
-            case 1:
-                message = R.string.account_device_revocation_wrong_password;
-                break;
-            case 2:
-                message = R.string.account_device_revocation_unknown_device;
-                break;
-            default:
-                message = R.string.account_device_revocation_error_unknown;
-        }
-        new MaterialAlertDialogBuilder(requireContext())
-                .setTitle(title)
-                .setMessage(message)
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> {
-                    dialog.dismiss();
-                    if (status == 1) {
-                        onDeviceRevocationAsked(device);
-                    }
-                })
-                .show();
-    }
-
-    @Override
-    public void updateDeviceList(final Map<String, String> devices, final String currentDeviceId) {
-        if (mDeviceAdapter == null) {
-            return;
-        }
-        mDeviceAdapter.setData(devices, currentDeviceId);
-        collapse(mBinding.devicesList);
-    }
-
-    private void shareAccount(String username) {
-        if (!StringUtils.isEmpty(username)) {
-            Intent sharingIntent = new Intent(Intent.ACTION_SEND);
-            sharingIntent.setType("text/plain");
-            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getText(R.string.account_contact_me));
-            sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.account_share_body, username, getText(R.string.app_website)));
-            startActivity(Intent.createChooser(sharingIntent, getText(R.string.share_via)));
-        }
-    }
-
-    private Fragment fragmentWithBundle(Fragment result, String accountId) {
-        Bundle args = new Bundle();
-        args.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId);
-        result.setArguments(args);
-        return result;
-    }
-
-    private void changeFragment(Fragment fragment, String tag) {
-        getChildFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(R.id.fragment, fragment, tag)
-                .addToBackStack(tag).commit();
-        mBinding.fragment.setVisibility(View.VISIBLE);
-        mOnBackPressedCallback.setEnabled(true);
-    }
-
-    public void goToAccount(String accountId) {
-        changeFragment(AccountFragment.newInstance(accountId), MediaPreferenceFragment.TAG);
-    }
-
-    public void goToMedia(String accountId) {
-        changeFragment(MediaPreferenceFragment.newInstance(accountId), MediaPreferenceFragment.TAG);
-    }
-
-    public void goToSystem(String accountId) {
-        changeFragment(GeneralAccountFragment.newInstance(accountId), GeneralAccountFragment.TAG);
-    }
-
-    public void goToAdvanced(String accountId) {
-        changeFragment(fragmentWithBundle(new AdvancedAccountFragment(), accountId), SettingsFragment.TAG);
-    }
-
-    public void goToBlackList(String accountId) {
-        BlockListFragment blockListFragment = new BlockListFragment();
-        Bundle args = new Bundle();
-        args.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId);
-        blockListFragment.setArguments(args);
-        changeFragment(blockListFragment, BlockListFragment.TAG);
-    }
-
-    public void popBackStack() {
-        getChildFragmentManager().popBackStackImmediate();
-        String fragmentTag = getChildFragmentManager().getBackStackEntryAt(getChildFragmentManager().getBackStackEntryCount() - 1).getName();
-        Fragment fragment = getChildFragmentManager().findFragmentByTag(fragmentTag);
-        changeFragment(fragment, fragmentTag);
-    }
-
-    @Override
-    public void onConfirmRevocation(String deviceId, String password) {
-        presenter.revokeDevice(deviceId, password);
-    }
-
-    @Override
-    public void onDeviceRevocationAsked(String deviceId) {
-        ConfirmRevocationDialog dialog = new ConfirmRevocationDialog();
-        Bundle args = new Bundle();
-        args.putString(ConfirmRevocationDialog.DEVICEID_KEY, deviceId);
-        args.putBoolean(ConfirmRevocationDialog.HAS_PASSWORD_KEY, mAccountHasPassword);
-        dialog.setArguments(args);
-        dialog.setListener(this);
-        dialog.show(getParentFragmentManager(), FRAGMENT_DIALOG_REVOCATION);
-    }
-
-    @Override
-    public void onDeviceRename() {
-        final String dev_name = presenter.getDeviceName();
-        RenameDeviceDialog dialog = new RenameDeviceDialog();
-        Bundle args = new Bundle();
-        args.putString(RenameDeviceDialog.DEVICENAME_KEY, dev_name);
-        dialog.setArguments(args);
-        dialog.setListener(this);
-        dialog.show(getParentFragmentManager(), FRAGMENT_DIALOG_RENAME);
-    }
-
-    @Override
-    public void onDeviceRename(String newName) {
-        Log.d(TAG, "onDeviceRename: " + presenter.getDeviceName() + " -> " + newName);
-        presenter.renameDevice(newName);
-    }
-
-    private void expand(View summary) {
-        summary.setVisibility(View.VISIBLE);
-
-        final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-        summary.measure(View.MeasureSpec.makeMeasureSpec(widthSpec, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(1200, View.MeasureSpec.AT_MOST));
-        final int targetHeight = summary.getMeasuredHeight();
-
-        ValueAnimator animator = slideAnimator(0, targetHeight, summary);
-        animator.start();
-
-        mBinding.chipMore.setText(R.string.account_link_hide_button);
-    }
-
-    private void collapse(final View summary) {
-        int finalHeight = summary.getHeight();
-        ValueAnimator mAnimator = slideAnimator(finalHeight, 0, summary);
-        mAnimator.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                // Height=0, but it set visibility to GONE
-                summary.setVisibility(View.GONE);
-            }
-
-            @Override
-            public void onAnimationStart(Animator animator) {
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animator) {
-            }
-        });
-
-        mAnimator.start();
-        mBinding.chipMore.setText(getString(R.string.account_link_show_button, mDeviceAdapter.getCount()));
-    }
-
-    private static ValueAnimator slideAnimator(int start, int end, final View summary) {
-        ValueAnimator animator = ValueAnimator.ofInt(start, end);
-        animator.addUpdateListener(valueAnimator -> {
-            // Update Height
-            int value = (Integer) valueAnimator.getAnimatedValue();
-            ViewGroup.LayoutParams layoutParams = summary.getLayoutParams();
-            layoutParams.height = value;
-            summary.setLayoutParams(layoutParams);
-        });
-        return animator;
-    }
-
-    private void setLinkedDevicesAdapter(Account account) {
-        if (account.getDevices().size() == 1) {
-            mBinding.chipMore.setVisibility(View.GONE);
-        } else {
-            mBinding.chipMore.setVisibility(View.VISIBLE);
-            if (mDeviceAdapter == null) {
-                mDeviceAdapter = new DeviceAdapter(requireContext(), account.getDevices(), account.getDeviceId(), JamiAccountSummaryFragment.this);
-                mBinding.chipMore.setText(getString(R.string.account_link_show_button, mDeviceAdapter.getCount()));
-                mBinding.devicesList.setAdapter(mDeviceAdapter);
-            } else {
-                mDeviceAdapter.setData(account.getDevices(), account.getDeviceId());
-            }
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt
new file mode 100644
index 000000000..e610e253a
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt
@@ -0,0 +1,744 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Loïc Siret <loic.siret@savoirfairelinux.com>
+ *          Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.account
+
+import android.Manifest
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.app.Activity
+import android.app.ProgressDialog
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.text.TextUtils
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver.OnScrollChangedListener
+import android.widget.*
+import androidx.activity.OnBackPressedCallback
+import androidx.core.content.ContextCompat
+import androidx.core.content.FileProvider
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentTransaction
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.account.BackupAccountDialog.UnlockAccountListener
+import cx.ring.account.ChangePasswordDialog.PasswordChangedListener
+import cx.ring.account.ConfirmRevocationDialog.ConfirmRevocationListener
+import cx.ring.account.DeviceAdapter.DeviceRevocationListener
+import cx.ring.account.RegisterNameDialog.RegisterNameDialogListener
+import cx.ring.account.RenameDeviceDialog.RenameDeviceListener
+import cx.ring.client.HomeActivity
+import cx.ring.contactrequests.BlockListFragment
+import cx.ring.databinding.FragAccSummaryBinding
+import cx.ring.fragments.*
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.services.VCardServiceImpl.Companion.loadProfile
+import cx.ring.settings.AccountFragment
+import cx.ring.utils.AndroidFileUtils
+import cx.ring.utils.BitmapUtils
+import cx.ring.utils.ContentUriHandler
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.account.JamiAccountSummaryPresenter
+import net.jami.account.JamiAccountSummaryView
+import net.jami.model.Account
+import net.jami.utils.StringUtils
+import java.io.File
+import java.util.*
+
+@AndroidEntryPoint
+class JamiAccountSummaryFragment :
+    BaseSupportFragment<JamiAccountSummaryPresenter, JamiAccountSummaryView>(),
+    RegisterNameDialogListener, JamiAccountSummaryView, PasswordChangedListener,
+    UnlockAccountListener, OnScrollChangedListener, RenameDeviceListener, DeviceRevocationListener,
+    ConfirmRevocationListener {
+    private val mOnBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(false) {
+        override fun handleOnBackPressed() {
+            if (mBinding!!.fragment.visibility == View.VISIBLE) {
+                mBinding!!.fragment.visibility = View.GONE
+                this.isEnabled = false
+                childFragmentManager.popBackStack()
+            }
+        }
+    }
+    private var mWaitDialog: ProgressDialog? = null
+    private var mAccountHasPassword = true
+    private var mBestName = ""
+    private var mAccountId: String? = ""
+    private var mCacheArchive: File? = null
+    private var mProfilePhoto: ImageView? = null
+    private var mSourcePhoto: Bitmap? = null
+    private var tmpProfilePhotoUri: Uri? = null
+    private var mDeviceAdapter: DeviceAdapter? = null
+    private val mDisposableBag = CompositeDisposable()
+    private val mProfileDisposable = CompositeDisposable()
+    private var mBinding: FragAccSummaryBinding? = null
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        mDisposableBag.add(mProfileDisposable)
+        return FragAccSummaryBinding.inflate(inflater, container, false).apply {
+            mBinding = this
+        }.root
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        requireActivity().onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mDisposableBag.clear()
+        mBinding = null
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mDisposableBag.dispose()
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        requireArguments().let { arguments ->
+            presenter.setAccountId(arguments.getString(AccountEditionFragment.ACCOUNT_ID_KEY)!!)
+        }
+        mBinding!!.scrollview.viewTreeObserver.addOnScrollChangedListener(this)
+        mBinding!!.linkNewDevice.setOnClickListener { v: View? -> showWizard(mAccountId) }
+        mBinding!!.linkedDevices.setRightDrawableOnClickListener { v: View? -> onDeviceRename() }
+        mBinding!!.registerName.setOnClickListener { v: View? -> showUsernameRegistrationPopup() }
+        val items: MutableList<SettingItem> = ArrayList(4)
+        items.add(SettingItem(R.string.account, R.drawable.baseline_account_card_details) { presenter.goToAccount() })
+        items.add(SettingItem(R.string.account_preferences_media_tab, R.drawable.outline_file_copy_24) { presenter.goToMedia() })
+        items.add(SettingItem(R.string.notif_channel_messages, R.drawable.baseline_chat_24) { presenter.goToSystem() })
+        items.add(SettingItem(R.string.account_preferences_advanced_tab, R.drawable.round_check_circle_24) { presenter.goToAdvanced() })
+        val adapter = SettingsAdapter(view.context, R.layout.item_setting, items)
+        mBinding!!.settingsList.onItemClickListener =
+            AdapterView.OnItemClickListener { adapterView: AdapterView<*>?, v: View?, i: Int, l: Long ->
+                adapter.getItem(i)!!
+                    .onClick()
+            }
+        mBinding!!.settingsList.adapter = adapter
+        var totalHeight = 0
+        for (i in 0 until adapter.count) {
+            val listItem = adapter.getView(i, null, mBinding!!.settingsList)
+            listItem.measure(0, 0)
+            totalHeight += listItem.measuredHeight
+        }
+        val par = mBinding!!.settingsList.layoutParams
+        par.height = totalHeight + mBinding!!.settingsList.dividerHeight * (adapter.count - 1)
+        mBinding!!.settingsList.layoutParams = par
+        mBinding!!.settingsList.requestLayout()
+        mBinding!!.chipMore.setOnClickListener { v: View? ->
+            if (mBinding!!.devicesList.visibility == View.GONE) {
+                expand(mBinding!!.devicesList)
+            } else {
+                collapse(mBinding!!.devicesList)
+            }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        (requireActivity() as HomeActivity).let { activity ->
+            activity.showAccountStatus(true)
+            activity.switchButton.setOnCheckedChangeListener { _, isChecked: Boolean ->
+                presenter.enableAccount(isChecked)
+            }
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        (requireActivity() as HomeActivity).let { activity ->
+            activity.showAccountStatus(false)
+            activity.switchButton.setOnCheckedChangeListener(null)
+        }
+    }
+
+    fun setAccount(accountId: String?) {
+        presenter.setAccountId(accountId)
+    }
+
+    override fun updateUserView(account: Account) {
+        val context = context ?: return
+        mProfileDisposable.clear()
+        mProfileDisposable.add(loadProfile(context, account)
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ profile ->
+                mBinding?.let { binding ->
+                    binding.userPhoto.setImageDrawable(AvatarDrawable.build(context, account, profile, true))
+                    binding.username.setText(profile.first)
+                }
+            }, { e: Throwable -> Log.e(TAG, "Error loading avatar", e) })
+        )
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+        when (requestCode) {
+            WRITE_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) {
+                if (resultData != null) {
+                    val uri = resultData.data
+                    if (uri != null) {
+                        if (mCacheArchive != null) {
+                            AndroidFileUtils.moveToUri(requireContext().contentResolver, mCacheArchive!!, uri)
+                                .observeOn(AndroidSchedulers.mainThread())
+                                .subscribe({}) { e: Throwable ->
+                                    val v = view
+                                    if (v != null)
+                                        Snackbar.make(v, "Can't export archive: " + e.message, Snackbar.LENGTH_LONG).show()
+                                }
+                        }
+                    }
+                }
+            }
+            HomeActivity.REQUEST_CODE_PHOTO -> {
+                tmpProfilePhotoUri.let { photoUri ->
+                    if (resultCode == Activity.RESULT_OK) {
+                        if (photoUri == null) {
+                            if (resultData != null)
+                                updatePhoto(Single.just(resultData.extras!!["data"] as Bitmap?))
+                        } else {
+                            updatePhoto(photoUri)
+                        }
+                    }
+                    tmpProfilePhotoUri = null
+                }
+            }
+            HomeActivity.REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && resultData != null) {
+                updatePhoto(resultData.data!!)
+            }
+        }
+    }
+
+    override fun accountChanged(account: Account) {
+        updateUserView(account)
+        mBinding?.let { binding ->
+            binding.userPhoto.setOnClickListener { profileContainerClicked(account) }
+            binding.linkedDevices.setText(account.deviceName)
+            setLinkedDevicesAdapter(account)
+            mAccountHasPassword = account.hasPassword()
+            (requireActivity() as HomeActivity).switchButton.setCheckedSilent(account.isEnabled)
+            binding.accountAliasTxt.text = getString(R.string.profile)
+            binding.identity.setText(account.username)
+            mAccountId = account.accountID
+            mBestName = account.registeredName ?: account.displayUsername ?: account.username!!
+            mBestName = "$mBestName.gz"
+            val username = account.registeredName
+            val currentRegisteredName = account.registeringUsername
+            val hasRegisteredName = !currentRegisteredName && username != null && !username.isEmpty()
+            binding.groupRegisteringName.visibility = if (currentRegisteredName) View.VISIBLE else View.GONE
+            binding.btnShare.setOnClickListener { shareAccount(if (hasRegisteredName) username else account.username) }
+            binding.registerName.visibility = if (hasRegisteredName) View.GONE else View.VISIBLE
+            binding.registeredName.setText(if (hasRegisteredName) username else resources.getString(R.string.no_registered_name_for_account))
+            binding.btnQr.setOnClickListener {
+                QRCodeFragment.newInstance(QRCodeFragment.INDEX_CODE)
+                    .show(parentFragmentManager, QRCodeFragment.TAG)
+            }
+            binding.username.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus: Boolean ->
+                    val name = binding.username.text
+                    if (!hasFocus && !TextUtils.isEmpty(name)) {
+                        presenter.saveVCardFormattedName(name.toString())
+                    }
+                }
+        }
+
+        setSwitchStatus(account)
+    }
+
+    fun onBackPressed(): Boolean {
+        return false
+    }
+
+    private fun showWizard(accountId: String?) {
+        LinkDeviceFragment.newInstance(accountId)
+            .show(parentFragmentManager, LinkDeviceFragment.TAG)
+    }
+
+    override fun showNetworkError() {
+        dismissWaitDialog()
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(R.string.account_export_end_network_title)
+            .setMessage(R.string.account_export_end_network_message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    override fun showPasswordError() {
+        dismissWaitDialog()
+    }
+
+    override fun showGenericError() {
+        dismissWaitDialog()
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(R.string.account_export_end_error_title)
+            .setMessage(R.string.account_export_end_error_message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    override fun showPIN(pin: String) {}
+    private fun profileContainerClicked(account: Account) {
+        val inflater = LayoutInflater.from(activity)
+        val view = inflater.inflate(R.layout.dialog_profile, null) as ViewGroup
+        val profilePhoto = view.findViewById<ImageView>(R.id.profile_photo).apply { mProfilePhoto = this}
+        mDisposableBag.add(AvatarDrawable.load(inflater.context, account)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe { a -> profilePhoto.setImageDrawable(a) })
+        val cameraView = view.findViewById<ImageButton>(R.id.camera)
+        cameraView.setOnClickListener { presenter.cameraClicked() }
+        val gallery = view.findViewById<ImageButton>(R.id.gallery)
+        gallery.setOnClickListener { presenter.galleryClicked() }
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(R.string.profile)
+            .setView(view)
+            .setNegativeButton(android.R.string.cancel) { dialog, which -> dialog.cancel() }
+            .setPositiveButton(android.R.string.ok) { dialog, which ->
+                mSourcePhoto?.let { source ->
+                    presenter.saveVCard(mBinding!!.username.text.toString(),
+                        Single.just(source).map { obj -> BitmapUtils.bitmapToPhoto(obj) })
+                    mSourcePhoto = null
+                }
+            }
+            .show()
+    }
+
+    fun onClickExport() {
+        if (mAccountHasPassword) {
+            onBackupAccount()
+        } else {
+            onUnlockAccount(mAccountId!!, "")
+        }
+    }
+
+    private fun showUsernameRegistrationPopup() {
+        RegisterNameDialog().apply {
+            arguments = Bundle().apply {
+                putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId)
+                putBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword)
+            }
+            setListener(this@JamiAccountSummaryFragment)
+        }.show(parentFragmentManager, TAG)
+    }
+
+    override fun onRegisterName(name: String?, password: String?) {
+        presenter.registerName(name, password)
+    }
+
+    override fun showExportingProgressDialog() {
+        mWaitDialog = ProgressDialog.show(activity, getString(R.string.export_account_wait_title), getString(R.string.export_account_wait_message))
+    }
+
+    override fun showPasswordProgressDialog() {
+        mWaitDialog = ProgressDialog.show(activity, getString(R.string.export_account_wait_title), getString(R.string.account_password_change_wait_message))
+    }
+
+    private fun dismissWaitDialog() {
+        mWaitDialog?.apply {
+            dismiss()
+            mWaitDialog = null
+        }
+    }
+
+    override fun passwordChangeEnded(ok: Boolean) {
+        dismissWaitDialog()
+        if (!ok) {
+            MaterialAlertDialogBuilder(requireContext())
+                .setTitle(R.string.account_device_revocation_wrong_password)
+                .setMessage(R.string.account_export_end_decryption_message)
+                .setPositiveButton(android.R.string.ok, null)
+                .show()
+        }
+    }
+
+    private fun createFile(mimeType: String?, fileName: String) {
+        val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+        intent.addCategory(Intent.CATEGORY_OPENABLE)
+        intent.type = mimeType
+        intent.putExtra(Intent.EXTRA_TITLE, fileName)
+        startActivityForResult(intent, WRITE_REQUEST_CODE)
+    }
+
+    override fun displayCompleteArchive(dest: File) {
+        val type = AndroidFileUtils.getMimeType(dest.absolutePath)
+        mCacheArchive = dest
+        dismissWaitDialog()
+        createFile(type, mBestName)
+    }
+
+    private fun onBackupAccount() {
+        BackupAccountDialog().apply {
+            arguments = Bundle().apply {
+                putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId)
+            }
+            setListener(this@JamiAccountSummaryFragment)
+        }.show(parentFragmentManager, FRAGMENT_DIALOG_BACKUP)
+    }
+
+    fun onPasswordChangeAsked() {
+        ChangePasswordDialog().apply {
+            arguments = Bundle().apply {
+                putString(AccountEditionFragment.ACCOUNT_ID_KEY, mAccountId)
+                putBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, mAccountHasPassword)
+            }
+            setListener(this@JamiAccountSummaryFragment)
+        }.show(parentFragmentManager, FRAGMENT_DIALOG_PASSWORD)
+    }
+
+    override fun onPasswordChanged(oldPassword: String, newPassword: String) {
+        presenter.changePassword(oldPassword, newPassword)
+    }
+
+    override fun onUnlockAccount(accountId: String, password: String) {
+        val context = requireContext()
+        val cacheDir = File(AndroidFileUtils.getTempShareDir(context), "archives")
+        cacheDir.mkdirs()
+        if (!cacheDir.canWrite()) Log.w(TAG, "Can't write to: $cacheDir")
+        val dest = File(cacheDir, mBestName)
+        if (dest.exists()) dest.delete()
+        presenter.downloadAccountsArchive(dest, password)
+    }
+
+    override fun gotToImageCapture() {
+        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+        try {
+            val context = requireContext()
+            val file = AndroidFileUtils.createImageFile(context)
+            val uri = FileProvider.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file)
+            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
+                .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                .putExtra("android.intent.extras.CAMERA_FACING", 1)
+                .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
+                .putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
+            tmpProfilePhotoUri = uri
+            startActivityForResult(intent, HomeActivity.REQUEST_CODE_PHOTO)
+        } catch (e: Exception) {
+            Toast.makeText(requireContext(), "Error starting camera: " + e.localizedMessage, Toast.LENGTH_SHORT).show()
+            Log.e(TAG, "Can't create temp file", e)
+        }
+    }
+
+    override fun askCameraPermission() {
+        requestPermissions(arrayOf(
+                Manifest.permission.CAMERA,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE
+            ), HomeActivity.REQUEST_PERMISSION_CAMERA
+        )
+    }
+
+    override fun goToGallery() {
+        try {
+            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+            startActivityForResult(intent, HomeActivity.REQUEST_CODE_GALLERY)
+        } catch (e: Exception) {
+            Toast.makeText(requireContext(), R.string.gallery_error_message, Toast.LENGTH_SHORT)
+                .show()
+        }
+    }
+
+    override fun askGalleryPermission() {
+        requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), HomeActivity.REQUEST_PERMISSION_READ_STORAGE)
+    }
+
+    private fun updatePhoto(uriImage: Uri) {
+        updatePhoto(AndroidFileUtils.loadBitmap(requireContext(), uriImage))
+    }
+
+    private fun updatePhoto(image: Single<Bitmap>) {
+        val account = presenter.account ?: return
+        mDisposableBag.add(image.subscribeOn(Schedulers.io())
+            .map { img ->
+                mSourcePhoto = img
+                AvatarDrawable.Builder()
+                    .withPhoto(img)
+                    .withNameData(null, account.registeredName)
+                    .withId(account.uri)
+                    .withCircleCrop(true)
+                    .build(requireContext())
+            }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ avatar: AvatarDrawable -> mProfilePhoto!!.setImageDrawable(avatar) }) { e: Throwable ->
+                Log.e(TAG, "Error loading image", e)
+            })
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        when (requestCode) {
+            HomeActivity.REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter.cameraClicked()
+            }
+            HomeActivity.REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter.galleryClicked()
+            }
+        }
+    }
+
+    override fun onScrollChanged() {
+        if (mBinding != null) {
+            val activity = activity
+            if (activity is HomeActivity) activity.setToolbarElevation(
+                mBinding!!.scrollview.canScrollVertically(SCROLL_DIRECTION_UP)
+            )
+        }
+    }
+
+    override fun setSwitchStatus(account: Account) {
+        val switchButton = (requireActivity() as HomeActivity).switchButton
+        var color = R.color.red_400
+        val status: String
+        if (account.isEnabled) {
+            if (account.isTrying) {
+                color = R.color.orange_400
+                switchButton.showImage(true)
+                switchButton.startImageAnimation()
+            } else if (account.needsMigration()) {
+                status = getString(R.string.account_update_needed)
+                switchButton.showImage(false)
+                switchButton.status = status
+            } else if (account.isInError) {
+                status = getString(R.string.account_status_connection_error)
+                switchButton.showImage(false)
+                switchButton.status = status
+            } else if (account.isRegistered) {
+                status = getString(R.string.account_status_online)
+                color = R.color.green_400
+                switchButton.showImage(false)
+                switchButton.status = status
+            } else if (!account.isRegistered) {
+                color = R.color.grey_400
+                status = getString(R.string.account_status_offline)
+                switchButton.showImage(false)
+                switchButton.status = status
+            } else {
+                status = getString(R.string.account_status_error)
+                switchButton.showImage(false)
+                switchButton.status = status
+            }
+        } else {
+            color = R.color.grey_400
+            status = getString(R.string.account_status_offline)
+            switchButton.showImage(false)
+            switchButton.status = status
+        }
+        switchButton.backColor = ContextCompat.getColor(requireContext(), color)
+    }
+
+    override fun showRevokingProgressDialog() {
+        mWaitDialog = ProgressDialog.show(activity,
+            getString(R.string.revoke_device_wait_title),
+            getString(R.string.revoke_device_wait_message)
+        )
+    }
+
+    override fun deviceRevocationEnded(device: String, status: Int) {
+        dismissWaitDialog()
+        val message: Int
+        var title = R.string.account_device_revocation_error_title
+        when (status) {
+            0 -> {
+                title = R.string.account_device_revocation_success_title
+                message = R.string.account_device_revocation_success
+            }
+            1 -> message = R.string.account_device_revocation_wrong_password
+            2 -> message = R.string.account_device_revocation_unknown_device
+            else -> message = R.string.account_device_revocation_error_unknown
+        }
+        MaterialAlertDialogBuilder(requireContext())
+            .setTitle(title)
+            .setMessage(message)
+            .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
+                dialog.dismiss()
+                if (status == 1) {
+                    onDeviceRevocationAsked(device)
+                }
+            }
+            .show()
+    }
+
+    override fun updateDeviceList(devices: Map<String, String>, currentDeviceId: String) {
+        if (mDeviceAdapter == null) {
+            return
+        }
+        mDeviceAdapter!!.setData(devices, currentDeviceId)
+        collapse(mBinding!!.devicesList)
+    }
+
+    private fun shareAccount(username: String?) {
+        if (!StringUtils.isEmpty(username)) {
+            val sharingIntent = Intent(Intent.ACTION_SEND)
+            sharingIntent.type = "text/plain"
+            sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getText(R.string.account_contact_me))
+            sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.account_share_body, username, getText(R.string.app_website)))
+            startActivity(Intent.createChooser(sharingIntent, getText(R.string.share_via)))
+        }
+    }
+
+    private fun fragmentWithBundle(result: Fragment, accountId: String): Fragment {
+        return result.apply {
+            arguments = Bundle().apply { putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) }
+        }
+    }
+
+    private fun changeFragment(fragment: Fragment, tag: String?) {
+        childFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(R.id.fragment, fragment, tag)
+            .addToBackStack(tag).commit()
+        mBinding!!.fragment.visibility = View.VISIBLE
+        mOnBackPressedCallback.isEnabled = true
+    }
+
+    override fun goToAccount(accountId: String) {
+        changeFragment(AccountFragment.newInstance(accountId), AccountFragment.TAG)
+    }
+
+    override fun goToMedia(accountId: String) {
+        changeFragment(MediaPreferenceFragment.newInstance(accountId), MediaPreferenceFragment.TAG)
+    }
+
+    override fun goToSystem(accountId: String) {
+        changeFragment(GeneralAccountFragment.newInstance(accountId), GeneralAccountFragment.TAG)
+    }
+
+    override fun goToAdvanced(accountId: String) {
+        changeFragment(fragmentWithBundle(AdvancedAccountFragment(), accountId), AdvancedAccountFragment.TAG)
+    }
+
+    fun goToBlackList(accountId: String?) {
+        val blockListFragment = BlockListFragment().apply {
+            arguments = Bundle().apply { putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) }
+        }
+        changeFragment(blockListFragment, BlockListFragment.TAG)
+    }
+
+    fun popBackStack() {
+        childFragmentManager.popBackStackImmediate()
+        val fragmentTag = childFragmentManager.getBackStackEntryAt(childFragmentManager.backStackEntryCount - 1).name
+        val fragment = childFragmentManager.findFragmentByTag(fragmentTag)
+        if (fragment != null)
+            changeFragment(fragment, fragmentTag)
+    }
+
+    override fun onConfirmRevocation(deviceId: String, password: String) {
+        presenter.revokeDevice(deviceId, password)
+    }
+
+    override fun onDeviceRevocationAsked(deviceId: String?) {
+        ConfirmRevocationDialog().apply {
+            arguments = Bundle().apply {
+                putString(ConfirmRevocationDialog.DEVICEID_KEY, deviceId)
+                putBoolean(ConfirmRevocationDialog.HAS_PASSWORD_KEY, mAccountHasPassword)
+            }
+            setListener(this@JamiAccountSummaryFragment)
+        }.show(parentFragmentManager, FRAGMENT_DIALOG_REVOCATION)
+    }
+
+    override fun onDeviceRename() {
+        RenameDeviceDialog().apply {
+            arguments = Bundle().apply { putString(RenameDeviceDialog.DEVICENAME_KEY, presenter.deviceName) }
+            setListener(this@JamiAccountSummaryFragment)
+        }.show(parentFragmentManager, FRAGMENT_DIALOG_RENAME)
+    }
+
+    override fun onDeviceRename(newName: String?) {
+        Log.d(TAG, "onDeviceRename: " + presenter.deviceName + " -> " + newName)
+        presenter.renameDevice(newName)
+    }
+
+    private fun expand(summary: View) {
+        summary.visibility = View.VISIBLE
+        val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+        summary.measure(
+            View.MeasureSpec.makeMeasureSpec(widthSpec, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(1200, View.MeasureSpec.AT_MOST)
+        )
+        val targetHeight = summary.measuredHeight
+        val animator = slideAnimator(0, targetHeight, summary)
+        animator.start()
+        mBinding!!.chipMore.setText(R.string.account_link_hide_button)
+    }
+
+    private fun collapse(summary: View) {
+        val finalHeight = summary.height
+        val mAnimator = slideAnimator(finalHeight, 0, summary)
+        mAnimator.addListener(object : Animator.AnimatorListener {
+            override fun onAnimationEnd(animator: Animator) {
+                // Height=0, but it set visibility to GONE
+                summary.visibility = View.GONE
+            }
+
+            override fun onAnimationStart(animator: Animator) {}
+            override fun onAnimationCancel(animator: Animator) {}
+            override fun onAnimationRepeat(animator: Animator) {}
+        })
+        mAnimator.start()
+        mBinding!!.chipMore.text = getString(R.string.account_link_show_button, mDeviceAdapter!!.count)
+    }
+
+    private fun setLinkedDevicesAdapter(account: Account) {
+        if (account.devices.size == 1) {
+            mBinding!!.chipMore.visibility = View.GONE
+        } else {
+            mBinding!!.chipMore.visibility = View.VISIBLE
+            if (mDeviceAdapter == null) {
+                mDeviceAdapter = DeviceAdapter(requireContext(), account.devices, account.deviceId, this@JamiAccountSummaryFragment)
+                mBinding!!.chipMore.text = getString(R.string.account_link_show_button, mDeviceAdapter!!.count)
+                mBinding!!.devicesList.adapter = mDeviceAdapter
+            } else {
+                mDeviceAdapter!!.setData(account.devices, account.deviceId)
+            }
+        }
+    }
+
+    companion object {
+        val TAG = JamiAccountSummaryFragment::class.simpleName!!
+        private val FRAGMENT_DIALOG_REVOCATION = "$TAG.dialog.deviceRevocation"
+        private val FRAGMENT_DIALOG_RENAME = "$TAG.dialog.deviceRename"
+        private val FRAGMENT_DIALOG_PASSWORD = "$TAG.dialog.changePassword"
+        private val FRAGMENT_DIALOG_BACKUP = "$TAG.dialog.backup"
+        private const val WRITE_REQUEST_CODE = 43
+        private const val SCROLL_DIRECTION_UP = -1
+        private fun slideAnimator(start: Int, end: Int, summary: View): ValueAnimator {
+            val animator = ValueAnimator.ofInt(start, end)
+            animator.addUpdateListener { valueAnimator: ValueAnimator ->
+                // Update Height
+                val value = valueAnimator.animatedValue as Int
+                val layoutParams = summary.layoutParams
+                layoutParams.height = value
+                summary.layoutParams = layoutParams
+            }
+            return animator
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java
index 482d7d87f..5a50c8395 100644
--- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java
@@ -39,7 +39,6 @@ import androidx.annotation.Nullable;
 import com.google.android.material.textfield.TextInputLayout;
 
 import cx.ring.R;
-import cx.ring.application.JamiApplication;
 import cx.ring.databinding.FragAccJamiUsernameBinding;
 
 import net.jami.account.JamiAccountCreationPresenter;
@@ -47,8 +46,10 @@ import net.jami.account.JamiAccountCreationView;
 import net.jami.mvp.AccountCreationModel;
 import cx.ring.mvp.BaseSupportFragment;
 import cx.ring.utils.RegisteredNameFilter;
+import dagger.hilt.android.AndroidEntryPoint;
 
-public class JamiAccountUsernameFragment extends BaseSupportFragment<JamiAccountCreationPresenter>
+@AndroidEntryPoint
+public class JamiAccountUsernameFragment extends BaseSupportFragment<JamiAccountCreationPresenter, JamiAccountCreationView>
         implements JamiAccountCreationView {
 
     private static final String KEY_MODEL = "model";
@@ -75,7 +76,7 @@ public class JamiAccountUsernameFragment extends BaseSupportFragment<JamiAccount
             model = (AccountCreationModelImpl) savedInstanceState.getSerializable(KEY_MODEL);
         }
         binding = FragAccJamiUsernameBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
+        //((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.java
deleted file mode 100644
index 2fb6748ee..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.account;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.viewpager.widget.ViewPager;
-
-import cx.ring.databinding.FragAccJamiLinkBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import net.jami.mvp.AccountCreationModel;
-
-public class JamiLinkAccountFragment extends BaseSupportFragment {
-
-    public static final String TAG = JamiLinkAccountFragment.class.getSimpleName();
-    private static final int NUM_PAGES = 2;
-
-    private AccountCreationModel model;
-    private FragAccJamiLinkBinding mBinding;
-    private Fragment mCurrentFragment;
-
-    private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
-        @Override
-        public void handleOnBackPressed() {
-            if (mCurrentFragment instanceof ProfileCreationFragment) {
-                ProfileCreationFragment fragment = (ProfileCreationFragment) mCurrentFragment;
-                ((AccountWizardActivity) getActivity()).profileCreated(fragment.getModel(), false);
-                return;
-            }
-            mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() - 1);
-        }
-    };
-
-    public static JamiLinkAccountFragment newInstance(AccountCreationModelImpl ringAccountViewModel) {
-        JamiLinkAccountFragment fragment = new JamiLinkAccountFragment();
-        fragment.model = ringAccountViewModel;
-        return fragment;
-    }
-
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        outState.putSerializable("model", model);
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        setRetainInstance(true);
-        if (savedInstanceState != null && model == null) {
-            model = (AccountCreationModel) savedInstanceState.getSerializable("model");
-        }
-        mBinding = FragAccJamiLinkBinding.inflate(inflater, container, false);
-        return mBinding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mBinding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        ScreenSlidePagerAdapter pagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager(), model);
-        mBinding.pager.setAdapter(pagerAdapter);
-        mBinding.pager.disableScroll(true);
-
-        mBinding.pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
-            @Override
-            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-            }
-
-            @Override
-            public void onPageSelected(int position) {
-                mCurrentFragment = pagerAdapter.getRegisteredFragment(position);
-                onBackPressedCallback.setEnabled(mCurrentFragment instanceof ProfileCreationFragment);
-            }
-
-            @Override
-            public void onPageScrollStateChanged(int state) {
-
-            }
-        });
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
-    }
-
-    public void scrollPagerFragment(AccountCreationModel accountCreationModel) {
-        if (accountCreationModel == null) {
-            mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() - 1);
-            return;
-        }
-        mBinding.pager.setCurrentItem(mBinding.pager.getCurrentItem() + 1);
-        for (Fragment fragment : getChildFragmentManager().getFragments()) {
-            if (fragment instanceof JamiAccountPasswordFragment) {
-                ((JamiAccountPasswordFragment) fragment).setUsername(accountCreationModel.getUsername());
-            }
-        }
-    }
-
-    private static class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
-        AccountCreationModelImpl ringAccountViewModel;
-        SparseArray<Fragment> mRegisteredFragments = new SparseArray<>();
-
-        public ScreenSlidePagerAdapter(FragmentManager fm, AccountCreationModel model) {
-            super(fm);
-            ringAccountViewModel = (AccountCreationModelImpl) model;
-        }
-
-        @Override
-        public Fragment getItem(int position) {
-            Fragment fragment = null;
-
-            switch (position) {
-                case 0:
-                    fragment = JamiLinkAccountPasswordFragment.newInstance(ringAccountViewModel);
-                    break;
-                case 1:
-                    fragment = ProfileCreationFragment.newInstance(ringAccountViewModel);
-                    break;
-            }
-
-            return fragment;
-        }
-
-        @NonNull
-        @Override
-        public Object instantiateItem(@NonNull ViewGroup container, int position) {
-            Fragment fragment = (Fragment) super.instantiateItem(container, position);
-            mRegisteredFragments.put(position, fragment);
-            return super.instantiateItem(container, position);
-        }
-
-        @Override
-        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
-            mRegisteredFragments.remove(position);
-            super.destroyItem(container, position, object);
-        }
-
-        @Override
-        public int getCount() {
-            return NUM_PAGES;
-        }
-
-        public Fragment getRegisteredFragment(int position) {
-            return mRegisteredFragments.get(position);
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt
new file mode 100644
index 000000000..34a6ccdbc
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt
@@ -0,0 +1,151 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.account
+
+import android.content.Context
+import android.os.Bundle
+import android.util.SparseArray
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentStatePagerAdapter
+import androidx.viewpager.widget.ViewPager.OnPageChangeListener
+import cx.ring.databinding.FragAccJamiLinkBinding
+import net.jami.mvp.AccountCreationModel
+
+class JamiLinkAccountFragment : Fragment() {
+    private lateinit var model: AccountCreationModel
+    private var mBinding: FragAccJamiLinkBinding? = null
+    private var mCurrentFragment: Fragment? = null
+
+    private val onBackPressedCallback: OnBackPressedCallback =
+        object : OnBackPressedCallback(false) {
+            override fun handleOnBackPressed() {
+                if (mCurrentFragment is ProfileCreationFragment) {
+                    val fragment = mCurrentFragment as ProfileCreationFragment
+                    (activity as AccountWizardActivity?)!!.profileCreated(fragment.model, false)
+                    return
+                }
+                mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1
+            }
+        }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        outState.putSerializable("model", model)
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        retainInstance = true
+        if (savedInstanceState != null) {
+            model = savedInstanceState.getSerializable("model") as AccountCreationModel
+        }
+        return FragAccJamiLinkBinding.inflate(inflater, container, false).apply {
+            mBinding = this
+        }.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mBinding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        val pagerAdapter = ScreenSlidePagerAdapter(childFragmentManager, model)
+        mBinding!!.pager.adapter = pagerAdapter
+        mBinding!!.pager.disableScroll(true)
+        mBinding!!.pager.addOnPageChangeListener(object : OnPageChangeListener {
+            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
+
+            override fun onPageSelected(position: Int) {
+                mCurrentFragment = pagerAdapter.getRegisteredFragment(position)
+                onBackPressedCallback.isEnabled = mCurrentFragment is ProfileCreationFragment
+            }
+
+            override fun onPageScrollStateChanged(state: Int) {}
+        })
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+    }
+
+    fun scrollPagerFragment(accountCreationModel: AccountCreationModel?) {
+        if (accountCreationModel == null) {
+            mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1
+            return
+        }
+        mBinding!!.pager.currentItem = mBinding!!.pager.currentItem + 1
+        for (fragment in childFragmentManager.fragments) {
+            if (fragment is JamiAccountPasswordFragment) {
+                fragment.setUsername(accountCreationModel.username)
+            }
+        }
+    }
+
+    private class ScreenSlidePagerAdapter(fm: FragmentManager, model: AccountCreationModel) :
+        FragmentStatePagerAdapter(fm) {
+        var ringAccountViewModel: AccountCreationModelImpl = model as AccountCreationModelImpl
+        var mRegisteredFragments = SparseArray<Fragment>()
+        override fun getItem(position: Int): Fragment {
+            var fragment: Fragment? = null
+            when (position) {
+                0 -> fragment = JamiLinkAccountPasswordFragment.newInstance(ringAccountViewModel)
+                1 -> fragment = ProfileCreationFragment.newInstance(ringAccountViewModel)
+            }
+            return fragment!!
+        }
+
+        override fun instantiateItem(container: ViewGroup, position: Int): Any {
+            val fragment = super.instantiateItem(container, position) as Fragment
+            mRegisteredFragments.put(position, fragment)
+            return super.instantiateItem(container, position)
+        }
+
+        override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
+            mRegisteredFragments.remove(position)
+            super.destroyItem(container, position, `object`)
+        }
+
+        override fun getCount(): Int {
+            return NUM_PAGES
+        }
+
+        fun getRegisteredFragment(position: Int): Fragment {
+            return mRegisteredFragments[position]
+        }
+
+    }
+
+    companion object {
+        val TAG = JamiLinkAccountFragment::class.simpleName!!
+        private const val NUM_PAGES = 2
+
+        fun newInstance(ringAccountViewModel: AccountCreationModelImpl): JamiLinkAccountFragment {
+            val fragment = JamiLinkAccountFragment()
+            fragment.model = ringAccountViewModel
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java
index 67da98a8c..81b175a7d 100644
--- a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java
@@ -41,8 +41,10 @@ import net.jami.account.JamiLinkAccountPresenter;
 import net.jami.account.JamiLinkAccountView;
 import net.jami.mvp.AccountCreationModel;
 import cx.ring.mvp.BaseSupportFragment;
+import dagger.hilt.android.AndroidEntryPoint;
 
-public class JamiLinkAccountPasswordFragment extends BaseSupportFragment<JamiLinkAccountPresenter>
+@AndroidEntryPoint
+public class JamiLinkAccountPasswordFragment extends BaseSupportFragment<JamiLinkAccountPresenter, JamiLinkAccountView>
         implements JamiLinkAccountView {
 
     public static final String TAG = JamiLinkAccountPasswordFragment.class.getSimpleName();
@@ -61,7 +63,6 @@ public class JamiLinkAccountPasswordFragment extends BaseSupportFragment<JamiLin
         if (model == null)
             return null;
         mBinding = FragAccJamiLinkPasswordBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return mBinding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.java b/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.java
deleted file mode 100644
index 764ee026a..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.account;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import java.io.File;
-import java.io.IOException;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.FragAccProfileCreateBinding;
-
-import net.jami.account.ProfileCreationPresenter;
-import net.jami.account.ProfileCreationView;
-import net.jami.model.Account;
-import net.jami.mvp.AccountCreationModel;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.core.Single;
-
-public class ProfileCreationFragment extends BaseSupportFragment<ProfileCreationPresenter> implements ProfileCreationView {
-    public static final String TAG = ProfileCreationFragment.class.getSimpleName();
-
-    public static final int REQUEST_CODE_PHOTO = 1;
-    public static final int REQUEST_CODE_GALLERY = 2;
-    public static final int REQUEST_PERMISSION_CAMERA = 3;
-    public static final int REQUEST_PERMISSION_READ_STORAGE = 4;
-
-    private AccountCreationModel model;
-    private Uri tmpProfilePhotoUri;
-    private FragAccProfileCreateBinding binding;
-
-    public static ProfileCreationFragment newInstance(AccountCreationModelImpl model) {
-        ProfileCreationFragment fragment = new ProfileCreationFragment();
-        fragment.model = model;
-        return fragment;
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragAccProfileCreateBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        setRetainInstance(true);
-
-        if (model == null) {
-            getActivity().finish();
-            return;
-        }
-        if (binding.profilePhoto.getDrawable() == null) {
-            binding.profilePhoto.setImageDrawable(
-                    new AvatarDrawable.Builder()
-                            .withNameData(model.getFullName(), model.getUsername())
-                            .withCircleCrop(true)
-                            .build(view.getContext())
-            );
-        }
-        presenter.initPresenter(model);
-
-        binding.gallery.setOnClickListener(v -> presenter.galleryClick());
-        binding.camera.setOnClickListener(v -> presenter.cameraClick());
-        binding.nextCreateAccount.setOnClickListener(v -> presenter.nextClick());
-        binding.skipCreateAccount.setOnClickListener(v -> presenter.skipClick());
-        binding.username.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {}
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                presenter.fullNameUpdated(s.toString());
-            }
-        });
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
-        switch (requestCode) {
-            case REQUEST_CODE_PHOTO:
-                if (resultCode == Activity.RESULT_OK) {
-                    if (tmpProfilePhotoUri == null) {
-                        if (intent != null) {
-                            Bundle bundle = intent.getExtras();
-                            Bitmap b = bundle == null ? null : (Bitmap) bundle.get("data");
-                            if (b != null) {
-                                presenter.photoUpdated(Single.just(b));
-                            }
-                        }
-                    } else {
-                        presenter.photoUpdated(AndroidFileUtils.loadBitmap(getContext(), tmpProfilePhotoUri).map(b -> (Object)b));
-                    }
-                }
-                break;
-            case REQUEST_CODE_GALLERY:
-                if (resultCode == Activity.RESULT_OK && intent != null) {
-                    presenter.photoUpdated(AndroidFileUtils.loadBitmap(getActivity(), intent.getData()).map(b -> (Object)b));
-                }
-                break;
-            default:
-                super.onActivityResult(requestCode, resultCode, intent);
-                break;
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        switch (requestCode) {
-            case ProfileCreationFragment.REQUEST_PERMISSION_CAMERA:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.cameraPermissionChanged(true);
-                    presenter.cameraClick();
-                }
-                break;
-            case ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.galleryClick();
-                }
-                break;
-        }
-    }
-
-    @Override
-    public void displayProfileName(String profileName) {
-        binding.username.setText(profileName);
-    }
-
-    @Override
-    public void goToGallery() {
-        try {
-            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-            startActivityForResult(intent, REQUEST_CODE_GALLERY);
-        } catch (Exception e) {
-            Toast.makeText(requireContext(), R.string.gallery_error_message, Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void goToPhotoCapture() {
-        try {
-            Context context = requireContext();
-            File file = AndroidFileUtils.createImageFile(context);
-            Uri uri = ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file);
-            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
-                    .putExtra(MediaStore.EXTRA_OUTPUT, uri)
-                    .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
-                    .putExtra("android.intent.extras.CAMERA_FACING", 1)
-                    .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
-                    .putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
-            tmpProfilePhotoUri = uri;
-            startActivityForResult(intent, REQUEST_CODE_PHOTO);
-        } catch (IOException e) {
-            Log.e(TAG, "Can't create temp file", e);
-        } catch (ActivityNotFoundException e) {
-            Log.e(TAG, "Could not start activity");
-        }
-    }
-
-    @Override
-    public void askStoragePermission() {
-        requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_READ_STORAGE);
-    }
-
-    @Override
-    public void askPhotoPermission() {
-        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CAMERA);
-    }
-
-    @Override
-    public void goToNext(AccountCreationModel accountCreationModel, boolean saveProfile) {
-        Activity wizardActivity = getActivity();
-        if (wizardActivity instanceof AccountWizardActivity) {
-            AccountWizardActivity wizard = (AccountWizardActivity) wizardActivity;
-            wizard.profileCreated(accountCreationModel, saveProfile);
-        }
-    }
-
-    @Override
-    public void setProfile(AccountCreationModel accountCreationModel) {
-        AccountCreationModelImpl model = ((AccountCreationModelImpl) accountCreationModel);
-        Account newAccount = model.getNewAccount();
-        binding.profilePhoto.setImageDrawable(
-                new AvatarDrawable.Builder()
-                        .withPhoto(model.getPhoto())
-                        .withNameData(accountCreationModel.getFullName(), accountCreationModel.getUsername())
-                        .withId(newAccount == null ? null : newAccount.getUsername())
-                        .withCircleCrop(true)
-                        .build(getContext())
-        );
-    }
-
-    public AccountCreationModel getModel() {
-        return model;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt
new file mode 100644
index 000000000..e568b8ca9
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt
@@ -0,0 +1,224 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.account
+
+import android.Manifest
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import cx.ring.R
+import cx.ring.databinding.FragAccProfileCreateBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.utils.AndroidFileUtils.createImageFile
+import cx.ring.utils.AndroidFileUtils.loadBitmap
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ContentUriHandler.getUriForFile
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.core.Single
+import net.jami.account.ProfileCreationPresenter
+import net.jami.account.ProfileCreationView
+import net.jami.mvp.AccountCreationModel
+import java.io.IOException
+
+@AndroidEntryPoint
+class ProfileCreationFragment : BaseSupportFragment<ProfileCreationPresenter, ProfileCreationView>(), ProfileCreationView {
+    var model: AccountCreationModel? = null
+        private set
+    private var tmpProfilePhotoUri: Uri? = null
+    private var binding: FragAccProfileCreateBinding? = null
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        return FragAccProfileCreateBinding.inflate(inflater, container, false).apply {
+            binding = this
+        }.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        retainInstance = true
+        if (model == null) {
+            activity?.finish()
+            return
+        }
+        if (binding!!.profilePhoto.drawable == null) {
+            binding!!.profilePhoto.setImageDrawable(
+                AvatarDrawable.Builder()
+                    .withNameData(model!!.fullName, model!!.username)
+                    .withCircleCrop(true)
+                    .build(view.context)
+            )
+        }
+        presenter.initPresenter(model)
+        binding!!.gallery.setOnClickListener { presenter.galleryClick() }
+        binding!!.camera.setOnClickListener { presenter.cameraClick() }
+        binding!!.nextCreateAccount.setOnClickListener { presenter.nextClick() }
+        binding!!.skipCreateAccount.setOnClickListener { presenter.skipClick() }
+        binding!!.username.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+            override fun afterTextChanged(s: Editable) {
+                presenter.fullNameUpdated(s.toString())
+            }
+        })
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
+        when (requestCode) {
+            REQUEST_CODE_PHOTO -> if (resultCode == Activity.RESULT_OK) {
+                if (tmpProfilePhotoUri == null) {
+                    if (intent != null) {
+                        val bundle = intent.extras
+                        val b = if (bundle == null) null else bundle["data"] as Bitmap?
+                        if (b != null) {
+                            presenter.photoUpdated(Single.just(b))
+                        }
+                    }
+                } else {
+                    presenter.photoUpdated(loadBitmap(requireContext(), tmpProfilePhotoUri!!).map { b: Bitmap -> b })
+                }
+            }
+            REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && intent != null) {
+                presenter.photoUpdated(loadBitmap(requireContext(), intent.data!!).map { b -> b })
+            }
+            else -> super.onActivityResult(requestCode, resultCode, intent)
+        }
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        when (requestCode) {
+            REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter.cameraPermissionChanged(true)
+                presenter.cameraClick()
+            }
+            REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter.galleryClick()
+            }
+        }
+    }
+
+    override fun displayProfileName(profileName: String) {
+        binding!!.username.setText(profileName)
+    }
+
+    override fun goToGallery() {
+        try {
+            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+            startActivityForResult(intent, REQUEST_CODE_GALLERY)
+        } catch (e: Exception) {
+            Toast.makeText(requireContext(), R.string.gallery_error_message, Toast.LENGTH_SHORT)
+                .show()
+        }
+    }
+
+    override fun goToPhotoCapture() {
+        try {
+            val context = requireContext()
+            val file = createImageFile(context)
+            val uri = getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file)
+            val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
+                .putExtra(MediaStore.EXTRA_OUTPUT, uri)
+                .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                .putExtra("android.intent.extras.CAMERA_FACING", 1)
+                .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
+                .putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
+            tmpProfilePhotoUri = uri
+            startActivityForResult(intent, REQUEST_CODE_PHOTO)
+        } catch (e: IOException) {
+            Log.e(TAG, "Can't create temp file", e)
+        } catch (e: ActivityNotFoundException) {
+            Log.e(TAG, "Could not start activity")
+        }
+    }
+
+    override fun askStoragePermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
+            REQUEST_PERMISSION_READ_STORAGE
+        )
+    }
+
+    override fun askPhotoPermission() {
+        requestPermissions(
+            arrayOf(
+                Manifest.permission.CAMERA,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE
+            ), REQUEST_PERMISSION_CAMERA
+        )
+    }
+
+    override fun goToNext(accountCreationModel: AccountCreationModel, saveProfile: Boolean) {
+        val wizardActivity: Activity? = activity
+        if (wizardActivity is AccountWizardActivity) {
+            wizardActivity.profileCreated(accountCreationModel, saveProfile)
+        }
+    }
+
+    override fun setProfile(accountCreationModel: AccountCreationModel) {
+        val model = accountCreationModel as AccountCreationModelImpl
+        val newAccount = model.newAccount
+        binding!!.profilePhoto.setImageDrawable(
+            AvatarDrawable.Builder()
+                .withPhoto(model.photo)
+                .withNameData(
+                    accountCreationModel.getFullName(),
+                    accountCreationModel.getUsername()
+                )
+                .withId(newAccount?.username)
+                .withCircleCrop(true)
+                .build(requireContext())
+        )
+    }
+
+    companion object {
+        val TAG = ProfileCreationFragment::class.simpleName!!
+        const val REQUEST_CODE_PHOTO = 1
+        const val REQUEST_CODE_GALLERY = 2
+        const val REQUEST_PERMISSION_CAMERA = 3
+        const val REQUEST_PERMISSION_READ_STORAGE = 4
+
+        fun newInstance(model: AccountCreationModelImpl?): ProfileCreationFragment {
+            val fragment = ProfileCreationFragment()
+            fragment.model = model
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java b/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
deleted file mode 100644
index f192b82e4..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.account;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.os.Bundle;
-import android.text.InputFilter;
-import android.text.TextWatcher;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.FragRegisterNameBinding;
-import net.jami.services.AccountService;
-import cx.ring.utils.RegisteredNameFilter;
-import cx.ring.utils.RegisteredNameTextWatcher;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.disposables.Disposable;
-
-public class RegisterNameDialog extends DialogFragment {
-    static final String TAG = RegisterNameDialog.class.getSimpleName();
-    @Inject
-    AccountService mAccountService;
-    @Inject
-    Scheduler mUiScheduler;
-
-    private TextWatcher mUsernameTextWatcher;
-    private RegisterNameDialogListener mListener = null;
-
-    private Disposable mDisposableListener;
-    private FragRegisterNameBinding binding;
-
-    public void setListener(RegisterNameDialogListener l) {
-        mListener = l;
-    }
-
-    private void onLookupResult(final int state, final String name) {
-        CharSequence actualName = binding.ringUsername.getText();
-        if (actualName == null || actualName.length() == 0) {
-            binding.ringUsernameTxtBox.setErrorEnabled(false);
-            binding.ringUsernameTxtBox.setError(null);
-            return;
-        }
-
-        if (name.contentEquals(actualName)) {
-            switch (state) {
-                case 0:
-                    // on found
-                    binding.ringUsernameTxtBox.setErrorEnabled(true);
-                    binding.ringUsernameTxtBox.setError(getText(R.string.username_already_taken));
-                    break;
-                case 1:
-                    // invalid name
-                    binding.ringUsernameTxtBox.setErrorEnabled(true);
-                    binding.ringUsernameTxtBox.setError(getText(R.string.invalid_username));
-                    break;
-                default:
-                    // on error
-                    binding.ringUsernameTxtBox.setErrorEnabled(false);
-                    binding.ringUsernameTxtBox.setError(null);
-                    break;
-            }
-        }
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        binding = FragRegisterNameBinding.inflate(getActivity().getLayoutInflater());
-        View view = binding.getRoot();
-
-        // dependency injection
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-
-        String accountId = "";
-        boolean hasPassword = true;
-        Bundle args = getArguments();
-        if (args != null) {
-            accountId = args.getString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId);
-            hasPassword = args.getBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, true);
-        }
-
-        mUsernameTextWatcher = new RegisteredNameTextWatcher(getActivity(), mAccountService, accountId, binding.ringUsernameTxtBox, binding.ringUsername);
-        binding.ringUsername.setFilters(new InputFilter[]{new RegisteredNameFilter()});
-        binding.ringUsername.addTextChangedListener(mUsernameTextWatcher);
-        // binding.ringUsername.setOnEditorActionListener((v, actionId, event) -> RegisterNameDialog.this.onEditorAction(v, actionId));
-
-        binding.passwordTxtBox.setVisibility(hasPassword ? View.VISIBLE : View.GONE);
-        binding.passwordTxt.setOnEditorActionListener((v, actionId, event) -> RegisterNameDialog.this.onEditorAction(v, actionId));
-
-        AlertDialog dialog = (AlertDialog) getDialog();
-        if (dialog != null) {
-            dialog.setView(view);
-            dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
-        }
-
-        AlertDialog result = new MaterialAlertDialogBuilder(requireContext())
-                .setView(view)
-                .setMessage(R.string.register_username)
-                .setTitle(R.string.register_name)
-                .setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick
-                .setNegativeButton(android.R.string.cancel, (d, b) -> dismiss())
-                .create();
-
-        result.setOnShowListener(d -> {
-            Button positiveButton = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
-            positiveButton.setOnClickListener(view1 -> {
-                if (validate()) {
-                    dismiss();
-                }
-            });
-        });
-
-        return result;
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        if (binding != null) {
-            binding.ringUsername.addTextChangedListener(mUsernameTextWatcher);
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        mDisposableListener = mAccountService
-                .getRegisteredNames()
-                .observeOn(mUiScheduler)
-                .subscribe(r -> onLookupResult(r.state, r.name));
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        mDisposableListener.dispose();
-    }
-
-    @Override
-    public void onDetach() {
-        if (binding != null) {
-            binding.ringUsername.removeTextChangedListener(mUsernameTextWatcher);
-        }
-        super.onDetach();
-    }
-
-    private boolean isValidUsername() {
-        return binding.ringUsernameTxtBox.getError() == null;
-    }
-
-    private boolean checkInput() {
-        if (binding.ringUsername.getText() == null || binding.ringUsername.getText().length() == 0) {
-            binding.ringUsernameTxtBox.setErrorEnabled(true);
-            binding.ringUsernameTxtBox.setError(getText(R.string.prompt_new_username));
-            return false;
-        }
-
-        if (!isValidUsername()) {
-            binding.ringUsername.requestFocus();
-            return false;
-        }
-
-        binding.ringUsernameTxtBox.setErrorEnabled(false);
-        binding.ringUsernameTxtBox.setError(null);
-
-        if (binding.passwordTxtBox.getVisibility() == View.VISIBLE) {
-            if (binding.passwordTxt.getText() == null || binding.passwordTxt.getText().length() == 0) {
-                binding.passwordTxtBox.setErrorEnabled(true);
-                binding.passwordTxtBox.setError(getString(R.string.prompt_password));
-                return false;
-            } else {
-                binding.passwordTxtBox.setErrorEnabled(false);
-                binding.passwordTxtBox.setError(null);
-            }
-        }
-        return true;
-    }
-
-    private boolean validate() {
-        if (checkInput() && mListener != null) {
-            final String username = binding.ringUsername.getText().toString();
-            final String password = binding.passwordTxt.getText().toString();
-            mListener.onRegisterName(username, password);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean onEditorAction(TextView v, int actionId) {
-        if (v == binding.passwordTxt) {
-            if (actionId == EditorInfo.IME_ACTION_DONE) {
-                boolean validationResult = validate();
-                if (validationResult) {
-                    Dialog dialog = getDialog();
-                    if (dialog != null)
-                        dialog.dismiss();
-                }
-
-                return validationResult;
-            }
-        }
-        return false;
-    }
-
-    public interface RegisterNameDialogListener {
-        void onRegisterName(String name, String password);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.kt b/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.kt
new file mode 100644
index 000000000..887db2ab0
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/RegisterNameDialog.kt
@@ -0,0 +1,218 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.account
+
+import android.app.Dialog
+import android.content.Context
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.InputFilter
+import android.text.TextWatcher
+import android.view.KeyEvent
+import android.view.View
+import android.view.WindowManager
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.databinding.FragRegisterNameBinding
+import cx.ring.utils.RegisteredNameFilter
+import cx.ring.utils.RegisteredNameTextWatcher
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.Disposable
+import net.jami.services.AccountService
+import net.jami.services.AccountService.RegisteredName
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class RegisterNameDialog : DialogFragment() {
+    @Inject
+    lateinit var mAccountService: AccountService
+
+    private var mUsernameTextWatcher: TextWatcher? = null
+    private var mListener: RegisterNameDialogListener? = null
+    private var mDisposableListener: Disposable? = null
+    private var binding: FragRegisterNameBinding? = null
+    fun setListener(l: RegisterNameDialogListener?) {
+        mListener = l
+    }
+
+    private fun onLookupResult(state: Int, name: String) {
+        binding?.let { binding ->
+            val actualName: CharSequence = binding.ringUsername.text!!
+            if (actualName.isEmpty()) {
+                binding.ringUsernameTxtBox.isErrorEnabled = false
+                binding.ringUsernameTxtBox.error = null
+                return
+            }
+            if (name.contentEquals(actualName)) {
+                when (state) {
+                    0 -> {
+                        // on found
+                        binding.ringUsernameTxtBox.isErrorEnabled = true
+                        binding.ringUsernameTxtBox.error = getText(R.string.username_already_taken)
+                    }
+                    1 -> {
+                        // invalid name
+                        binding.ringUsernameTxtBox.isErrorEnabled = true
+                        binding.ringUsernameTxtBox.error = getText(R.string.invalid_username)
+                    }
+                    else -> {
+                        // on error
+                        binding.ringUsernameTxtBox.isErrorEnabled = false
+                        binding.ringUsernameTxtBox.error = null
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        binding = FragRegisterNameBinding.inflate(layoutInflater)
+        val view: View = binding!!.root
+        var accountId = ""
+        var hasPassword = true
+        val args = arguments
+        if (args != null) {
+            accountId = args.getString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId)
+            hasPassword = args.getBoolean(AccountEditionFragment.ACCOUNT_HAS_PASSWORD_KEY, true)
+        }
+        mUsernameTextWatcher = RegisteredNameTextWatcher(
+            requireContext(),
+            mAccountService,
+            accountId,
+            binding!!.ringUsernameTxtBox,
+            binding!!.ringUsername
+        )
+        binding!!.ringUsername.filters = arrayOf<InputFilter>(RegisteredNameFilter())
+        binding!!.ringUsername.addTextChangedListener(mUsernameTextWatcher)
+        // binding.ringUsername.setOnEditorActionListener((v, actionId, event) -> RegisterNameDialog.this.onEditorAction(v, actionId));
+        binding!!.passwordTxtBox.visibility = if (hasPassword) View.VISIBLE else View.GONE
+        binding!!.passwordTxt.setOnEditorActionListener { v: TextView, actionId: Int, event: KeyEvent? ->
+            onEditorAction(v, actionId)
+        }
+        val dialog = dialog as AlertDialog?
+        if (dialog != null) {
+            dialog.setView(view)
+            dialog.window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+        }
+        val result = MaterialAlertDialogBuilder(requireContext())
+            .setView(view)
+            .setMessage(R.string.register_username)
+            .setTitle(R.string.register_name)
+            .setPositiveButton(android.R.string.ok, null) //Set to null. We override the onclick
+            .setNegativeButton(android.R.string.cancel) { d: DialogInterface?, b: Int -> dismiss() }
+            .create()
+        result.setOnShowListener { d: DialogInterface ->
+            val positiveButton = (d as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
+            positiveButton.setOnClickListener {
+                if (validate()) {
+                    dismiss()
+                }
+            }
+        }
+        return result
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        if (binding != null) {
+            binding!!.ringUsername.addTextChangedListener(mUsernameTextWatcher)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        mDisposableListener = mAccountService.registeredNames
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { r: RegisteredName -> onLookupResult(r.state, r.name) }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        mDisposableListener!!.dispose()
+    }
+
+    override fun onDetach() {
+        if (binding != null) {
+            binding!!.ringUsername.removeTextChangedListener(mUsernameTextWatcher)
+        }
+        super.onDetach()
+    }
+
+    private val isValidUsername: Boolean
+        get() = binding!!.ringUsernameTxtBox.error == null
+
+    private fun checkInput(): Boolean {
+        binding?.let { binding ->
+            if (binding.ringUsername.text == null || binding.ringUsername.text!!.isEmpty()) {
+                binding.ringUsernameTxtBox.isErrorEnabled = true
+                binding.ringUsernameTxtBox.error = getText(R.string.prompt_new_username)
+                return false
+            }
+            if (!isValidUsername) {
+                binding.ringUsername.requestFocus()
+                return false
+            }
+            binding.ringUsernameTxtBox.isErrorEnabled = false
+            binding.ringUsernameTxtBox.error = null
+            if (binding.passwordTxtBox.visibility == View.VISIBLE) {
+                if (binding.passwordTxt.text == null || binding.passwordTxt.text!!.isEmpty()) {
+                    binding.passwordTxtBox.isErrorEnabled = true
+                    binding.passwordTxtBox.error = getString(R.string.prompt_password)
+                    return false
+                } else {
+                    binding.passwordTxtBox.isErrorEnabled = false
+                    binding.passwordTxtBox.error = null
+                }
+            }
+        }
+        return true
+    }
+
+    private fun validate(): Boolean {
+        if (checkInput() && mListener != null) {
+            val username = binding!!.ringUsername.text!!.toString()
+            val password = binding!!.passwordTxt.text!!.toString()
+            mListener!!.onRegisterName(username, password)
+            return true
+        }
+        return false
+    }
+
+    private fun onEditorAction(v: TextView, actionId: Int): Boolean {
+        if (v === binding?.passwordTxt) {
+            if (actionId == EditorInfo.IME_ACTION_DONE) {
+                val validationResult = validate()
+                if (validationResult) {
+                    dialog?.dismiss()
+                }
+                return validationResult
+            }
+        }
+        return false
+    }
+
+    interface RegisterNameDialogListener {
+        fun onRegisterName(name: String?, password: String?)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
deleted file mode 100644
index 19b830984..000000000
--- a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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.account;
-
-import android.app.Dialog;
-import android.os.Bundle;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-
-import android.view.WindowManager;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-
-import androidx.fragment.app.DialogFragment;
-import cx.ring.R;
-import cx.ring.databinding.DialogDeviceRenameBinding;
-
-public class RenameDeviceDialog extends DialogFragment {
-    public static final String DEVICENAME_KEY = "devicename_key";
-    static final String TAG = RenameDeviceDialog.class.getSimpleName();
-    private RenameDeviceListener mListener = null;
-    private DialogDeviceRenameBinding binding;
-
-    public void setListener(RenameDeviceListener listener) {
-        mListener = listener;
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        binding = DialogDeviceRenameBinding.inflate(getActivity().getLayoutInflater());
-
-        binding.ringDeviceNameTxt.setText(getArguments().getString(DEVICENAME_KEY));
-        binding.ringDeviceNameTxt.setOnEditorActionListener((v, actionId, event) -> {
-            if (actionId == EditorInfo.IME_ACTION_DONE) {
-                boolean validationResult = validate();
-                if (validationResult) {
-                    getDialog().dismiss();
-                }
-                return validationResult;
-            }
-            return false;
-        });
-
-        final AlertDialog dialog = new MaterialAlertDialogBuilder(requireContext())
-                .setView(binding.getRoot())
-                .setTitle(R.string.rename_device_title)
-                .setMessage(R.string.rename_device_message)
-                .setPositiveButton(R.string.rename_device_button, null)
-                .setNegativeButton(android.R.string.cancel, null)
-                .create();
-        dialog.setOnShowListener(dialog1 -> {
-            Button button = ((AlertDialog) dialog1).getButton(AlertDialog.BUTTON_POSITIVE);
-            button.setOnClickListener(view1 -> {
-                if (validate()) {
-                    dialog1.dismiss();
-                }
-            });
-        });
-        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
-        return dialog;
-    }
-
-    @Override
-    public void onDestroy() {
-        mListener = null;
-        super.onDestroy();
-    }
-
-    private boolean checkInput(String input) {
-        if (input.isEmpty()) {
-            binding.ringDeviceNameTxtBox.setErrorEnabled(true);
-            binding.ringDeviceNameTxtBox.setError(getText(R.string.account_device_name_empty));
-            return false;
-        } else {
-            binding.ringDeviceNameTxtBox.setErrorEnabled(false);
-            binding.ringDeviceNameTxtBox.setError(null);
-        }
-        return true;
-    }
-
-    private boolean validate() {
-        String input = binding.ringDeviceNameTxt.getText().toString().trim();
-        if (checkInput(input) && mListener != null) {
-            mListener.onDeviceRename(input);
-            return true;
-        }
-        return false;
-    }
-
-    public interface RenameDeviceListener {
-        void onDeviceRename(String newName);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt
new file mode 100644
index 000000000..2eab16808
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt
@@ -0,0 +1,111 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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.account
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.WindowManager
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.databinding.DialogDeviceRenameBinding
+
+class RenameDeviceDialog : DialogFragment() {
+    private var mListener: RenameDeviceListener? = null
+    private var binding: DialogDeviceRenameBinding? = null
+    fun setListener(listener: RenameDeviceListener?) {
+        mListener = listener
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        binding = DialogDeviceRenameBinding.inflate(layoutInflater)
+        binding!!.ringDeviceNameTxt.setText(requireArguments().getString(DEVICENAME_KEY))
+        binding!!.ringDeviceNameTxt.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? ->
+            if (actionId == EditorInfo.IME_ACTION_DONE) {
+                val validationResult = validate()
+                if (validationResult) {
+                    requireDialog().dismiss()
+                }
+                return@setOnEditorActionListener validationResult
+            }
+            false
+        }
+        return MaterialAlertDialogBuilder(requireContext())
+            .setView(binding!!.root)
+            .setTitle(R.string.rename_device_title)
+            .setMessage(R.string.rename_device_message)
+            .setPositiveButton(R.string.rename_device_button, null)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
+            .apply {
+                setOnShowListener { d: DialogInterface ->
+                    val button = (d as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
+                    button.setOnClickListener {
+                        if (validate()) {
+                            d.dismiss()
+                        }
+                    }
+                }
+                window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
+            }
+    }
+
+    override fun onDestroy() {
+        mListener = null
+        super.onDestroy()
+    }
+
+    private fun checkInput(input: String): Boolean {
+        if (input.isEmpty()) {
+            binding?.apply {
+                ringDeviceNameTxtBox.isErrorEnabled = true
+                ringDeviceNameTxtBox.error = getText(R.string.account_device_name_empty)
+            }
+            return false
+        } else {
+            binding?.apply {
+                ringDeviceNameTxtBox.isErrorEnabled = false
+                ringDeviceNameTxtBox.error = null
+            }
+        }
+        return true
+    }
+
+    private fun validate(): Boolean {
+        val input = binding!!.ringDeviceNameTxt.text.toString().trim { it <= ' ' }
+        if (checkInput(input) && mListener != null) {
+            mListener!!.onDeviceRename(input)
+            return true
+        }
+        return false
+    }
+
+    interface RenameDeviceListener {
+        fun onDeviceRename(newName: String?)
+    }
+
+    companion object {
+        const val DEVICENAME_KEY = "devicename_key"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java
deleted file mode 100644
index 74ae2720c..000000000
--- a/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.adapters;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.DiffUtil;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.List;
-
-import cx.ring.fragments.CallFragment;
-import cx.ring.views.AvatarDrawable;
-import cx.ring.views.AvatarFactory;
-import cx.ring.databinding.ItemConferenceParticipantBinding;
-
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Call;
-import cx.ring.views.ParticipantView;
-
-public class ConfParticipantAdapter extends RecyclerView.Adapter<ParticipantView> {
-    protected final ConfParticipantAdapter.ConfParticipantSelected onSelectedCallback;
-    private List<Conference.ParticipantInfo> calls = null;
-
-    public ConfParticipantAdapter(@NonNull ConfParticipantSelected cb) {
-        onSelectedCallback = cb;
-    }
-
-    @NonNull
-    @Override
-    public ParticipantView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return new ParticipantView(ItemConferenceParticipantBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull ParticipantView holder, int position) {
-        final Conference.ParticipantInfo info = calls.get(position);
-        final Contact contact = info.contact;
-
-        final Context context = holder.itemView.getContext();
-        if (info.call != null && info.call.getCallStatus() != Call.CallStatus.CURRENT)  {
-            holder.binding.displayName.setText(String.format("%s\n%s", contact.getDisplayName(), context.getText(CallFragment.callStateToHumanState(info.call.getCallStatus()))));
-            holder.binding.photo.setAlpha(.5f);
-        } else {
-            holder.binding.displayName.setText(contact.getDisplayName());
-            holder.binding.photo.setAlpha(1f);
-        }
-
-        if (holder.disposable != null)
-            holder.disposable.dispose();
-
-        holder.binding.photo.setImageDrawable(new AvatarDrawable.Builder()
-                .withContact(contact)
-                .withCircleCrop(true)
-                .withPresence(false)
-                .build(context));
-        /*;
-        holder.disposable = AvatarFactory.getAvatar(context, contact)
-                .subscribe(holder.binding.photo::setImageDrawable);*/
-        holder.itemView.setOnClickListener(view -> onSelectedCallback.onParticipantSelected(view, info));
-    }
-
-    @Override
-    public int getItemCount() {
-        return calls == null ? 0 : calls.size();
-    }
-
-    public void updateFromCalls(@NonNull final List<Conference.ParticipantInfo> contacts) {
-        final List<Conference.ParticipantInfo> oldCalls = calls;
-        calls = contacts;
-        if (oldCalls != null) {
-            DiffUtil.calculateDiff(new DiffUtil.Callback() {
-                @Override
-                public int getOldListSize() {
-                    return oldCalls.size();
-                }
-
-                @Override
-                public int getNewListSize() {
-                    return contacts.size();
-                }
-
-                @Override
-                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-                    return oldCalls.get(oldItemPosition) == contacts.get(newItemPosition);
-                }
-
-                @Override
-                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-                    return false;
-                }
-            }).dispatchUpdatesTo(this);
-        } else {
-            notifyDataSetChanged();
-        }
-    }
-
-    public interface ConfParticipantSelected {
-        void onParticipantSelected(View view, Conference.ParticipantInfo contact);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.kt b/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.kt
new file mode 100644
index 000000000..dfb49a492
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/adapters/ConfParticipantAdapter.kt
@@ -0,0 +1,105 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.databinding.ItemConferenceParticipantBinding
+import cx.ring.fragments.CallFragment
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.ParticipantView
+import net.jami.model.Call
+import net.jami.model.Conference.ParticipantInfo
+
+class ConfParticipantAdapter(private val onSelectedCallback: ConfParticipantSelected) :
+    RecyclerView.Adapter<ParticipantView>() {
+    private var calls: List<ParticipantInfo>? = null
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParticipantView {
+        return ParticipantView(ItemConferenceParticipantBinding.inflate(LayoutInflater.from(parent.context), parent, false))
+    }
+
+    override fun onBindViewHolder(holder: ParticipantView, position: Int) {
+        val info = calls!![position]
+        val contact = info.contact
+        val context = holder.itemView.context
+        val call = info.call
+        if (call != null && call.callStatus != Call.CallStatus.CURRENT) {
+            holder.binding.displayName.text = String.format("%s\n%s", contact.displayName, context.getText(CallFragment.callStateToHumanState(call.callStatus)))
+            holder.binding.photo.alpha = .5f
+        } else {
+            holder.binding.displayName.text = contact.displayName
+            holder.binding.photo.alpha = 1f
+        }
+        if (holder.disposable != null) holder.disposable.dispose()
+        holder.binding.photo.setImageDrawable(
+            AvatarDrawable.Builder()
+                .withContact(contact)
+                .withCircleCrop(true)
+                .withPresence(false)
+                .build(context)
+        )
+        /*;
+        holder.disposable = AvatarFactory.getAvatar(context, contact)
+                .subscribe(holder.binding.photo::setImageDrawable);*/
+        holder.itemView.setOnClickListener { view: View ->
+            onSelectedCallback.onParticipantSelected(view, info)
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return if (calls == null) 0 else calls!!.size
+    }
+
+    fun updateFromCalls(contacts: List<ParticipantInfo>) {
+        val oldCalls = calls
+        calls = contacts
+        if (oldCalls != null) {
+            DiffUtil.calculateDiff(object : DiffUtil.Callback() {
+                override fun getOldListSize(): Int {
+                    return oldCalls.size
+                }
+
+                override fun getNewListSize(): Int {
+                    return contacts.size
+                }
+
+                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+                    return oldCalls[oldItemPosition] === contacts[newItemPosition]
+                }
+
+                override fun areContentsTheSame(
+                    oldItemPosition: Int,
+                    newItemPosition: Int
+                ): Boolean {
+                    return false
+                }
+            }).dispatchUpdatesTo(this)
+        } else {
+            notifyDataSetChanged()
+        }
+    }
+
+    interface ConfParticipantSelected {
+        fun onParticipantSelected(view: View, contact: ParticipantInfo)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
deleted file mode 100644
index f0400896a..000000000
--- a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java
+++ /dev/null
@@ -1,1246 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors:    Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *              Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.adapters;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.SurfaceTexture;
-import android.graphics.drawable.Drawable;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.Build;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.Surface;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import androidx.annotation.LayoutRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.cardview.widget.CardView;
-import androidx.core.app.ActivityOptionsCompat;
-import androidx.core.content.ContextCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
-import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
-
-import com.bumptech.glide.load.resource.bitmap.CenterInside;
-import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
-import com.bumptech.glide.request.target.DrawableImageViewTarget;
-
-import net.jami.conversation.ConversationPresenter;
-import net.jami.model.Account;
-import net.jami.model.Call;
-import net.jami.model.Contact;
-import net.jami.model.ContactEvent;
-import net.jami.model.DataTransfer;
-import net.jami.model.Interaction;
-import net.jami.model.Interaction.InteractionStatus;
-import net.jami.model.Interaction.InteractionType;
-import net.jami.model.TextMessage;
-import net.jami.utils.StringUtils;
-
-import java.io.File;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
-
-import cx.ring.R;
-import cx.ring.client.MediaViewerActivity;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.GlideApp;
-import cx.ring.utils.GlideOptions;
-import cx.ring.utils.ResourceMapper;
-import cx.ring.views.ConversationViewHolder;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-
-public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHolder> {
-    private final static String TAG = ConversationAdapter.class.getSimpleName();
-
-    private final ArrayList<Interaction> mInteractions = new ArrayList<>();
-
-    private final ConversationPresenter presenter;
-    private final ConversationFragment conversationFragment;
-    private final int hPadding;
-    private final int vPadding;
-    private final int mPictureMaxSize;
-    private final GlideOptions PICTURE_OPTIONS;
-    private RecyclerViewContextMenuInfo mCurrentLongItem = null;
-    private int convColor = 0;
-
-    private int expandedItemPosition = -1;
-    private int lastDeliveredPosition = -1;
-    private int lastDisplayedPosition = -1;
-    private final Observable<Long> timestampUpdateTimer;
-    private int lastMsgPos = -1;
-
-    private boolean isComposing = false;
-    private boolean mShowReadIndicator = true;
-
-    private static final int[] msgBGLayouts = new int[] {
-            R.drawable.textmsg_bg_out_first,
-            R.drawable.textmsg_bg_out_middle,
-            R.drawable.textmsg_bg_out_last,
-            R.drawable.textmsg_bg_out,
-            R.drawable.textmsg_bg_in_first,
-            R.drawable.textmsg_bg_in_middle,
-            R.drawable.textmsg_bg_in_last,
-            R.drawable.textmsg_bg_in
-    };
-
-    public ConversationAdapter(ConversationFragment fragment, ConversationPresenter p) {
-        conversationFragment = fragment;
-        presenter = p;
-        Resources res = conversationFragment.getResources();
-        hPadding = res.getDimensionPixelSize(R.dimen.padding_medium);
-        vPadding = res.getDimensionPixelSize(R.dimen.padding_small);
-        mPictureMaxSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, res.getDisplayMetrics());
-        int corner = (int) res.getDimension(R.dimen.conversation_message_radius);
-        PICTURE_OPTIONS = new GlideOptions()
-                .transform(new CenterInside())
-                .fitCenter()
-                .override(mPictureMaxSize)
-                .transform(new RoundedCorners(corner));
-        timestampUpdateTimer = Observable.interval(10, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
-                .startWithItem(0L);
-    }
-
-    /**
-     * Refreshes the data and notifies the changes
-     *
-     * @param list an arraylist of interactions
-     */
-    public void updateDataset(final List<Interaction> list) {
-        Log.d(TAG, "updateDataset: list size=" + list.size());
-        if (mInteractions.isEmpty()) {
-            mInteractions.addAll(list);
-        } else if (list.size() > mInteractions.size()) {
-            mInteractions.addAll(list.subList(mInteractions.size(), list.size()));
-        } else {
-            mInteractions.clear();
-            mInteractions.addAll(list);
-        }
-        notifyDataSetChanged();
-    }
-
-    public boolean add(Interaction e) {
-        if (!TextUtils.isEmpty(e.getMessageId())) {
-            if (mInteractions.isEmpty() || e.getParentIds().contains(mInteractions.get(mInteractions.size()-1).getMessageId())) {
-                boolean update = !mInteractions.isEmpty();
-                mInteractions.add(e);
-                notifyItemInserted(mInteractions.size()-1);
-                if (update)
-                    notifyItemChanged(mInteractions.size()-2);
-                return true;
-            }
-            for (int i = 0, n = mInteractions.size(); i<n; i++) {
-                if (mInteractions.get(i).getParentIds().contains(e.getMessageId())) {
-                    Log.w(TAG, "Adding message at " + i + " previous count " + n);
-                    mInteractions.add(i, e);
-                    notifyItemInserted(i);
-                    return i == n-1;
-                }
-            }
-        } else {
-            boolean update = !mInteractions.isEmpty();
-            mInteractions.add(e);
-            notifyItemInserted(mInteractions.size() - 1);
-            if (update)
-                notifyItemChanged(mInteractions.size() - 2);
-        }
-        return true;
-    }
-
-    public void update(Interaction e) {
-        Log.w(TAG, "update " + e.getMessageId());
-        if (!e.isIncoming() && e.getStatus() == InteractionStatus.SUCCESS) {
-            notifyItemChanged(lastDeliveredPosition);
-        }
-        for (int i = mInteractions.size() - 1; i >= 0; i--) {
-            Interaction element = mInteractions.get(i);
-            if (e == element) {
-                notifyItemChanged(i);
-                break;
-            }
-        }
-    }
-
-    public void remove(Interaction e) {
-        if (e.getMessageId() != null) {
-            for (int i = mInteractions.size() - 1; i >= 0; i--) {
-                if (e.getMessageId().equals(mInteractions.get(i).getMessageId())) {
-                    mInteractions.remove(i);
-                    notifyItemRemoved(i);
-                    if (i > 0) {
-                        notifyItemChanged(i - 1);
-                    }
-                    if (i != mInteractions.size()) {
-                        notifyItemChanged(i);
-                    }
-                    break;
-                }
-            }
-        } else {
-            for (int i = mInteractions.size() - 1; i >= 0; i--) {
-                if (e.getId() == mInteractions.get(i).getId()) {
-                    mInteractions.remove(i);
-                    notifyItemRemoved(i);
-                    if (i > 0) {
-                        notifyItemChanged(i - 1);
-                    }
-                    if (i != mInteractions.size()) {
-                        notifyItemChanged(i);
-                    }
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Updates the contact photo to use for this conversation
-     */
-    public void setPhoto() {
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public int getItemCount() {
-        return mInteractions.size() + (isComposing ? 1 : 0);
-    }
-
-    @Override
-    public long getItemId(int position) {
-        if (isComposing && position == mInteractions.size())
-            return Long.MAX_VALUE;
-        return mInteractions.get(position).getId();
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (isComposing && position == mInteractions.size())
-            return MessageType.COMPOSING_INDICATION.ordinal();
-
-        Interaction interaction = mInteractions.get(position);
-
-        if (interaction != null) {
-            switch (interaction.getType()) {
-                case CONTACT:
-                    return MessageType.CONTACT_EVENT.ordinal();
-                case CALL:
-                    return MessageType.CALL_INFORMATION.ordinal();
-                case TEXT:
-                    if (interaction.isIncoming()) {
-                        return MessageType.INCOMING_TEXT_MESSAGE.ordinal();
-                    } else {
-                        return MessageType.OUTGOING_TEXT_MESSAGE.ordinal();
-                    }
-                case DATA_TRANSFER:
-                    DataTransfer file = (DataTransfer) interaction;
-                    int out = interaction.isIncoming() ? 0 : 4;
-                    if (file.isComplete()) {
-                        if (file.isPicture()) {
-                            return MessageType.INCOMING_IMAGE.ordinal() + out;
-                        } else if (file.isAudio()) {
-                            return MessageType.INCOMING_AUDIO.ordinal() + out;
-                        } else if (file.isVideo()) {
-                            return MessageType.INCOMING_VIDEO.ordinal() + out;
-                        }
-                    }
-                    return out;
-                case INVALID:
-                    return MessageType.INVALID.ordinal();
-            }
-        }
-        return -1;
-    }
-
-    @NonNull
-    @Override
-    public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        MessageType type = MessageType.values()[viewType];
-        ViewGroup v = type == MessageType.INVALID ? new FrameLayout(parent.getContext()) : (ViewGroup) LayoutInflater.from(parent.getContext()).inflate(type.layout, parent, false);
-        return new ConversationViewHolder(v, type);
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull ConversationViewHolder conversationViewHolder, int position) {
-        if (isComposing && position == mInteractions.size()) {
-            configureForTypingIndicator(conversationViewHolder);
-            return;
-        }
-
-        Interaction interaction = mInteractions.get(position);
-        if (interaction == null)
-            return;
-
-        conversationViewHolder.compositeDisposable.clear();
-
-        if (position > lastMsgPos) {
-            lastMsgPos = position;
-            Animation animation = AnimationUtils.loadAnimation(
-                    conversationViewHolder.itemView.getContext(), R.anim.fade_in);
-            animation.setStartOffset(150);
-            conversationViewHolder.itemView.startAnimation(animation);
-        }
-
-        //Log.w(TAG, "onBindViewHolder " + interaction.getType() + " " + interaction);
-        if (interaction.getType() == InteractionType.INVALID) {
-            conversationViewHolder.itemView.setVisibility(View.GONE);
-        } else {
-            conversationViewHolder.itemView.setVisibility(View.VISIBLE);
-            if (interaction.getType() == InteractionType.TEXT) {
-                configureForTextMessage(conversationViewHolder, interaction, position);
-            } else if (interaction.getType() == InteractionType.CALL) {
-                configureForCallInfo(conversationViewHolder, interaction);
-            } else if (interaction.getType() == InteractionType.CONTACT) {
-                configureForContactEvent(conversationViewHolder, interaction);
-            } else if (interaction.getType() == InteractionType.DATA_TRANSFER) {
-                configureForFileInfo(conversationViewHolder, interaction, position);
-            }
-        }
-    }
-
-    @Override
-    public void onViewRecycled(@NonNull ConversationViewHolder holder) {
-        holder.itemView.setOnLongClickListener(null);
-        if (holder.mImage != null) {
-            holder.mImage.setOnLongClickListener(null);
-        }
-        if (holder.video != null) {
-            holder.video.setOnClickListener(null);
-            holder.video.setSurfaceTextureListener(null);
-        }
-        if (holder.surface != null) {
-            holder.surface.release();
-            holder.surface = null;
-        }
-        if (holder.player != null) {
-            try {
-                if (holder.player.isPlaying())
-                    holder.player.stop();
-                holder.player.reset();
-            } catch (Exception e) {
-                // left blank intentionally
-            }
-            holder.player.release();
-            holder.player = null;
-        }
-        if (holder.mMsgTxt != null) {
-            holder.mMsgTxt.setOnLongClickListener(null);
-        }
-        if (holder.mItem != null) {
-            holder.mItem.setOnClickListener(null);
-        }
-        if (expandedItemPosition == holder.getLayoutPosition()) {
-            if (holder.mMsgDetailTxt != null)
-                holder.mMsgDetailTxt.setVisibility(View.GONE);
-            expandedItemPosition = -1;
-        }
-        holder.compositeDisposable.clear();
-    }
-
-    public void setPrimaryColor(int color) {
-        convColor = color;
-        notifyDataSetChanged();
-    }
-
-    public void setComposingStatus(Account.ComposingStatus composingStatus) {
-        boolean composing = composingStatus == Account.ComposingStatus.Active;
-        if (isComposing != composing) {
-            isComposing = composing;
-            if (composing)
-                notifyItemInserted(mInteractions.size());
-            else
-                notifyItemRemoved(mInteractions.size());
-        }
-    }
-
-    public void setReadIndicatorStatus(boolean show) {
-        mShowReadIndicator = show;
-    }
-
-    public void setLastDisplayed(Interaction interaction) {
-        Log.w(TAG, "setLastDisplayed " + interaction.getDaemonId());
-        for (int i = mInteractions.size() - 1; i >= 0; i--) {
-            Interaction element = mInteractions.get(i);
-            if (interaction.getId() == element.getId()) {
-                if (lastDisplayedPosition != -1)
-                    notifyItemChanged(lastDisplayedPosition);
-                lastDisplayedPosition = i;
-                notifyItemChanged(i);
-                Log.w(TAG, "new displayed item " + i);
-                break;
-            }
-        }
-    }
-
-    private static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {
-        RecyclerViewContextMenuInfo(int position, long id) {
-            this.position = position;
-            this.id = id;
-        }
-        final public int position;
-        final public long id;
-    }
-
-    public boolean onContextItemSelected(MenuItem item) {
-        ConversationAdapter.RecyclerViewContextMenuInfo info = mCurrentLongItem;
-        Interaction interaction = null;
-        if (info == null) {
-            return false;
-        }
-        try {
-            interaction = mInteractions.get(info.position);
-        } catch (IndexOutOfBoundsException e) {
-            Log.e(TAG, "Interaction array may be empty or null", e);
-        }
-        if (interaction == null)
-            return false;
-        if (interaction.getType() == (InteractionType.CONTACT))
-            return false;
-
-        int itemId = item.getItemId();
-        if (itemId == R.id.conv_action_download) {
-            presenter.saveFile(interaction);
-        } else if (itemId == R.id.conv_action_share) {
-            presenter.shareFile(interaction);
-        } else if (itemId == R.id.conv_action_open) {
-            presenter.openFile(interaction);
-        } else if (itemId == R.id.conv_action_delete) {
-            presenter.deleteConversationItem(interaction);
-        } else if (itemId == R.id.conv_action_cancel_message) {
-            presenter.cancelMessage(interaction);
-        } else if (itemId == R.id.conv_action_copy_text) {
-            addToClipboard((interaction).getBody());
-        }
-        return true;
-    }
-
-    private void addToClipboard(String text) {
-        android.content.ClipboardManager clipboard = (android.content.ClipboardManager) conversationFragment.requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
-        android.content.ClipData clip = android.content.ClipData.newPlainText("Copied Message", text);
-        clipboard.setPrimaryClip(clip);
-    }
-
-    private void configureImage(@NonNull final ConversationViewHolder viewHolder, @NonNull File path) {
-        Context context = viewHolder.mImage.getContext();
-
-        GlideApp.with(context)
-                .load(path)
-                .apply(PICTURE_OPTIONS)
-                .into(new DrawableImageViewTarget(viewHolder.mImage).waitForLayout());
-
-        viewHolder.mImage.setOnClickListener(v -> {
-            Uri contentUri = ContentUriHandler.getUriForFile(v.getContext(), ContentUriHandler.AUTHORITY_FILES, path);
-            Intent i = new Intent(context, MediaViewerActivity.class)
-                    .setAction(Intent.ACTION_VIEW)
-                    .setDataAndType(contentUri, "image/*")
-                    .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            ActivityOptionsCompat options = ActivityOptionsCompat.
-                    makeSceneTransitionAnimation(conversationFragment.getActivity(), viewHolder.mImage, "picture");
-            conversationFragment.startActivityForResult(i, 3006, options.toBundle());
-        });
-    }
-
-    private void configureAudio(@NonNull final ConversationViewHolder viewHolder, @NonNull File path) {
-        Context context = viewHolder.itemView.getContext();
-        try {
-            ((ImageView) viewHolder.btnAccept).setImageResource(R.drawable.baseline_play_arrow_24);
-            final MediaPlayer player = MediaPlayer.create(context,
-                    ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, path));
-            viewHolder.player = player;
-            if (player != null) {
-                player.setOnCompletionListener(mp -> {
-                    player.seekTo(0);
-                    ((ImageView) viewHolder.btnAccept).setImageResource(R.drawable.baseline_play_arrow_24);
-                });
-                viewHolder.btnAccept.setOnClickListener((b) -> {
-                    if (player.isPlaying()) {
-                        player.pause();
-                        ((ImageView) viewHolder.btnAccept).setImageResource(R.drawable.baseline_play_arrow_24);
-                    } else {
-                        player.start();
-                        ((ImageView) viewHolder.btnAccept).setImageResource(R.drawable.baseline_pause_24);
-                    }
-                });
-                viewHolder.btnRefuse.setOnClickListener((b) -> {
-                    if (player.isPlaying())
-                        player.pause();
-                    player.seekTo(0);
-                    ((ImageView) viewHolder.btnAccept).setImageResource(R.drawable.baseline_play_arrow_24);
-                });
-                viewHolder.compositeDisposable.add(Observable.interval(1L, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
-                        .startWithItem(0L)
-                        .subscribe(t -> {
-                            int pS = player.getCurrentPosition() / 1000;
-                            int dS = player.getDuration() / 1000;
-                            viewHolder.mMsgTxt.setText(String.format(Locale.getDefault(),
-                                    "%02d:%02d / %02d:%02d", pS / 60, pS % 60, dS / 60, dS % 60));
-                        }));
-            } else {
-                viewHolder.btnAccept.setOnClickListener(null);
-                viewHolder.btnRefuse.setOnClickListener(null);
-            }
-        } catch (IllegalStateException | NullPointerException e) {
-            Log.e(TAG, "Error initializing player, it may have already been released: " + e.getMessage());
-        }
-    }
-
-    private void configureVideo(@NonNull final ConversationViewHolder viewHolder, @NonNull File path) {
-        Context context = viewHolder.itemView.getContext();
-        if (viewHolder.player != null) {
-            viewHolder.player.release();
-            viewHolder.player = null;
-        }
-        final MediaPlayer player = MediaPlayer.create(context,
-                ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, path));
-        if (player == null)
-            return;
-        viewHolder.player = player;
-        final Drawable playBtn = ContextCompat.getDrawable(viewHolder.mLayout.getContext(), R.drawable.baseline_play_arrow_24).mutate();
-        DrawableCompat.setTint(playBtn, Color.WHITE);
-        ((CardView) viewHolder.mLayout).setForeground(playBtn);
-        player.setOnCompletionListener(mp -> {
-            if (player.isPlaying())
-                player.pause();
-            player.seekTo(1);
-            ((CardView) viewHolder.mLayout).setForeground(playBtn);
-        });
-        player.setOnVideoSizeChangedListener((mp, width, height) -> {
-            Log.w(TAG, "OnVideoSizeChanged " + width + "x" + height);
-            FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) viewHolder.video.getLayoutParams();
-            int maxDim = Math.max(width, height);
-            p.width = width * mPictureMaxSize / maxDim;
-            p.height = height * mPictureMaxSize / maxDim;
-            viewHolder.video.setLayoutParams(p);
-        });
-        if (viewHolder.video.isAvailable()) {
-            if (viewHolder.surface == null) {
-                viewHolder.surface = new Surface(viewHolder.video.getSurfaceTexture());
-            }
-            player.setSurface(viewHolder.surface);
-        }
-        viewHolder.video.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
-            @Override
-            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-                if (viewHolder.surface == null) {
-                    viewHolder.surface = new Surface(surface);
-                    try {
-                        player.setSurface(viewHolder.surface);
-                    } catch (Exception e) {
-                        // Left blank
-                    }
-                }
-            }
-
-            @Override
-            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-            }
-
-            @Override
-            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-                try {
-                    player.setSurface(null);
-                } catch (Exception e) {
-                    // Left blank
-                }
-                player.release();
-                if (viewHolder.surface != null) {
-                    viewHolder.surface.release();
-                    viewHolder.surface = null;
-                }
-                return true;
-            }
-
-            @Override
-            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-            }
-        });
-        viewHolder.video.setOnClickListener(v -> {
-            try {
-                if (player.isPlaying()) {
-                    player.pause();
-                    ((CardView) viewHolder.mLayout).setForeground(playBtn);
-                } else {
-                    player.start();
-                    ((CardView) viewHolder.mLayout).setForeground(null);
-                }
-            } catch (Exception e) {
-                // Left blank
-            }
-        });
-        player.seekTo(1);
-    }
-
-    private void configureForFileInfo(@NonNull final ConversationViewHolder viewHolder,
-                                      @NonNull final Interaction interaction, int position) {
-        DataTransfer file = (DataTransfer) interaction;
-
-        File path = presenter.getDeviceRuntimeService().getConversationPath(file);
-        if (file.isComplete())
-            file.setSize(path.length());
-        String timeString = timestampToDetailString(viewHolder.itemView.getContext(), file.getTimestamp());
-        viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(time -> {
-            InteractionStatus status = file.getStatus();
-            if (status == InteractionStatus.TRANSFER_FINISHED) {
-                viewHolder.mMsgDetailTxt.setText(String.format("%s - %s",
-                        timeString, Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize())));
-            } else if (status == InteractionStatus.TRANSFER_ONGOING) {
-                viewHolder.mMsgDetailTxt.setText(String.format("%s / %s - %s",
-                        Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getBytesProgress()), Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize()),
-                        ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.getContext(), status)));
-            } else {
-                viewHolder.mMsgDetailTxt.setText(String.format("%s - %s - %s",
-                        timeString, Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize()),
-                        ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.getContext(), status)));
-            }
-        }));
-
-        TransferMsgType type = viewHolder.type.getTransferType();
-        viewHolder.compositeDisposable.clear();
-        if (hasPermanentTimeString(file, position)) {
-            viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(t -> {
-                String timeSeparationString = timestampToDetailString(viewHolder.itemView.getContext(), file.getTimestamp());
-                viewHolder.mMsgDetailTxtPerm.setText(timeSeparationString);
-            }));
-            viewHolder.mMsgDetailTxtPerm.setVisibility(View.VISIBLE);
-        } else {
-            viewHolder.mMsgDetailTxtPerm.setVisibility(View.GONE);
-        }
-
-        Contact contact = interaction.getContact();
-        if (interaction.isIncoming()) {
-            viewHolder.mAvatar.setImageBitmap(null);
-            viewHolder.mAvatar.setVisibility(View.VISIBLE);
-            if (contact != null) {
-                viewHolder.mAvatar.setImageDrawable(conversationFragment.getConversationAvatar(contact.getPrimaryNumber()));
-            }
-        } else {
-            switch (interaction.getStatus()) {
-                case SENDING:
-                    viewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                    viewHolder.mStatusIcon.setImageResource(R.drawable.baseline_circle_24);
-                    break;
-                case FAILURE:
-                    viewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                    viewHolder.mStatusIcon.setImageResource(R.drawable.round_highlight_off_24);
-                    break;
-                case DISPLAYED:
-                    viewHolder.mStatusIcon.setVisibility(mShowReadIndicator ? View.VISIBLE : View.GONE);
-                    viewHolder.mStatusIcon.setImageDrawable(conversationFragment.getSmallConversationAvatar(contact.getPrimaryNumber()));
-                    break;
-                default:
-                    viewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                    viewHolder.mStatusIcon.setImageResource(R.drawable.baseline_check_circle_24);
-                    lastDeliveredPosition = position;
-            }
-        }
-
-        View longPressView = type == TransferMsgType.IMAGE ?
-                viewHolder.mImage : (type == TransferMsgType.VIDEO) ?
-                viewHolder.video : (type == TransferMsgType.AUDIO) ?
-                viewHolder.mAudioInfoLayout : viewHolder.mFileInfoLayout;
-        if (longPressView == null) {
-            return;
-        }
-        if (type == TransferMsgType.AUDIO || type == TransferMsgType.FILE) {
-            longPressView.getBackground().setTintList(null);
-        }
-
-        longPressView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
-            menu.setHeaderTitle(file.getDisplayName());
-            new MenuInflater(v.getContext()).inflate(R.menu.conversation_item_actions_file, menu);
-            if (file.getStatus() == InteractionStatus.TRANSFER_ONGOING) {
-                menu.findItem(R.id.conv_action_delete).setTitle(android.R.string.cancel);
-                menu.removeItem(R.id.conv_action_download);
-                menu.removeItem(R.id.conv_action_share);
-                menu.removeItem(R.id.conv_action_open);
-            } else {
-                if (!file.isComplete()) {
-                    menu.removeItem(R.id.conv_action_download);
-                    menu.removeItem(R.id.conv_action_share);
-                }
-            }
-            conversationFragment.onCreateContextMenu(menu, v, menuInfo);
-        });
-        longPressView.setOnLongClickListener(v -> {
-            if (type == TransferMsgType.AUDIO || type == TransferMsgType.FILE) {
-                conversationFragment.updatePosition(viewHolder.getAdapterPosition());
-                longPressView.getBackground().setTint(conversationFragment.getResources().getColor(R.color.grey_500));
-            }
-            mCurrentLongItem = new RecyclerViewContextMenuInfo(viewHolder.getAdapterPosition(), v.getId());
-            return false;
-        });
-
-        if (type == TransferMsgType.IMAGE) {
-            configureImage(viewHolder, path);
-        } else if (type == TransferMsgType.VIDEO) {
-            configureVideo(viewHolder, path);
-        } else if (type == TransferMsgType.AUDIO) {
-            configureAudio(viewHolder, path);
-        } else {
-            InteractionStatus status = file.getStatus();
-            if (status.isError()) {
-                viewHolder.mIcon.setImageResource(R.drawable.baseline_warning_24);
-            } else {
-                viewHolder.mIcon.setImageResource(R.drawable.baseline_attach_file_24);
-            }
-
-            viewHolder.mMsgTxt.setText(file.getDisplayName());
-
-            if (status == InteractionStatus.TRANSFER_AWAITING_HOST) {
-                viewHolder.btnRefuse.setVisibility(View.VISIBLE);
-                viewHolder.mAnswerLayout.setVisibility(View.VISIBLE);
-                viewHolder.btnAccept.setOnClickListener(v -> presenter.acceptFile(file));
-                viewHolder.btnRefuse.setOnClickListener(v -> presenter.refuseFile(file));
-            } else if (status == InteractionStatus.FILE_AVAILABLE) {
-                viewHolder.btnRefuse.setVisibility(View.GONE);
-                viewHolder.mAnswerLayout.setVisibility(View.VISIBLE);
-                viewHolder.btnAccept.setOnClickListener(v -> presenter.acceptFile(file));
-            } else {
-                viewHolder.mAnswerLayout.setVisibility(View.GONE);
-                if (status == InteractionStatus.TRANSFER_ONGOING) {
-                    viewHolder.progress.setMax((int) (file.getTotalSize() / 1024));
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                        viewHolder.progress.setProgress((int) (file.getBytesProgress() / 1024), true);
-                    } else {
-                        viewHolder.progress.setProgress((int) (file.getBytesProgress() / 1024));
-                    }
-                    viewHolder.progress.show();
-                } else {
-                    viewHolder.progress.hide();
-                }
-            }
-        }
-    }
-
-    private void configureForTypingIndicator(@NonNull final ConversationViewHolder viewHolder) {
-        AnimatedVectorDrawableCompat anim = AnimatedVectorDrawableCompat.create(viewHolder.itemView.getContext(), R.drawable.typing_indicator_animation);
-        if (anim != null) {
-            viewHolder.mStatusIcon.setImageDrawable(anim);
-            anim.registerAnimationCallback(new Animatable2Compat.AnimationCallback() {
-                @Override
-                public void onAnimationEnd(Drawable drawable) {
-                    anim.start();
-                }
-            });
-            anim.start();
-        }
-    }
-
-    /**
-     * Configures the viewholder to display a classic text message, ie. not a call info text message
-     *
-     * @param convViewHolder The conversation viewHolder
-     * @param interaction    The conversation element to display
-     * @param position       The position of the viewHolder
-     */
-    private void configureForTextMessage(@NonNull final ConversationViewHolder convViewHolder,
-                                         @NonNull final Interaction interaction,
-                                         int position) {
-        final Context context = convViewHolder.itemView.getContext();
-        TextMessage textMessage = (TextMessage)interaction;
-        Contact contact = textMessage.getContact();
-        if (contact == null) {
-            Log.e(TAG, "Invalid contact, not able to display message correctly");
-            return;
-        }
-        // Log.w(TAG, "configureForTextMessage " + position + " " + interaction.getDaemonId() + " " + interaction.getStatus());
-
-        String message = textMessage.getBody().trim();
-        View longPressView = convViewHolder.mMsgTxt;
-        longPressView.getBackground().setTintList(null);
-
-        longPressView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
-            Date date = new Date(interaction.getTimestamp());
-            DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
-            menu.setHeaderTitle(dateFormat.format(date));
-            conversationFragment.onCreateContextMenu(menu, v, menuInfo);
-            MenuInflater inflater = conversationFragment.getActivity().getMenuInflater();
-            inflater.inflate(R.menu.conversation_item_actions_messages, menu);
-
-            if ((interaction).getStatus() == (InteractionStatus.SENDING)) {
-                menu.removeItem(R.id.conv_action_delete);
-            } else {
-                menu.findItem(R.id.conv_action_delete).setTitle(R.string.menu_message_delete);
-                menu.removeItem(R.id.conv_action_cancel_message);
-            }
-        });
-
-        longPressView.setOnLongClickListener((View v) -> {
-            if (expandedItemPosition == position) {
-                expandedItemPosition = -1;
-            }
-            conversationFragment.updatePosition(convViewHolder.getBindingAdapterPosition());
-            if (textMessage.isIncoming()) {
-                longPressView.getBackground().setTint(conversationFragment.getResources().getColor(R.color.grey_500));
-            } else {
-                longPressView.getBackground().setTint(conversationFragment.getResources().getColor(R.color.blue_900));
-            }
-            mCurrentLongItem = new RecyclerViewContextMenuInfo(convViewHolder.getBindingAdapterPosition(), v.getId());
-            return false;
-        });
-
-        final boolean isTimeShown = hasPermanentTimeString(textMessage, position);
-        final SequenceType msgSequenceType = getMsgSequencing(position, isTimeShown);
-        if (StringUtils.isOnlyEmoji(message)) {
-            convViewHolder.mMsgTxt.getBackground().setAlpha(0);
-            convViewHolder.mMsgTxt.setTextSize(32.0f);
-            convViewHolder.mMsgTxt.setPadding(0, 0, 0, 0);
-        } else {
-            int resIndex = msgSequenceType.ordinal() + (textMessage.isIncoming() ? 1 : 0) * 4;
-            convViewHolder.mMsgTxt.setBackground(ContextCompat.getDrawable(context, msgBGLayouts[resIndex]));
-            if (convColor != 0 && !textMessage.isIncoming()) {
-                convViewHolder.mMsgTxt.getBackground().setTint(convColor);
-            }
-            convViewHolder.mMsgTxt.getBackground().setAlpha(255);
-            convViewHolder.mMsgTxt.setTextSize(16.f);
-            convViewHolder.mMsgTxt.setPadding(hPadding, vPadding, hPadding, vPadding);
-        }
-
-        convViewHolder.mMsgTxt.setText(message);
-
-        boolean endOfSeq = msgSequenceType == SequenceType.LAST || msgSequenceType == SequenceType.SINGLE;
-        if (textMessage.isIncoming()) {
-            if (endOfSeq) {
-                convViewHolder.mAvatar.setImageDrawable(
-                        conversationFragment.getConversationAvatar(contact.getPrimaryNumber())
-                );
-                convViewHolder.mAvatar.setVisibility(View.VISIBLE);
-            } else {
-                if (position == lastMsgPos - 1 && convViewHolder.mAvatar != null) {
-                    Animation animation = AnimationUtils.loadAnimation(
-                            convViewHolder.mAvatar.getContext(), R.anim.fade_out);
-                    animation.setAnimationListener(new Animation.AnimationListener(){
-                        @Override
-                        public void onAnimationStart(Animation arg0) {
-                        }
-                        @Override
-                        public void onAnimationRepeat(Animation arg0) {
-                        }
-                        @Override
-                        public void onAnimationEnd(Animation arg0) {
-                            convViewHolder.mAvatar.setImageBitmap(null);
-                            convViewHolder.mAvatar.setVisibility(View.INVISIBLE);
-                        }
-                    });
-                    convViewHolder.mAvatar.startAnimation(animation);
-                } else {
-                    if (convViewHolder.mAvatar != null) {
-                        convViewHolder.mAvatar.setImageBitmap(null);
-                        convViewHolder.mAvatar.setVisibility(View.INVISIBLE);
-                    }
-                }
-            }
-        } else {
-            switch (textMessage.getStatus()) {
-                case SENDING:
-                    convViewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                    convViewHolder.mStatusIcon.setImageResource(R.drawable.baseline_circle_24);
-                    break;
-                case FAILURE:
-                    convViewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                    convViewHolder.mStatusIcon.setImageResource(R.drawable.round_highlight_off_24);
-                    break;
-                case DISPLAYED:
-                    if (lastDisplayedPosition == position) {
-                        convViewHolder.mStatusIcon.setVisibility(mShowReadIndicator ? View.VISIBLE : View.GONE);
-                        convViewHolder.mStatusIcon.setImageDrawable(conversationFragment.getSmallConversationAvatar(contact.getPrimaryNumber()));
-                    } else {
-                        convViewHolder.mStatusIcon.setVisibility(View.GONE);
-                        convViewHolder.mStatusIcon.setImageDrawable(null);
-                    }
-                    break;
-                default:
-                    if (position == lastOutgoingIndex()) {
-                        convViewHolder.mStatusIcon.setVisibility(View.VISIBLE);
-                        convViewHolder.mStatusIcon.setImageResource(R.drawable.baseline_check_circle_24);
-                        lastDeliveredPosition = position;
-                    } else {
-                        convViewHolder.mStatusIcon.setVisibility(View.GONE);
-                        convViewHolder.mStatusIcon.setImageDrawable(null);
-                    }
-            }
-        }
-
-        setBottomMargin(convViewHolder.mMsgTxt, endOfSeq ? 8 : 0);
-
-        if (isTimeShown) {
-            convViewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(t -> {
-                String timeSeparationString = timestampToDetailString(context, textMessage.getTimestamp());
-                convViewHolder.mMsgDetailTxtPerm.setText(timeSeparationString);
-            }));
-            convViewHolder.mMsgDetailTxtPerm.setVisibility(View.VISIBLE);
-        } else {
-            convViewHolder.mMsgDetailTxtPerm.setVisibility(View.GONE);
-            final boolean isExpanded = position == expandedItemPosition;
-            if (isExpanded) {
-                convViewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(t -> {
-                    String timeSeparationString = timestampToDetailString(context, textMessage.getTimestamp());
-                    convViewHolder.mMsgDetailTxt.setText(timeSeparationString);
-                }));
-            }
-            setItemViewExpansionState(convViewHolder, isExpanded);
-            convViewHolder.mItem.setOnClickListener((View v) -> {
-                if (convViewHolder.animator != null && convViewHolder.animator.isRunning()) {
-                    return;
-                }
-                if (expandedItemPosition >= 0) {
-                    int prev = expandedItemPosition;
-                    notifyItemChanged(prev);
-                }
-                expandedItemPosition = isExpanded ? -1 : position;
-                notifyItemChanged(expandedItemPosition);
-            });
-        }
-    }
-
-    private void configureForContactEvent(@NonNull final ConversationViewHolder viewHolder, @NonNull final Interaction interaction) {
-        ContactEvent event = (ContactEvent) interaction;
-        if (event.event == ContactEvent.Event.ADDED) {
-            viewHolder.mMsgTxt.setText(R.string.hist_contact_added);
-        } else if (event.event == ContactEvent.Event.INCOMING_REQUEST) {
-            viewHolder.mMsgTxt.setText(R.string.hist_invitation_received);
-        }
-        viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(t -> {
-            String timeSeparationString = timestampToDetailString(viewHolder.itemView.getContext(), event.getTimestamp());
-            viewHolder.mMsgDetailTxt.setText(timeSeparationString);
-        }));
-    }
-
-    /**
-     * Configures the viewholder to display a call info text message, ie. not a classic text message
-     *
-     * @param convViewHolder The conversation viewHolder
-     * @param interaction    The conversation element to display
-     */
-    private void configureForCallInfo(@NonNull final ConversationViewHolder convViewHolder,
-                                      @NonNull final Interaction interaction) {
-        convViewHolder.mIcon.setScaleY(1);
-        Context context = convViewHolder.itemView.getContext();
-
-        View longPressView = convViewHolder.mCallInfoLayout;
-        longPressView.getBackground().setTintList(null);
-        longPressView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
-            conversationFragment.onCreateContextMenu(menu, v, menuInfo);
-            MenuInflater inflater = conversationFragment.getActivity().getMenuInflater();
-            inflater.inflate(R.menu.conversation_item_actions_messages, menu);
-
-            menu.findItem(R.id.conv_action_delete).setTitle(R.string.menu_delete);
-            menu.removeItem(R.id.conv_action_cancel_message);
-            menu.removeItem(R.id.conv_action_copy_text);
-        });
-
-        longPressView.setOnLongClickListener((View v) -> {
-            longPressView.getBackground().setTint(conversationFragment.getResources().getColor(R.color.grey_500));
-            conversationFragment.updatePosition(convViewHolder.getAdapterPosition());
-            mCurrentLongItem = new RecyclerViewContextMenuInfo(convViewHolder.getAdapterPosition(), v.getId());
-            return false;
-        });
-
-        int pictureResID;
-        String historyTxt;
-        Call call = (Call) interaction;
-        if (call.isMissed()) {
-            if (call.isIncoming()) {
-                pictureResID = R.drawable.baseline_call_missed_24;
-            } else {
-                pictureResID = R.drawable.baseline_call_missed_outgoing_24;
-                // Flip the photo upside down to show a "missed outgoing call"
-                convViewHolder.mIcon.setScaleY(-1);
-            }
-            historyTxt = call.isIncoming() ?
-                    context.getString(R.string.notif_missed_incoming_call) :
-                    context.getString(R.string.notif_missed_outgoing_call);
-        } else {
-            pictureResID = (call.isIncoming()) ?
-                    R.drawable.baseline_call_received_24 :
-                    R.drawable.baseline_call_made_24;
-            historyTxt = call.isIncoming() ?
-                    context.getString(R.string.notif_incoming_call) :
-                    context.getString(R.string.notif_outgoing_call);
-        }
-
-        convViewHolder.mIcon.setImageResource(pictureResID);
-        convViewHolder.mHistTxt.setText(historyTxt);
-        convViewHolder.mHistDetailTxt.setText(DateFormat.getDateTimeInstance()
-                .format(call.getTimestamp())); // start date
-    }
-
-    /**
-     * Computes the string to set in text details between messages, indicating time separation.
-     *
-     * @param timestamp The timestamp used to launch the computation with Date().getTime().
-     *                  Can be the last received message timestamp for example.
-     * @return The string to display in the text details between messages.
-     */
-    private String timestampToDetailString(Context context, long timestamp) {
-        long diff = new Date().getTime() - timestamp;
-        String timeStr;
-        if (diff < DateUtils.WEEK_IN_MILLIS) {
-            if (diff < DateUtils.DAY_IN_MILLIS && DateUtils.isToday(timestamp)) { // 11:32 A.M.
-                timeStr = DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME);
-            } else {
-                timeStr = DateUtils.formatDateTime(context, timestamp,
-                        DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_NO_YEAR |
-                                DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_TIME);
-            }
-        } else if (diff < DateUtils.YEAR_IN_MILLIS) { // JAN. 7, 11:02 A.M.
-            timeStr = DateUtils.formatDateTime(context, timestamp,
-                    DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR |
-                            DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_TIME);
-        } else {
-            timeStr = DateUtils.formatDateTime(context, timestamp,
-                    DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
-                            DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_WEEKDAY |
-                            DateUtils.FORMAT_ABBREV_ALL);
-        }
-        return timeStr.toUpperCase(Locale.getDefault());
-    }
-
-    /**
-     * Helper method to return the previous TextMessage relative to an initial position.
-     *
-     * @param position The initial position
-     * @return the previous TextMessage if any, null otherwise
-     */
-    @Nullable
-    private Interaction getPreviousMessageFromPosition(int position) {
-        if (!mInteractions.isEmpty() && position > 0) {
-            return mInteractions.get(position - 1);
-        }
-        return null;
-    }
-
-    /**
-     * Helper method to return the next TextMessage relative to an initial position.
-     *
-     * @param position The initial position
-     * @return the next TextMessage if any, null otherwise
-     */
-    @Nullable
-    private Interaction getNextMessageFromPosition(int position) {
-        if (!mInteractions.isEmpty() && position < mInteractions.size() - 1) {
-            return mInteractions.get(position + 1);
-        }
-        return null;
-    }
-
-    private boolean isSeqBreak(@NonNull Interaction first, @NonNull Interaction second) {
-        return StringUtils.isOnlyEmoji(first.getBody()) != StringUtils.isOnlyEmoji(second.getBody())
-                || first.isIncoming() != second.isIncoming()
-                || first.getType() != InteractionType.TEXT
-                || second.getType() != InteractionType.TEXT;
-    }
-
-    private boolean isAlwaysSingleMsg(@NonNull Interaction msg) {
-        return msg.getType() != InteractionType.TEXT
-                || StringUtils.isOnlyEmoji(msg.getBody());
-    }
-
-    private SequenceType getMsgSequencing(final int i, final boolean isTimeShown) {
-        Interaction msg = mInteractions.get(i);
-        if (isAlwaysSingleMsg(msg)) {
-            return SequenceType.SINGLE;
-        }
-        if (mInteractions.size() == 1 || i == 0) {
-            if (mInteractions.size() == i + 1) {
-                return SequenceType.SINGLE;
-            }
-            Interaction nextMsg = getNextMessageFromPosition(i);
-            if (nextMsg != null) {
-                if (isSeqBreak(msg, nextMsg) || hasPermanentTimeString(nextMsg, i + 1)) {
-                    return SequenceType.SINGLE;
-                } else {
-                    return SequenceType.FIRST;
-                }
-            }
-        } else if (mInteractions.size() == i + 1) {
-            Interaction prevMsg = getPreviousMessageFromPosition(i);
-            if (prevMsg != null) {
-                if (isSeqBreak(msg, prevMsg) || isTimeShown) {
-                    return SequenceType.SINGLE;
-                } else {
-                    return SequenceType.LAST;
-                }
-            }
-        }
-        Interaction prevMsg = getPreviousMessageFromPosition(i);
-        Interaction nextMsg = getNextMessageFromPosition(i);
-        if (prevMsg != null && nextMsg != null) {
-            boolean nextMsgHasTime = hasPermanentTimeString(nextMsg, i + 1);
-            if (((isSeqBreak(msg, prevMsg) || isTimeShown) && !(isSeqBreak(msg, nextMsg) || nextMsgHasTime))) {
-                return SequenceType.FIRST;
-            } else if (!isSeqBreak(msg, prevMsg) && !isTimeShown && isSeqBreak(msg, nextMsg)) {
-                return SequenceType.LAST;
-            } else if (!isSeqBreak(msg, prevMsg) && !isTimeShown && !isSeqBreak(msg, nextMsg)) {
-                return nextMsgHasTime ? SequenceType.LAST : SequenceType.MIDDLE;
-            }
-        }
-        return SequenceType.SINGLE;
-    }
-
-    private void setItemViewExpansionState(ConversationViewHolder viewHolder, boolean expanded) {
-        View view = viewHolder.mMsgDetailTxt;
-        if (viewHolder.animator == null) {
-            if (view.getHeight() == 0 && !expanded) {
-                return;
-            }
-            viewHolder.animator = new ValueAnimator();
-        }
-        if (viewHolder.animator.isRunning()) {
-            viewHolder.animator.reverse();
-            return;
-        }
-        view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        viewHolder.animator.setIntValues(0, view.getMeasuredHeight());
-        if (expanded) {
-            view.setVisibility(View.VISIBLE);
-        }
-        viewHolder.animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                ValueAnimator va = (ValueAnimator) animation;
-                if ((Integer) va.getAnimatedValue() == 0) {
-                    view.setVisibility(View.GONE);
-                }
-                viewHolder.animator = null;
-            }
-        });
-        viewHolder.animator.setDuration(200);
-        viewHolder.animator.addUpdateListener(animation -> {
-            view.getLayoutParams().height = (Integer) animation.getAnimatedValue();
-            view.requestLayout();
-        });
-        if (!expanded) {
-            viewHolder.animator.reverse();
-        } else {
-            viewHolder.animator.start();
-        }
-    }
-
-    private static void setBottomMargin(View view, int value) {
-        int targetSize = (int) (value * view.getContext().getResources().getDisplayMetrics().density);
-        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
-        params.bottomMargin = targetSize;
-    }
-
-    private boolean hasPermanentTimeString(final Interaction msg, int position) {
-        if (msg == null) {
-            return false;
-        }
-        Interaction prevMsg = getPreviousMessageFromPosition(position);
-        return (prevMsg != null &&
-                (msg.getTimestamp() - prevMsg.getTimestamp()) > 10 * DateUtils.MINUTE_IN_MILLIS);
-    }
-
-    private int lastOutgoingIndex() {
-        int i;
-        for (i = mInteractions.size() - 1; i >= 0; i--) {
-            if (!mInteractions.get(i).isIncoming()) {
-                break;
-            }
-        }
-        return i;
-    }
-
-    private enum SequenceType {
-        FIRST,
-        MIDDLE,
-        LAST,
-        SINGLE
-    }
-
-    private enum TransferMsgType {
-        FILE,
-        IMAGE,
-        AUDIO,
-        VIDEO
-    }
-    public enum MessageType {
-        INCOMING_FILE(R.layout.item_conv_file_peer),
-        INCOMING_IMAGE(R.layout.item_conv_image_peer),
-        INCOMING_AUDIO(R.layout.item_conv_audio_peer),
-        INCOMING_VIDEO(R.layout.item_conv_video_peer),
-        OUTGOING_FILE(R.layout.item_conv_file_me),
-        OUTGOING_IMAGE(R.layout.item_conv_image_me),
-        OUTGOING_AUDIO(R.layout.item_conv_audio_me),
-        OUTGOING_VIDEO(R.layout.item_conv_video_me),
-        CONTACT_EVENT(R.layout.item_conv_contact),
-        CALL_INFORMATION(R.layout.item_conv_call),
-        INCOMING_TEXT_MESSAGE(R.layout.item_conv_msg_peer),
-        OUTGOING_TEXT_MESSAGE(R.layout.item_conv_msg_me),
-        COMPOSING_INDICATION(R.layout.item_conv_composing),
-        INVALID(-1);
-
-        @LayoutRes private final int layout;
-
-        MessageType(@LayoutRes int l) {
-            layout = l;
-        }
-
-        boolean isFile() {
-            return this == INCOMING_FILE || this == OUTGOING_FILE;
-        }
-        boolean isAudio() {
-            return this == INCOMING_AUDIO || this == OUTGOING_AUDIO;
-        }
-        boolean isVideo() {
-            return this == INCOMING_VIDEO || this == OUTGOING_VIDEO;
-        }
-        boolean isImage() {
-            return this == INCOMING_IMAGE || this == OUTGOING_IMAGE;
-        }
-
-        public TransferMsgType getTransferType() {
-            return isFile() ? TransferMsgType.FILE
-                    : (isImage() ? TransferMsgType.IMAGE
-                    : (isAudio() ? TransferMsgType.AUDIO
-                    : (isVideo() ? TransferMsgType.VIDEO : TransferMsgType.FILE)));
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
new file mode 100644
index 000000000..6647eaef7
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
@@ -0,0 +1,1141 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors:    Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *              Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.adapters
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.SurfaceTexture
+import android.graphics.drawable.Drawable
+import android.media.MediaPlayer
+import android.os.Build
+import android.text.TextUtils
+import android.text.format.DateUtils
+import android.text.format.Formatter
+import android.util.Log
+import android.util.TypedValue
+import android.view.*
+import android.view.ContextMenu.ContextMenuInfo
+import android.view.TextureView.SurfaceTextureListener
+import android.view.ViewGroup.MarginLayoutParams
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.annotation.LayoutRes
+import androidx.cardview.widget.CardView
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
+import androidx.recyclerview.widget.RecyclerView
+import androidx.vectordrawable.graphics.drawable.Animatable2Compat
+import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
+import com.bumptech.glide.load.resource.bitmap.CenterInside
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners
+import com.bumptech.glide.request.target.DrawableImageViewTarget
+import cx.ring.R
+import cx.ring.client.MediaViewerActivity
+import cx.ring.fragments.ConversationFragment
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ContentUriHandler.getUriForFile
+import cx.ring.utils.GlideApp
+import cx.ring.utils.GlideOptions
+import cx.ring.utils.ResourceMapper
+import cx.ring.views.ConversationViewHolder
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import net.jami.conversation.ConversationPresenter
+import net.jami.model.*
+import net.jami.model.Account.ComposingStatus
+import net.jami.model.Interaction.InteractionStatus
+import net.jami.utils.StringUtils
+import java.io.File
+import java.text.DateFormat
+import java.util.*
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
+
+class ConversationAdapter(
+    private val conversationFragment: ConversationFragment,
+    private val presenter: ConversationPresenter
+) : RecyclerView.Adapter<ConversationViewHolder>() {
+
+    private val mInteractions = ArrayList<Interaction>()
+    private val hPadding: Int
+    private val vPadding: Int
+    private val mPictureMaxSize: Int
+    private val PICTURE_OPTIONS: GlideOptions
+    private var mCurrentLongItem: RecyclerViewContextMenuInfo? = null
+    private var convColor = 0
+    private var expandedItemPosition = -1
+    private var lastDeliveredPosition = -1
+    private var lastDisplayedPosition = -1
+    private val timestampUpdateTimer: Observable<Long>
+    private var lastMsgPos = -1
+    private var isComposing = false
+    private var mShowReadIndicator = true
+
+    /**
+     * Refreshes the data and notifies the changes
+     *
+     * @param list an arraylist of interactions
+     */
+    @SuppressLint("NotifyDataSetChanged")
+    fun updateDataset(list: List<Interaction>) {
+        Log.d(TAG, "updateDataset: list size=" + list.size)
+        when {
+            mInteractions.isEmpty() -> {
+                mInteractions.addAll(list)
+                notifyDataSetChanged()
+            }
+            list.size > mInteractions.size -> {
+                val oldSize = mInteractions.size
+                mInteractions.addAll(list.subList(oldSize, list.size))
+                notifyItemRangeInserted(oldSize, list.size)
+            }
+            else -> {
+                mInteractions.clear()
+                mInteractions.addAll(list)
+                notifyDataSetChanged()
+            }
+        }
+    }
+
+    fun add(e: Interaction): Boolean {
+        if (!TextUtils.isEmpty(e.messageId)) {
+            if (mInteractions.isEmpty() || mInteractions[mInteractions.size - 1].messageId == e.parentId) {
+                val update = mInteractions.isNotEmpty()
+                mInteractions.add(e)
+                notifyItemInserted(mInteractions.size - 1)
+                if (update) notifyItemChanged(mInteractions.size - 2)
+                return true
+            }
+            var i = 0
+            val n = mInteractions.size
+            while (i < n) {
+                if (e.messageId == mInteractions[i].parentId) {
+                    Log.w(TAG, "Adding message at $i previous count $n")
+                    mInteractions.add(i, e)
+                    notifyItemInserted(i)
+                    return i == n - 1
+                }
+                i++
+            }
+        } else {
+            val update = mInteractions.isNotEmpty()
+            mInteractions.add(e)
+            notifyItemInserted(mInteractions.size - 1)
+            if (update) notifyItemChanged(mInteractions.size - 2)
+        }
+        return true
+    }
+
+    fun update(e: Interaction) {
+        Log.w(TAG, "update " + e.messageId)
+        if (!e.isIncoming && e.status == InteractionStatus.SUCCESS) {
+            notifyItemChanged(lastDeliveredPosition)
+        }
+        for (i in mInteractions.indices.reversed()) {
+            val element = mInteractions[i]
+            if (e === element) {
+                notifyItemChanged(i)
+                break
+            }
+        }
+    }
+
+    fun remove(e: Interaction) {
+        if (e.messageId != null) {
+            for (i in mInteractions.indices.reversed()) {
+                if (e.messageId == mInteractions[i].messageId) {
+                    mInteractions.removeAt(i)
+                    notifyItemRemoved(i)
+                    if (i > 0) {
+                        notifyItemChanged(i - 1)
+                    }
+                    if (i != mInteractions.size) {
+                        notifyItemChanged(i)
+                    }
+                    break
+                }
+            }
+        } else {
+            for (i in mInteractions.indices.reversed()) {
+                if (e.id == mInteractions[i].id) {
+                    mInteractions.removeAt(i)
+                    notifyItemRemoved(i)
+                    if (i > 0) {
+                        notifyItemChanged(i - 1)
+                    }
+                    if (i != mInteractions.size) {
+                        notifyItemChanged(i)
+                    }
+                    break
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates the contact photo to use for this conversation
+     */
+    fun setPhoto() {
+        notifyDataSetChanged()
+    }
+
+    override fun getItemCount(): Int {
+        return mInteractions.size + if (isComposing) 1 else 0
+    }
+
+    override fun getItemId(position: Int): Long {
+        return if (isComposing && position == mInteractions.size) Long.MAX_VALUE else mInteractions[position].id.toLong()
+    }
+
+    override fun getItemViewType(position: Int): Int {
+        if (isComposing && position == mInteractions.size) return MessageType.COMPOSING_INDICATION.ordinal
+        val interaction = mInteractions[position]
+        return when (interaction.type) {
+            Interaction.InteractionType.CONTACT -> MessageType.CONTACT_EVENT.ordinal
+            Interaction.InteractionType.CALL -> MessageType.CALL_INFORMATION.ordinal
+            Interaction.InteractionType.TEXT -> if (interaction.isIncoming) {
+                MessageType.INCOMING_TEXT_MESSAGE.ordinal
+            } else {
+                MessageType.OUTGOING_TEXT_MESSAGE.ordinal
+            }
+            Interaction.InteractionType.DATA_TRANSFER -> {
+                val file = interaction as DataTransfer
+                val out = if (interaction.isIncoming) 0 else 4
+                if (file.isComplete) {
+                    when {
+                        file.isPicture -> return MessageType.INCOMING_IMAGE.ordinal + out
+                        file.isAudio -> return MessageType.INCOMING_AUDIO.ordinal + out
+                        file.isVideo -> return MessageType.INCOMING_VIDEO.ordinal + out
+                    }
+                }
+                out
+            }
+            Interaction.InteractionType.INVALID -> MessageType.INVALID.ordinal
+            null -> MessageType.INVALID.ordinal
+        }
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationViewHolder {
+        val type = MessageType.values()[viewType]
+        val v = if (type == MessageType.INVALID) FrameLayout(parent.context)
+        else (LayoutInflater.from(parent.context).inflate(type.layout, parent, false) as ViewGroup)
+        return ConversationViewHolder(v, type)
+    }
+
+    override fun onBindViewHolder(conversationViewHolder: ConversationViewHolder, position: Int) {
+        if (isComposing && position == mInteractions.size) {
+            configureForTypingIndicator(conversationViewHolder)
+            return
+        }
+        val interaction = mInteractions[position]
+        conversationViewHolder.compositeDisposable.clear()
+        if (position > lastMsgPos) {
+            lastMsgPos = position
+            val animation = AnimationUtils.loadAnimation(conversationViewHolder.itemView.context, R.anim.fade_in)
+            animation.startOffset = 150
+            conversationViewHolder.itemView.startAnimation(animation)
+        }
+
+        //Log.w(TAG, "onBindViewHolder " + interaction.getType() + " " + interaction);
+        if (interaction.type == Interaction.InteractionType.INVALID) {
+            conversationViewHolder.itemView.visibility = View.GONE
+        } else {
+            conversationViewHolder.itemView.visibility = View.VISIBLE
+            if (interaction.type == Interaction.InteractionType.TEXT) {
+                configureForTextMessage(conversationViewHolder, interaction, position)
+            } else if (interaction.type == Interaction.InteractionType.CALL) {
+                configureForCallInfo(conversationViewHolder, interaction)
+            } else if (interaction.type == Interaction.InteractionType.CONTACT) {
+                configureForContactEvent(conversationViewHolder, interaction)
+            } else if (interaction.type == Interaction.InteractionType.DATA_TRANSFER) {
+                configureForFileInfo(conversationViewHolder, interaction, position)
+            }
+        }
+    }
+
+    override fun onViewRecycled(holder: ConversationViewHolder) {
+        holder.itemView.setOnLongClickListener(null)
+        if (holder.mImage != null) {
+            holder.mImage.setOnLongClickListener(null)
+        }
+        if (holder.video != null) {
+            holder.video.setOnClickListener(null)
+            holder.video.surfaceTextureListener = null
+        }
+        if (holder.surface != null) {
+            holder.surface.release()
+            holder.surface = null
+        }
+        if (holder.player != null) {
+            try {
+                if (holder.player.isPlaying) holder.player.stop()
+                holder.player.reset()
+            } catch (e: Exception) {
+                // left blank intentionally
+            }
+            holder.player.release()
+            holder.player = null
+        }
+        if (holder.mMsgTxt != null) {
+            holder.mMsgTxt.setOnLongClickListener(null)
+        }
+        if (holder.mItem != null) {
+            holder.mItem.setOnClickListener(null)
+        }
+        if (expandedItemPosition == holder.layoutPosition) {
+            if (holder.mMsgDetailTxt != null) holder.mMsgDetailTxt.visibility = View.GONE
+            expandedItemPosition = -1
+        }
+        holder.compositeDisposable.clear()
+    }
+
+    fun setPrimaryColor(color: Int) {
+        convColor = color
+        notifyDataSetChanged()
+    }
+
+    fun setComposingStatus(composingStatus: ComposingStatus) {
+        val composing = composingStatus == ComposingStatus.Active
+        if (isComposing != composing) {
+            isComposing = composing
+            if (composing) notifyItemInserted(mInteractions.size) else notifyItemRemoved(
+                mInteractions.size
+            )
+        }
+    }
+
+    fun setReadIndicatorStatus(show: Boolean) {
+        mShowReadIndicator = show
+    }
+
+    fun setLastDisplayed(interaction: Interaction) {
+        Log.w(TAG, "setLastDisplayed " + interaction.daemonId)
+        for (i in mInteractions.indices.reversed()) {
+            val element = mInteractions[i]
+            if (interaction.id == element.id) {
+                if (lastDisplayedPosition != -1) notifyItemChanged(lastDisplayedPosition)
+                lastDisplayedPosition = i
+                notifyItemChanged(i)
+                Log.w(TAG, "new displayed item $i")
+                break
+            }
+        }
+    }
+
+    private class RecyclerViewContextMenuInfo(
+        val position: Int,
+        val id: Long
+    ) : ContextMenuInfo
+
+    fun onContextItemSelected(item: MenuItem): Boolean {
+        val info = mCurrentLongItem ?: return false
+        var interaction: Interaction? = null
+        try {
+            interaction = mInteractions[info.position]
+        } catch (e: IndexOutOfBoundsException) {
+            Log.e(TAG, "Interaction array may be empty or null", e)
+        }
+        if (interaction == null) return false
+        if (interaction.type == Interaction.InteractionType.CONTACT) return false
+        when (item.itemId) {
+            R.id.conv_action_download -> presenter.saveFile(interaction)
+            R.id.conv_action_share -> presenter.shareFile(interaction)
+            R.id.conv_action_open -> presenter.openFile(interaction)
+            R.id.conv_action_delete -> presenter.deleteConversationItem(interaction)
+            R.id.conv_action_cancel_message -> presenter.cancelMessage(interaction)
+            R.id.conv_action_copy_text -> addToClipboard(interaction.body)
+        }
+        return true
+    }
+
+    private fun addToClipboard(text: String?) {
+        if (text == null || text.isEmpty()) return
+        val clipboard = conversationFragment.requireActivity()
+            .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+        val clip = ClipData.newPlainText("Copied Message", text)
+        clipboard.setPrimaryClip(clip)
+    }
+
+    private fun configureImage(viewHolder: ConversationViewHolder, path: File) {
+        val context = viewHolder.mImage.context
+        GlideApp.with(context)
+            .load(path)
+            .apply(PICTURE_OPTIONS)
+            .into(DrawableImageViewTarget(viewHolder.mImage).waitForLayout())
+        viewHolder.mImage.setOnClickListener { v: View ->
+            try {
+                val contentUri = getUriForFile(v.context, ContentUriHandler.AUTHORITY_FILES, path)
+                val i = Intent(context, MediaViewerActivity::class.java)
+                    .setAction(Intent.ACTION_VIEW)
+                    .setDataAndType(contentUri, "image/*")
+                    .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), viewHolder.mImage, "picture")
+                conversationFragment.startActivityForResult(i, 3006, options.toBundle())
+            } catch (e: Exception) {
+                Log.w(TAG, "Can't open picture", e);
+            }
+        }
+    }
+
+    private fun configureAudio(viewHolder: ConversationViewHolder, path: File) {
+        val context = viewHolder.itemView.context
+        try {
+            (viewHolder.btnAccept as ImageView).setImageResource(R.drawable.baseline_play_arrow_24)
+            val player = MediaPlayer.create(context, getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, path))
+            viewHolder.player = player
+            if (player != null) {
+                player.setOnCompletionListener { mp: MediaPlayer ->
+                    mp.seekTo(0)
+                    (viewHolder.btnAccept as ImageView).setImageResource(R.drawable.baseline_play_arrow_24)
+                }
+                viewHolder.btnAccept.setOnClickListener {
+                    if (player.isPlaying) {
+                        player.pause()
+                        (viewHolder.btnAccept as ImageView).setImageResource(R.drawable.baseline_play_arrow_24)
+                    } else {
+                        player.start()
+                        (viewHolder.btnAccept as ImageView).setImageResource(R.drawable.baseline_pause_24)
+                    }
+                }
+                viewHolder.btnRefuse.setOnClickListener {
+                    if (player.isPlaying) player.pause()
+                    player.seekTo(0)
+                    (viewHolder.btnAccept as ImageView).setImageResource(R.drawable.baseline_play_arrow_24)
+                }
+                viewHolder.compositeDisposable.add(
+                    Observable.interval(1L, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
+                        .startWithItem(0L)
+                        .subscribe { t: Long? ->
+                            val pS = player.currentPosition / 1000
+                            val dS = player.duration / 1000
+                            viewHolder.mMsgTxt.text = String.format(
+                                Locale.getDefault(),
+                                "%02d:%02d / %02d:%02d", pS / 60, pS % 60, dS / 60, dS % 60
+                            )
+                        })
+            } else {
+                viewHolder.btnAccept.setOnClickListener(null)
+                viewHolder.btnRefuse.setOnClickListener(null)
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Error initializing player", e)
+        }
+    }
+
+    private fun configureVideo(viewHolder: ConversationViewHolder, path: File) {
+        val context = viewHolder.itemView.context
+        viewHolder.player?.let {
+            viewHolder.player = null
+            it.release()
+        }
+        val player = MediaPlayer.create(context, getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, path)) ?: return
+        viewHolder.player = player
+        val playBtn = ContextCompat.getDrawable(viewHolder.mLayout.context, R.drawable.baseline_play_arrow_24)!!.mutate()
+        DrawableCompat.setTint(playBtn, Color.WHITE)
+        (viewHolder.mLayout as CardView).foreground = playBtn
+        player.setOnCompletionListener { mp: MediaPlayer ->
+            if (mp.isPlaying) mp.pause()
+            mp.seekTo(1)
+            (viewHolder.mLayout as CardView).foreground = playBtn
+        }
+        player.setOnVideoSizeChangedListener { mp: MediaPlayer, width: Int, height: Int ->
+            Log.w(TAG, "OnVideoSizeChanged " + width + "x" + height)
+            val p = viewHolder.video.layoutParams as FrameLayout.LayoutParams
+            val maxDim = max(width, height)
+            p.width = width * mPictureMaxSize / maxDim
+            p.height = height * mPictureMaxSize / maxDim
+            viewHolder.video.layoutParams = p
+        }
+        if (viewHolder.video.isAvailable) {
+            if (viewHolder.surface == null) {
+                viewHolder.surface = Surface(viewHolder.video.surfaceTexture)
+            }
+            player.setSurface(viewHolder.surface)
+        }
+        viewHolder.video.surfaceTextureListener = object : SurfaceTextureListener {
+            override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+                if (viewHolder.surface == null) {
+                    viewHolder.surface = Surface(surface)
+                    try {
+                        player.setSurface(viewHolder.surface)
+                    } catch (e: Exception) {
+                        // Left blank
+                    }
+                }
+            }
+
+            override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+            }
+
+            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+                try {
+                    player.setSurface(null)
+                } catch (e: Exception) {
+                    // Left blank
+                }
+                player.release()
+                viewHolder.surface?.let {
+                    viewHolder.surface = null
+                    it.release()
+                }
+                return true
+            }
+
+            override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
+        }
+        viewHolder.video.setOnClickListener {
+            try {
+                if (player.isPlaying) {
+                    player.pause()
+                    (viewHolder.mLayout as CardView).foreground = playBtn
+                } else {
+                    player.start()
+                    (viewHolder.mLayout as CardView).foreground = null
+                }
+            } catch (e: Exception) {
+                // Left blank
+            }
+        }
+        player.seekTo(1)
+    }
+
+    private fun configureForFileInfo(viewHolder: ConversationViewHolder, interaction: Interaction, position: Int) {
+        val file = interaction as DataTransfer
+        val path = presenter.deviceRuntimeService.getConversationPath(file)
+        //if (file.isComplete())
+        //    file.setSize(path.length());
+        val timeString = timestampToDetailString(viewHolder.itemView.context, file.timestamp)
+        viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe {
+            when (val status = file.status) {
+                InteractionStatus.TRANSFER_FINISHED -> {
+                    viewHolder.mMsgDetailTxt.text = String.format("%s - %s", timeString,
+                        Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize))
+                }
+                InteractionStatus.TRANSFER_ONGOING -> {
+                    viewHolder.mMsgDetailTxt.text = String.format("%s / %s - %s",
+                        Formatter.formatFileSize(viewHolder.itemView.context, file.bytesProgress),
+                        Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize),
+                        ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status)
+                    )
+                }
+                else -> {
+                    viewHolder.mMsgDetailTxt.text = String.format("%s - %s - %s", timeString,
+                        Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize),
+                        ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status)
+                    )
+                }
+            }
+        })
+        val type = viewHolder.type.transferType
+        viewHolder.compositeDisposable.clear()
+        if (hasPermanentTimeString(file, position)) {
+            viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe {
+                viewHolder.mMsgDetailTxtPerm.text = timestampToDetailString(viewHolder.itemView.context, file.timestamp)
+            })
+            viewHolder.mMsgDetailTxtPerm.visibility = View.VISIBLE
+        } else {
+            viewHolder.mMsgDetailTxtPerm.visibility = View.GONE
+        }
+        val contact = interaction.contact
+        if (interaction.isIncoming) {
+            viewHolder.mAvatar.setImageBitmap(null)
+            viewHolder.mAvatar.visibility = View.VISIBLE
+            if (contact != null) {
+                viewHolder.mAvatar.setImageDrawable(conversationFragment.getConversationAvatar(contact.primaryNumber))
+            }
+        } else {
+            when (interaction.status) {
+                InteractionStatus.SENDING -> {
+                    viewHolder.mStatusIcon.visibility = View.VISIBLE
+                    viewHolder.mStatusIcon.setImageResource(R.drawable.baseline_circle_24)
+                }
+                InteractionStatus.FAILURE -> {
+                    viewHolder.mStatusIcon.visibility = View.VISIBLE
+                    viewHolder.mStatusIcon.setImageResource(R.drawable.round_highlight_off_24)
+                }
+                InteractionStatus.DISPLAYED -> {
+                    viewHolder.mStatusIcon.visibility = if (mShowReadIndicator) View.VISIBLE else View.GONE
+                    viewHolder.mStatusIcon.setImageDrawable(conversationFragment.getSmallConversationAvatar(contact!!.primaryNumber))
+                }
+                else -> {
+                    viewHolder.mStatusIcon.visibility = View.VISIBLE
+                    viewHolder.mStatusIcon.setImageResource(R.drawable.baseline_check_circle_24)
+                    lastDeliveredPosition = position
+                }
+            }
+        }
+        val longPressView =
+            (if (type == TransferMsgType.IMAGE) viewHolder.mImage else if (type == TransferMsgType.VIDEO) viewHolder.video else if (type == TransferMsgType.AUDIO) viewHolder.mAudioInfoLayout else viewHolder.mFileInfoLayout)
+                ?: return
+        if (type == TransferMsgType.AUDIO || type == TransferMsgType.FILE) {
+            longPressView.background.setTintList(null)
+        }
+        longPressView.setOnCreateContextMenuListener { menu: ContextMenu, v: View, menuInfo: ContextMenuInfo? ->
+            menu.setHeaderTitle(file.displayName)
+            MenuInflater(v.context).inflate(R.menu.conversation_item_actions_file, menu)
+            if (file.status == InteractionStatus.TRANSFER_ONGOING) {
+                menu.findItem(R.id.conv_action_delete).setTitle(android.R.string.cancel)
+                menu.removeItem(R.id.conv_action_download)
+                menu.removeItem(R.id.conv_action_share)
+                menu.removeItem(R.id.conv_action_open)
+            } else {
+                if (!file.isComplete) {
+                    menu.removeItem(R.id.conv_action_download)
+                    menu.removeItem(R.id.conv_action_share)
+                }
+            }
+            conversationFragment.onCreateContextMenu(menu, v, menuInfo)
+        }
+        longPressView.setOnLongClickListener { v: View ->
+            if (type == TransferMsgType.AUDIO || type == TransferMsgType.FILE) {
+                conversationFragment.updatePosition(viewHolder.adapterPosition)
+                longPressView.background.setTint(conversationFragment.resources.getColor(R.color.grey_500))
+            }
+            mCurrentLongItem = RecyclerViewContextMenuInfo(viewHolder.adapterPosition, v.id.toLong())
+            false
+        }
+        if (type == TransferMsgType.IMAGE) {
+            configureImage(viewHolder, path)
+        } else if (type == TransferMsgType.VIDEO) {
+            configureVideo(viewHolder, path)
+        } else if (type == TransferMsgType.AUDIO) {
+            configureAudio(viewHolder, path)
+        } else {
+            val status = file.status
+            if (status.isError) {
+                viewHolder.mIcon.setImageResource(R.drawable.baseline_warning_24)
+            } else {
+                viewHolder.mIcon.setImageResource(R.drawable.baseline_attach_file_24)
+            }
+            viewHolder.mMsgTxt.text = file.displayName
+            if (status == InteractionStatus.TRANSFER_AWAITING_HOST) {
+                viewHolder.btnRefuse.visibility = View.VISIBLE
+                viewHolder.mAnswerLayout.visibility = View.VISIBLE
+                viewHolder.btnAccept.setOnClickListener { presenter.acceptFile(file) }
+                viewHolder.btnRefuse.setOnClickListener { presenter.refuseFile(file) }
+            } else if (status == InteractionStatus.FILE_AVAILABLE) {
+                viewHolder.btnRefuse.visibility = View.GONE
+                viewHolder.mAnswerLayout.visibility = View.VISIBLE
+                viewHolder.btnAccept.setOnClickListener { presenter.acceptFile(file) }
+            } else {
+                viewHolder.mAnswerLayout.visibility = View.GONE
+                if (status == InteractionStatus.TRANSFER_ONGOING) {
+                    viewHolder.progress.max = (file.totalSize / 1024).toInt()
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                        viewHolder.progress.setProgress((file.bytesProgress / 1024).toInt(), true)
+                    } else {
+                        viewHolder.progress.progress = (file.bytesProgress / 1024).toInt()
+                    }
+                    viewHolder.progress.show()
+                } else {
+                    viewHolder.progress.hide()
+                }
+            }
+        }
+    }
+
+    private fun configureForTypingIndicator(viewHolder: ConversationViewHolder) {
+        AnimatedVectorDrawableCompat.create(viewHolder.itemView.context, R.drawable.typing_indicator_animation)?.let { anim ->
+            viewHolder.mStatusIcon.setImageDrawable(anim)
+            anim.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
+                override fun onAnimationEnd(drawable: Drawable) {
+                    anim.start()
+                }
+            })
+            anim.start()
+        }
+    }
+
+    /**
+     * Configures the viewholder to display a classic text message, ie. not a call info text message
+     *
+     * @param convViewHolder The conversation viewHolder
+     * @param interaction    The conversation element to display
+     * @param position       The position of the viewHolder
+     */
+    private fun configureForTextMessage(convViewHolder: ConversationViewHolder, interaction: Interaction, position: Int) {
+        val context = convViewHolder.itemView.context
+        val textMessage = interaction as TextMessage
+        val contact = textMessage.contact  ?: return
+        // Log.w(TAG, "configureForTextMessage " + position + " " + interaction.getDaemonId() + " " + interaction.getStatus());
+        val message = textMessage.body!!.trim { it <= ' ' }
+        val longPressView: View = convViewHolder.mMsgTxt
+        longPressView.background.setTintList(null)
+        longPressView.setOnCreateContextMenuListener { menu: ContextMenu, v: View?, menuInfo: ContextMenuInfo? ->
+            val date = Date(interaction.timestamp)
+            val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT)
+            menu.setHeaderTitle(dateFormat.format(date))
+            conversationFragment.onCreateContextMenu(menu, v!!, menuInfo)
+            val inflater = conversationFragment.requireActivity().menuInflater
+            inflater.inflate(R.menu.conversation_item_actions_messages, menu)
+            if (interaction.status == InteractionStatus.SENDING) {
+                menu.removeItem(R.id.conv_action_delete)
+            } else {
+                menu.findItem(R.id.conv_action_delete).setTitle(R.string.menu_message_delete)
+                menu.removeItem(R.id.conv_action_cancel_message)
+            }
+        }
+        longPressView.setOnLongClickListener { v: View ->
+            if (expandedItemPosition == position) {
+                expandedItemPosition = -1
+            }
+            conversationFragment.updatePosition(convViewHolder.bindingAdapterPosition)
+            if (textMessage.isIncoming) {
+                longPressView.background.setTint(conversationFragment.resources.getColor(R.color.grey_500))
+            } else {
+                longPressView.background.setTint(conversationFragment.resources.getColor(R.color.blue_900))
+            }
+            mCurrentLongItem = RecyclerViewContextMenuInfo(convViewHolder.bindingAdapterPosition, v.id.toLong())
+            false
+        }
+        val isTimeShown = hasPermanentTimeString(textMessage, position)
+        val msgSequenceType = getMsgSequencing(position, isTimeShown)
+        if (StringUtils.isOnlyEmoji(message)) {
+            convViewHolder.mMsgTxt.background.alpha = 0
+            convViewHolder.mMsgTxt.textSize = 32.0f
+            convViewHolder.mMsgTxt.setPadding(0, 0, 0, 0)
+        } else {
+            val resIndex = msgSequenceType.ordinal + (if (textMessage.isIncoming) 1 else 0) * 4
+            convViewHolder.mMsgTxt.background = ContextCompat.getDrawable(context, msgBGLayouts[resIndex])
+            if (convColor != 0 && !textMessage.isIncoming) {
+                convViewHolder.mMsgTxt.background.setTint(convColor)
+            }
+            convViewHolder.mMsgTxt.background.alpha = 255
+            convViewHolder.mMsgTxt.textSize = 16f
+            convViewHolder.mMsgTxt.setPadding(hPadding, vPadding, hPadding, vPadding)
+        }
+        convViewHolder.mMsgTxt.text = message
+        val endOfSeq =
+            msgSequenceType == SequenceType.LAST || msgSequenceType == SequenceType.SINGLE
+        if (textMessage.isIncoming) {
+            if (endOfSeq) {
+                convViewHolder.mAvatar.setImageDrawable(conversationFragment.getConversationAvatar(contact.primaryNumber))
+                convViewHolder.mAvatar.visibility = View.VISIBLE
+            } else {
+                if (position == lastMsgPos - 1 && convViewHolder.mAvatar != null) {
+                    val animation = AnimationUtils.loadAnimation(convViewHolder.mAvatar.context, R.anim.fade_out)
+                    animation.setAnimationListener(object : Animation.AnimationListener {
+                        override fun onAnimationStart(arg0: Animation) {}
+                        override fun onAnimationRepeat(arg0: Animation) {}
+                        override fun onAnimationEnd(arg0: Animation) {
+                            convViewHolder.mAvatar.setImageBitmap(null)
+                            convViewHolder.mAvatar.visibility = View.INVISIBLE
+                        }
+                    })
+                    convViewHolder.mAvatar.startAnimation(animation)
+                } else {
+                    if (convViewHolder.mAvatar != null) {
+                        convViewHolder.mAvatar.setImageBitmap(null)
+                        convViewHolder.mAvatar.visibility = View.INVISIBLE
+                    }
+                }
+            }
+        } else {
+            when (textMessage.status) {
+                InteractionStatus.SENDING -> {
+                    convViewHolder.mStatusIcon.visibility = View.VISIBLE
+                    convViewHolder.mStatusIcon.setImageResource(R.drawable.baseline_circle_24)
+                }
+                InteractionStatus.FAILURE -> {
+                    convViewHolder.mStatusIcon.visibility = View.VISIBLE
+                    convViewHolder.mStatusIcon.setImageResource(R.drawable.round_highlight_off_24)
+                }
+                InteractionStatus.DISPLAYED -> if (lastDisplayedPosition == position) {
+                    convViewHolder.mStatusIcon.visibility = if (mShowReadIndicator) View.VISIBLE else View.GONE
+                    convViewHolder.mStatusIcon.setImageDrawable(conversationFragment.getSmallConversationAvatar(contact.primaryNumber))
+                } else {
+                    convViewHolder.mStatusIcon.visibility = View.GONE
+                    convViewHolder.mStatusIcon.setImageDrawable(null)
+                }
+                else -> if (position == lastOutgoingIndex()) {
+                    convViewHolder.mStatusIcon.visibility = View.VISIBLE
+                    convViewHolder.mStatusIcon.setImageResource(R.drawable.baseline_check_circle_24)
+                    lastDeliveredPosition = position
+                } else {
+                    convViewHolder.mStatusIcon.visibility = View.GONE
+                    convViewHolder.mStatusIcon.setImageDrawable(null)
+                }
+            }
+        }
+        setBottomMargin(convViewHolder.mMsgTxt, if (endOfSeq) 8 else 0)
+        if (isTimeShown) {
+            convViewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe {
+                val timeSeparationString = timestampToDetailString(context, textMessage.timestamp)
+                convViewHolder.mMsgDetailTxtPerm.text = timeSeparationString
+            })
+            convViewHolder.mMsgDetailTxtPerm.visibility = View.VISIBLE
+        } else {
+            convViewHolder.mMsgDetailTxtPerm.visibility = View.GONE
+            val isExpanded = position == expandedItemPosition
+            if (isExpanded) {
+                convViewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe {
+                    val timeSeparationString =
+                        timestampToDetailString(context, textMessage.timestamp)
+                    convViewHolder.mMsgDetailTxt.text = timeSeparationString
+                })
+            }
+            setItemViewExpansionState(convViewHolder, isExpanded)
+            convViewHolder.mItem.setOnClickListener {
+                if (convViewHolder.animator != null && convViewHolder.animator.isRunning) {
+                    return@setOnClickListener
+                }
+                if (expandedItemPosition >= 0) {
+                    val prev = expandedItemPosition
+                    notifyItemChanged(prev)
+                }
+                expandedItemPosition = if (isExpanded) -1 else position
+                notifyItemChanged(expandedItemPosition)
+            }
+        }
+    }
+
+    private fun configureForContactEvent(viewHolder: ConversationViewHolder, interaction: Interaction) {
+        val event = interaction as ContactEvent
+        viewHolder.mMsgTxt.setText(when (event.event) {
+            ContactEvent.Event.ADDED -> R.string.hist_contact_added
+            ContactEvent.Event.INVITED -> R.string.hist_contact_invited
+            ContactEvent.Event.REMOVED -> R.string.hist_contact_left
+            ContactEvent.Event.BANNED -> R.string.hist_contact_banned
+            ContactEvent.Event.INCOMING_REQUEST -> R.string.hist_invitation_received
+            else -> R.string.hist_contact_added
+        })
+        viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe {
+            val timeSeparationString = timestampToDetailString(viewHolder.itemView.context, event.timestamp)
+            viewHolder.mMsgDetailTxt.text = timeSeparationString
+        })
+    }
+
+    /**
+     * Configures the viewholder to display a call info text message, ie. not a classic text message
+     *
+     * @param convViewHolder The conversation viewHolder
+     * @param interaction    The conversation element to display
+     */
+    private fun configureForCallInfo(convViewHolder: ConversationViewHolder, interaction: Interaction) {
+        convViewHolder.mIcon.scaleY = 1f
+        val context = convViewHolder.itemView.context
+        val longPressView: View = convViewHolder.mCallInfoLayout
+        longPressView.background.setTintList(null)
+        longPressView.setOnCreateContextMenuListener { menu: ContextMenu, v: View, menuInfo: ContextMenuInfo? ->
+            conversationFragment.onCreateContextMenu(menu, v, menuInfo)
+            val inflater = conversationFragment.requireActivity().menuInflater
+            inflater.inflate(R.menu.conversation_item_actions_messages, menu)
+            menu.findItem(R.id.conv_action_delete).setTitle(R.string.menu_delete)
+            menu.removeItem(R.id.conv_action_cancel_message)
+            menu.removeItem(R.id.conv_action_copy_text)
+        }
+        longPressView.setOnLongClickListener { v: View ->
+            longPressView.background.setTint(conversationFragment.resources.getColor(R.color.grey_500))
+            conversationFragment.updatePosition(convViewHolder.adapterPosition)
+            mCurrentLongItem = RecyclerViewContextMenuInfo(
+                convViewHolder.adapterPosition, v.id
+                    .toLong()
+            )
+            false
+        }
+        val pictureResID: Int
+        val historyTxt: String
+        val call = interaction as Call
+        if (call.isMissed) {
+            if (call.isIncoming) {
+                pictureResID = R.drawable.baseline_call_missed_24
+            } else {
+                pictureResID = R.drawable.baseline_call_missed_outgoing_24
+                // Flip the photo upside down to show a "missed outgoing call"
+                convViewHolder.mIcon.scaleY = -1f
+            }
+            historyTxt =
+                if (call.isIncoming) context.getString(R.string.notif_missed_incoming_call) else context.getString(
+                    R.string.notif_missed_outgoing_call
+                )
+        } else {
+            pictureResID =
+                if (call.isIncoming) R.drawable.baseline_call_received_24 else R.drawable.baseline_call_made_24
+            historyTxt =
+                if (call.isIncoming) context.getString(R.string.notif_incoming_call) else context.getString(
+                    R.string.notif_outgoing_call
+                )
+        }
+        convViewHolder.mIcon.setImageResource(pictureResID)
+        convViewHolder.mHistTxt.text = historyTxt
+        convViewHolder.mHistDetailTxt.text = DateFormat.getDateTimeInstance()
+            .format(call.timestamp) // start date
+    }
+
+    /**
+     * Computes the string to set in text details between messages, indicating time separation.
+     *
+     * @param timestamp The timestamp used to launch the computation with Date().getTime().
+     * Can be the last received message timestamp for example.
+     * @return The string to display in the text details between messages.
+     */
+    private fun timestampToDetailString(context: Context, timestamp: Long): String {
+        val diff = Date().time - timestamp
+        val timeStr: String = if (diff < DateUtils.WEEK_IN_MILLIS) {
+            if (diff < DateUtils.DAY_IN_MILLIS && DateUtils.isToday(timestamp)) { // 11:32 A.M.
+                DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME)
+            } else {
+                DateUtils.formatDateTime(
+                    context, timestamp,
+                    DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_NO_YEAR or
+                            DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME
+                )
+            }
+        } else if (diff < DateUtils.YEAR_IN_MILLIS) { // JAN. 7, 11:02 A.M.
+            DateUtils.formatDateTime(
+                context, timestamp,
+                DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or
+                        DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME
+            )
+        } else {
+            DateUtils.formatDateTime(
+                context, timestamp,
+                DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or
+                        DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_SHOW_WEEKDAY or
+                        DateUtils.FORMAT_ABBREV_ALL
+            )
+        }
+        return timeStr.uppercase(Locale.getDefault())
+    }
+
+    /**
+     * Helper method to return the previous TextMessage relative to an initial position.
+     *
+     * @param position The initial position
+     * @return the previous TextMessage if any, null otherwise
+     */
+    private fun getPreviousMessageFromPosition(position: Int): Interaction? {
+        return if (mInteractions.isNotEmpty() && position > 0) {
+            mInteractions[position - 1]
+        } else null
+    }
+
+    /**
+     * Helper method to return the next TextMessage relative to an initial position.
+     *
+     * @param position The initial position
+     * @return the next TextMessage if any, null otherwise
+     */
+    private fun getNextMessageFromPosition(position: Int): Interaction? {
+        return if (mInteractions.isNotEmpty() && position < mInteractions.size - 1) {
+            mInteractions[position + 1]
+        } else null
+    }
+
+    private fun isSeqBreak(first: Interaction, second: Interaction): Boolean {
+        return StringUtils.isOnlyEmoji(first.body) != StringUtils.isOnlyEmoji(second.body) || first.isIncoming != second.isIncoming || first.type != Interaction.InteractionType.TEXT || second.type != Interaction.InteractionType.TEXT
+    }
+
+    private fun isAlwaysSingleMsg(msg: Interaction): Boolean {
+        return (msg.type != Interaction.InteractionType.TEXT
+                || StringUtils.isOnlyEmoji(msg.body))
+    }
+
+    private fun getMsgSequencing(i: Int, isTimeShown: Boolean): SequenceType {
+        val msg = mInteractions[i]
+        if (isAlwaysSingleMsg(msg)) {
+            return SequenceType.SINGLE
+        }
+        if (mInteractions.size == 1 || i == 0) {
+            if (mInteractions.size == i + 1) {
+                return SequenceType.SINGLE
+            }
+            val nextMsg = getNextMessageFromPosition(i)
+            if (nextMsg != null) {
+                return if (isSeqBreak(msg, nextMsg) || hasPermanentTimeString(nextMsg, i + 1)) {
+                    SequenceType.SINGLE
+                } else {
+                    SequenceType.FIRST
+                }
+            }
+        } else if (mInteractions.size == i + 1) {
+            val prevMsg = getPreviousMessageFromPosition(i)
+            if (prevMsg != null) {
+                return if (isSeqBreak(msg, prevMsg) || isTimeShown) {
+                    SequenceType.SINGLE
+                } else {
+                    SequenceType.LAST
+                }
+            }
+        }
+        val prevMsg = getPreviousMessageFromPosition(i)
+        val nextMsg = getNextMessageFromPosition(i)
+        if (prevMsg != null && nextMsg != null) {
+            val nextMsgHasTime = hasPermanentTimeString(nextMsg, i + 1)
+            if ((isSeqBreak(msg, prevMsg) || isTimeShown) && !(isSeqBreak(
+                    msg,
+                    nextMsg
+                ) || nextMsgHasTime)
+            ) {
+                return SequenceType.FIRST
+            } else if (!isSeqBreak(msg, prevMsg) && !isTimeShown && isSeqBreak(msg, nextMsg)) {
+                return SequenceType.LAST
+            } else if (!isSeqBreak(msg, prevMsg) && !isTimeShown && !isSeqBreak(msg, nextMsg)) {
+                return if (nextMsgHasTime) SequenceType.LAST else SequenceType.MIDDLE
+            }
+        }
+        return SequenceType.SINGLE
+    }
+
+    private fun setItemViewExpansionState(viewHolder: ConversationViewHolder, expanded: Boolean) {
+        val view: View = viewHolder.mMsgDetailTxt
+        if (viewHolder.animator == null) {
+            if (view.height == 0 && !expanded) {
+                return
+            }
+            viewHolder.animator = ValueAnimator()
+        }
+        if (viewHolder.animator.isRunning) {
+            viewHolder.animator.reverse()
+            return
+        }
+        view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+        viewHolder.animator.setIntValues(0, view.measuredHeight)
+        if (expanded) {
+            view.visibility = View.VISIBLE
+        }
+        viewHolder.animator.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator) {
+                val va = animation as ValueAnimator
+                if (va.animatedValue as Int == 0) {
+                    view.visibility = View.GONE
+                }
+                viewHolder.animator = null
+            }
+        })
+        viewHolder.animator.duration = 200
+        viewHolder.animator.addUpdateListener { animation: ValueAnimator ->
+            view.layoutParams.height = (animation.animatedValue as Int)
+            view.requestLayout()
+        }
+        if (!expanded) {
+            viewHolder.animator.reverse()
+        } else {
+            viewHolder.animator.start()
+        }
+    }
+
+    private fun hasPermanentTimeString(msg: Interaction?, position: Int): Boolean {
+        if (msg == null) {
+            return false
+        }
+        val prevMsg = getPreviousMessageFromPosition(position)
+        return prevMsg != null &&
+                msg.timestamp - prevMsg.timestamp > 10 * DateUtils.MINUTE_IN_MILLIS
+    }
+
+    private fun lastOutgoingIndex(): Int {
+        var i: Int = mInteractions.size - 1
+        while (i >= 0) {
+            if (!mInteractions[i].isIncoming) {
+                break
+            }
+            i--
+        }
+        return i
+    }
+
+    private enum class SequenceType {
+        FIRST, MIDDLE, LAST, SINGLE
+    }
+
+    enum class TransferMsgType {
+        FILE, IMAGE, AUDIO, VIDEO
+    }
+
+    enum class MessageType(@LayoutRes val layout: Int) {
+        INCOMING_FILE(R.layout.item_conv_file_peer),
+        INCOMING_IMAGE(R.layout.item_conv_image_peer),
+        INCOMING_AUDIO(R.layout.item_conv_audio_peer),
+        INCOMING_VIDEO(R.layout.item_conv_video_peer),
+        OUTGOING_FILE(R.layout.item_conv_file_me),
+        OUTGOING_IMAGE(R.layout.item_conv_image_me),
+        OUTGOING_AUDIO(R.layout.item_conv_audio_me),
+        OUTGOING_VIDEO(R.layout.item_conv_video_me),
+        CONTACT_EVENT(R.layout.item_conv_contact),
+        CALL_INFORMATION(R.layout.item_conv_call),
+        INCOMING_TEXT_MESSAGE(R.layout.item_conv_msg_peer),
+        OUTGOING_TEXT_MESSAGE(R.layout.item_conv_msg_me),
+        COMPOSING_INDICATION(R.layout.item_conv_composing),
+        INVALID(-1);
+
+        val isFile: Boolean
+            get() = this == INCOMING_FILE || this == OUTGOING_FILE
+        val isAudio: Boolean
+            get() = this == INCOMING_AUDIO || this == OUTGOING_AUDIO
+        val isVideo: Boolean
+            get() = this == INCOMING_VIDEO || this == OUTGOING_VIDEO
+        val isImage: Boolean
+            get() = this == INCOMING_IMAGE || this == OUTGOING_IMAGE
+        val transferType: TransferMsgType
+            get() = if (isFile) TransferMsgType.FILE else if (isImage) TransferMsgType.IMAGE else if (isAudio) TransferMsgType.AUDIO else if (isVideo) TransferMsgType.VIDEO else TransferMsgType.FILE
+    }
+
+    companion object {
+        private val TAG = ConversationAdapter::class.java.simpleName
+        private val msgBGLayouts = intArrayOf(
+            R.drawable.textmsg_bg_out_first,
+            R.drawable.textmsg_bg_out_middle,
+            R.drawable.textmsg_bg_out_last,
+            R.drawable.textmsg_bg_out,
+            R.drawable.textmsg_bg_in_first,
+            R.drawable.textmsg_bg_in_middle,
+            R.drawable.textmsg_bg_in_last,
+            R.drawable.textmsg_bg_in
+        )
+
+        private fun setBottomMargin(view: View, value: Int) {
+            val targetSize = (value * view.context.resources.displayMetrics.density).toInt()
+            val params = view.layoutParams as MarginLayoutParams
+            params.bottomMargin = targetSize
+        }
+    }
+
+    init {
+        val res = conversationFragment.resources
+        hPadding = res.getDimensionPixelSize(R.dimen.padding_medium)
+        vPadding = res.getDimensionPixelSize(R.dimen.padding_small)
+        mPictureMaxSize = TypedValue.applyDimension(
+            TypedValue.COMPLEX_UNIT_DIP,
+            200f,
+            res.displayMetrics
+        ).toInt()
+        val corner = res.getDimension(R.dimen.conversation_message_radius).toInt()
+        PICTURE_OPTIONS = GlideOptions()
+            .transform(CenterInside())
+            .fitCenter()
+            .override(mPictureMaxSize)
+            .transform(RoundedCorners(corner))
+        timestampUpdateTimer =
+            Observable.interval(10, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
+                .startWithItem(0L)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
deleted file mode 100644
index c83d8a9fe..000000000
--- a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.adapters;
-
-import cx.ring.databinding.ItemSmartlistBinding;
-import cx.ring.databinding.ItemSmartlistHeaderBinding;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.viewholders.SmartListViewHolder;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-import android.os.Parcelable;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.DiffUtil;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SmartListAdapter extends RecyclerView.Adapter<SmartListViewHolder> {
-
-    private List<SmartListViewModel> mSmartListViewModels = new ArrayList<>();
-    private final SmartListViewHolder.SmartListListeners listener;
-    private final CompositeDisposable mDisposable;
-    private RecyclerView recyclerView;
-
-    public SmartListAdapter(List<SmartListViewModel> smartListViewModels, SmartListViewHolder.SmartListListeners listener, CompositeDisposable disposable) {
-        this.listener = listener;
-        mDisposable = disposable;
-        if (smartListViewModels != null)
-            mSmartListViewModels.addAll(smartListViewModels);
-    }
-
-    @NonNull
-    @Override
-    public SmartListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
-        if (viewType == 0) {
-            ItemSmartlistBinding itemBinding = ItemSmartlistBinding.inflate(layoutInflater, parent, false);
-            return new SmartListViewHolder(itemBinding, mDisposable);
-        } else {
-            ItemSmartlistHeaderBinding itemBinding = ItemSmartlistHeaderBinding.inflate(layoutInflater, parent, false);
-            return new SmartListViewHolder(itemBinding, mDisposable);
-        }
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        final SmartListViewModel smartListViewModel = mSmartListViewModels.get(position);
-        return smartListViewModel.getHeaderTitle() == SmartListViewModel.Title.None ? 0 : 1;
-    }
-
-    @Override
-    public void onViewRecycled(@NonNull SmartListViewHolder holder) {
-        super.onViewRecycled(holder);
-        holder.unbind();
-    }
-
-    @Override
-    public void onBindViewHolder(@NonNull SmartListViewHolder holder, int position) {
-        holder.bind(listener, mSmartListViewModels.get(position));
-    }
-
-    @Override
-    public int getItemCount() {
-        return mSmartListViewModels.size();
-    }
-
-    @Override
-    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
-        super.onAttachedToRecyclerView(recyclerView);
-        this.recyclerView = recyclerView;
-    }
-
-    public void update(List<SmartListViewModel> viewModels) {
-        //Log.w("SmartListAdapter", "update " + (viewModels == null ? null : viewModels.size()));
-        final List<SmartListViewModel> old = mSmartListViewModels;
-        mSmartListViewModels = viewModels == null ? new ArrayList<>() : viewModels;
-        if (old != null && viewModels != null) {
-            Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
-            DiffUtil.calculateDiff(new SmartListDiffUtil(old, viewModels))
-                    .dispatchUpdatesTo(this);
-            recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
-        } else {
-            notifyDataSetChanged();
-        }
-    }
-
-    public void update(SmartListViewModel smartListViewModel) {
-        for (int i = 0; i < mSmartListViewModels.size(); i++) {
-            SmartListViewModel old = mSmartListViewModels.get(i);
-            if (old.getContacts() == smartListViewModel.getContacts()) {
-                mSmartListViewModels.set(i, smartListViewModel);
-                notifyItemChanged(i);
-                return;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.kt b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.kt
new file mode 100644
index 000000000..3541baa8c
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListAdapter.kt
@@ -0,0 +1,99 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.adapters
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.databinding.ItemSmartlistBinding
+import cx.ring.databinding.ItemSmartlistHeaderBinding
+import cx.ring.viewholders.SmartListViewHolder
+import cx.ring.viewholders.SmartListViewHolder.SmartListListeners
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.smartlist.SmartListViewModel
+
+class SmartListAdapter(
+    smartListViewModels: List<SmartListViewModel>?,
+    private val listener: SmartListListeners,
+    private val mDisposable: CompositeDisposable
+) : RecyclerView.Adapter<SmartListViewHolder>() {
+    private var mSmartListViewModels: MutableList<SmartListViewModel> = if (smartListViewModels != null) ArrayList(smartListViewModels) else ArrayList()
+    private var recyclerView: RecyclerView? = null
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SmartListViewHolder {
+        val layoutInflater = LayoutInflater.from(parent.context)
+        return if (viewType == 0) {
+            val itemBinding = ItemSmartlistBinding.inflate(layoutInflater, parent, false)
+            SmartListViewHolder(itemBinding, mDisposable)
+        } else {
+            val itemBinding = ItemSmartlistHeaderBinding.inflate(layoutInflater, parent, false)
+            SmartListViewHolder(itemBinding, mDisposable)
+        }
+    }
+
+    override fun getItemViewType(position: Int): Int {
+        val smartListViewModel = mSmartListViewModels[position]
+        return if (smartListViewModel.headerTitle == SmartListViewModel.Title.None) 0 else 1
+    }
+
+    override fun onViewRecycled(holder: SmartListViewHolder) {
+        super.onViewRecycled(holder)
+        holder.unbind()
+    }
+
+    override fun onBindViewHolder(holder: SmartListViewHolder, position: Int) {
+        holder.bind(listener, mSmartListViewModels[position])
+    }
+
+    override fun getItemCount(): Int {
+        return mSmartListViewModels.size
+    }
+
+    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
+        super.onAttachedToRecyclerView(recyclerView)
+        this.recyclerView = recyclerView
+    }
+
+    fun update(viewModels: MutableList<SmartListViewModel>?) {
+        val old: List<SmartListViewModel> = mSmartListViewModels
+        mSmartListViewModels = viewModels ?: ArrayList()
+        if (viewModels != null) {
+            val recyclerViewState = recyclerView?.layoutManager?.onSaveInstanceState()
+            DiffUtil.calculateDiff(SmartListDiffUtil(old, viewModels))
+                .dispatchUpdatesTo(this)
+            recyclerView?.layoutManager?.onRestoreInstanceState(recyclerViewState)
+        } else {
+            notifyDataSetChanged()
+        }
+    }
+
+    fun update(smartListViewModel: SmartListViewModel) {
+        for (i in mSmartListViewModels.indices) {
+            val old = mSmartListViewModels[i]
+            if (old.contacts === smartListViewModel.contacts) {
+                mSmartListViewModels[i] = smartListViewModel
+                notifyItemChanged(i)
+                return
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.java b/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.java
deleted file mode 100644
index f26aff1fb..000000000
--- a/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.adapters;
-
-import androidx.recyclerview.widget.DiffUtil;
-
-import java.util.List;
-
-import net.jami.smartlist.SmartListViewModel;
-
-public class SmartListDiffUtil extends DiffUtil.Callback {
-
-    private final List<SmartListViewModel> mOldList;
-    private final List<SmartListViewModel> mNewList;
-
-    public SmartListDiffUtil(List<SmartListViewModel> oldList, List<SmartListViewModel> newList) {
-        mOldList = oldList;
-        mNewList = newList;
-    }
-
-    @Override
-    public int getOldListSize() {
-        return mOldList.size();
-    }
-
-    @Override
-    public int getNewListSize() {
-        return mNewList.size();
-    }
-
-    @Override
-    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
-        SmartListViewModel oldItem = mOldList.get(oldItemPosition);
-        SmartListViewModel newItem = mNewList.get(newItemPosition);
-        if (newItem.getHeaderTitle() != oldItem.getHeaderTitle())
-            return false;
-        if (newItem.getContacts() != oldItem.getContacts()) {
-            if (newItem.getContacts().size() != oldItem.getContacts().size())
-                return false;
-            for (int i = 0; i < newItem.getContacts().size(); i++) {
-                if (newItem.getContacts().get(i) != oldItem.getContacts().get(i))
-                    return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
-        return mNewList.get(newItemPosition).equals(mOldList.get(oldItemPosition));
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.kt b/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.kt
new file mode 100644
index 000000000..e90336ba1
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/adapters/SmartListDiffUtil.kt
@@ -0,0 +1,53 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.adapters
+
+import net.jami.smartlist.SmartListViewModel
+import androidx.recyclerview.widget.DiffUtil
+
+class SmartListDiffUtil(
+    private val mOldList: List<SmartListViewModel>,
+    private val mNewList: List<SmartListViewModel>
+) : DiffUtil.Callback() {
+    override fun getOldListSize(): Int {
+        return mOldList.size
+    }
+
+    override fun getNewListSize(): Int {
+        return mNewList.size
+    }
+
+    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+        val oldItem = mOldList[oldItemPosition]
+        val newItem = mNewList[newItemPosition]
+        if (newItem.headerTitle != oldItem.headerTitle) return false
+        if (newItem.contacts !== oldItem.contacts) {
+            if (newItem.contacts.size != oldItem.contacts.size) return false
+            for (i in newItem.contacts.indices) {
+                if (newItem.contacts[i] !== oldItem.contacts[i]) return false
+            }
+        }
+        return true
+    }
+
+    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+        return mNewList[newItemPosition] == mOldList[oldItemPosition]
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java b/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
deleted file mode 100644
index 5161c3344..000000000
--- a/ring-android/app/src/main/java/cx/ring/application/JamiApplication.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- *  Copyright (C) 2016-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.application;
-
-import android.app.Activity;
-import android.app.Application;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.system.Os;
-import android.util.Log;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import com.bumptech.glide.Glide;
-
-import net.jami.daemon.JamiService;
-import net.jami.facades.ConversationFacade;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.ContactService;
-import net.jami.services.DaemonService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.PreferencesService;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import cx.ring.BuildConfig;
-import cx.ring.R;
-import cx.ring.views.AvatarFactory;
-import cx.ring.dependencyinjection.DaggerJamiInjectionComponent;
-import cx.ring.dependencyinjection.JamiInjectionComponent;
-import cx.ring.dependencyinjection.JamiInjectionModule;
-import cx.ring.dependencyinjection.ServiceInjectionModule;
-import cx.ring.service.DRingService;
-import cx.ring.service.JamiJobService;
-import cx.ring.utils.AndroidFileUtils;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public abstract class JamiApplication extends Application {
-    private static final String TAG = JamiApplication.class.getSimpleName();
-    public static final String DRING_CONNECTION_CHANGED = BuildConfig.APPLICATION_ID + ".event.DRING_CONNECTION_CHANGE";
-    public static final int PERMISSIONS_REQUEST = 57;
-    private static final IntentFilter RINGER_FILTER = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
-    private static JamiApplication sInstance = null;
-
-    @Inject
-    @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-    @Inject
-    DaemonService mDaemonService;
-    @Inject
-    AccountService mAccountService;
-    @Inject
-    CallService mCallService;
-    //@Inject
-    //ConferenceService mConferenceService;
-    @Inject
-    HardwareService mHardwareService;
-    @Inject
-    PreferencesService mPreferencesService;
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-    @Inject
-    ContactService mContactService;
-
-    private JamiInjectionComponent mJamiInjectionComponent;
-    private final Map<String, Boolean> mPermissionsBeingAsked = new HashMap<>();;
-    private final BroadcastReceiver ringerModeListener = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            ringerModeChanged(intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, AudioManager.RINGER_MODE_NORMAL));
-        }
-    };
-
-    public abstract String getPushToken();
-
-    private boolean mBound = false;
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder s) {
-            Log.d(TAG, "onServiceConnected: " + className.getClassName());
-            mBound = true;
-            // bootstrap Daemon
-            //bootstrapDaemon();
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            Log.d(TAG, "onServiceDisconnected: " + className.getClassName());
-            mBound = false;
-        }
-    };
-
-    private void ringerModeChanged(int newMode) {
-        boolean mute = newMode == AudioManager.RINGER_MODE_VIBRATE || newMode == AudioManager.RINGER_MODE_SILENT;
-        mCallService.muteRingTone(mute);
-    }
-
-    @Override
-    public void onLowMemory() {
-        super.onLowMemory();
-        AvatarFactory.clearCache();
-        Glide.get(this).clearMemory();
-    }
-
-    public void bootstrapDaemon() {
-
-        if (mDaemonService.isStarted()) {
-            return;
-        }
-
-        mExecutor.execute(() -> {
-            try {
-                Log.d(TAG, "bootstrapDaemon: START");
-                if (mDaemonService.isStarted()) {
-                    return;
-                }
-                mDaemonService.startDaemon();
-
-                // Check if the camera hardware feature is available.
-                if (mDeviceRuntimeService.hasVideoPermission()) {
-                    //initVideo is called here to give time to the application to initialize hardware cameras
-                    Log.d(TAG, "bootstrapDaemon: At least one camera available. Initializing video...");
-                    mHardwareService.initVideo()
-                            .onErrorComplete()
-                            .subscribe();
-                } else {
-                    Log.d(TAG, "bootstrapDaemon: No camera available");
-                }
-
-                ringerModeChanged(((AudioManager) getSystemService(Context.AUDIO_SERVICE)).getRingerMode());
-                registerReceiver(ringerModeListener, RINGER_FILTER);
-
-                // load accounts from Daemon
-                mAccountService.loadAccountsFromDaemon(mPreferencesService.hasNetworkConnected());
-
-                if (mPreferencesService.getSettings().isAllowPushNotifications()) {
-                    String token = getPushToken();
-                    if (token != null) {
-                        JamiService.setPushNotificationToken(token);
-                    }
-                } else {
-                    JamiService.setPushNotificationToken("");
-                }
-
-                Intent intent = new Intent(DRING_CONNECTION_CHANGED);
-                intent.putExtra("connected", mDaemonService.isStarted());
-                sendBroadcast(intent);
-
-                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
-                    scheduleRefreshJob();
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "DRingService start failed", e);
-            }
-        });
-    }
-
-    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-    private void scheduleRefreshJob() {
-        JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
-        if (scheduler == null) {
-            Log.e(TAG, "JobScheduler: can't retrieve service");
-            return;
-        }
-        JobInfo.Builder jobBuilder = new JobInfo.Builder(JamiJobService.JOB_ID, new ComponentName(this, JamiJobService.class))
-                .setPersisted(true)
-                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
-        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
-            jobBuilder.setPeriodic(JamiJobService.JOB_INTERVAL, JamiJobService.JOB_FLEX);
-        else
-            jobBuilder.setPeriodic(JamiJobService.JOB_INTERVAL);
-        Log.w(TAG, "JobScheduler: scheduling job");
-        scheduler.schedule(jobBuilder.build());
-    }
-
-    public void terminateDaemon() {
-        Future<Boolean> stopResult = mExecutor.submit(() -> {
-            unregisterReceiver(ringerModeListener);
-            mDaemonService.stopDaemon();
-            Intent intent = new Intent(DRING_CONNECTION_CHANGED);
-            intent.putExtra("connected", mDaemonService.isStarted());
-            sendBroadcast(intent);
-
-            return true;
-        });
-
-        try {
-            stopResult.get();
-        } catch (Exception e) {
-            Log.e(TAG, "DRingService stop failed", e);
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-
-        //RxJavaPlugins.setErrorHandler(e -> Log.e(TAG, "Unhandled RxJava error", e));
-
-        // building injection dependency tree
-        mJamiInjectionComponent = DaggerJamiInjectionComponent.builder()
-                .jamiInjectionModule(new JamiInjectionModule(this))
-                .serviceInjectionModule(new ServiceInjectionModule(this))
-                .build();
-
-        // we can now inject in our self whatever modules define
-        mJamiInjectionComponent.inject(this);
-
-        bootstrapDaemon();
-
-        mPreferencesService.loadDarkMode();
-
-        Completable.fromAction(() -> {
-            File path = AndroidFileUtils.ringtonesPath(this);
-            File defaultRingtone = new File(path, getString(R.string.ringtone_default_name));
-            File defaultLink = new File(path, "default.opus");
-            if (!defaultRingtone.exists()) {
-                AndroidFileUtils.copyAssetFolder(getAssets(), "ringtones", path);
-            }
-            if (!defaultLink.exists()) {
-                Os.symlink(defaultRingtone.getAbsolutePath(), defaultLink.getAbsolutePath());
-            }
-
-            String caRootFile = getString(R.string.ca_root_file);
-            File dest = new File(getFilesDir(), caRootFile);
-            AndroidFileUtils.copyAsset(getAssets(), caRootFile, dest);
-            Os.setenv("CA_ROOT_FILE", dest.getAbsolutePath(), true);
-        })
-        .subscribeOn(Schedulers.io())
-        .subscribe();
-
-        setupActivityListener();
-    }
-
-    public void startDaemon() {
-        if (!DRingService.isRunning) {
-            try {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
-                        && mPreferencesService.getSettings().isAllowPersistentNotification()) {
-                    startForegroundService(new Intent(this, DRingService.class));
-                } else {
-                    startService(new Intent(this, DRingService.class));
-                }
-            } catch (Exception e) {
-                Log.w(TAG, "Error starting daemon service");
-            }
-        }
-        bindDaemon();
-    }
-
-    public void bindDaemon() {
-        if (!mBound) {
-            try {
-                bindService(new Intent(this, DRingService.class), mConnection, BIND_AUTO_CREATE | BIND_IMPORTANT | BIND_ABOVE_CLIENT);
-            } catch (Exception e) {
-                Log.w(TAG, "Error binding daemon service");
-            }
-        }
-    }
-
-    public static JamiApplication getInstance() {
-        return sInstance;
-    }
-
-    @Override
-    public void onTerminate() {
-        super.onTerminate();
-
-        // todo decide when to stop the daemon
-        terminateDaemon();
-        sInstance = null;
-    }
-
-    public JamiInjectionComponent getInjectionComponent() {
-        return mJamiInjectionComponent;
-    }
-
-    public boolean canAskForPermission(String permission) {
-
-        Boolean isBeingAsked = mPermissionsBeingAsked.get(permission);
-
-        if (isBeingAsked != null && isBeingAsked) {
-            return false;
-        }
-
-        mPermissionsBeingAsked.put(permission, true);
-
-        return true;
-    }
-
-    public void permissionHasBeenAsked(String permission) {
-        mPermissionsBeingAsked.remove(permission);
-    }
-
-    public DaemonService getDaemon() {
-        return mDaemonService;
-    }
-
-    public HardwareService getHardwareService() {
-        return mHardwareService;
-    }
-
-    private void setupActivityListener() {
-        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
-
-            @Override
-            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle bundle) {
-                if (mPreferencesService.getSettings().isRecordingBlocked()) {
-                    activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
-                }
-            }
-
-            @Override
-            public void onActivityStarted(@NonNull Activity activity) {}
-
-            @Override
-            public void onActivityResumed(@NonNull Activity activity) {}
-
-            @Override
-            public void onActivityPaused(@NonNull Activity activity) {}
-
-            @Override
-            public void onActivityStopped(@NonNull Activity activity) {}
-
-            @Override
-            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle bundle) {}
-
-            @Override
-            public void onActivityDestroyed(@NonNull Activity activity) {}
-        });
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/application/JamiApplication.kt b/ring-android/app/src/main/java/cx/ring/application/JamiApplication.kt
new file mode 100644
index 000000000..d01769276
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/application/JamiApplication.kt
@@ -0,0 +1,277 @@
+/*
+ *  Copyright (C) 2016-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.application
+
+import android.app.Activity
+import android.app.Application
+import android.app.job.JobInfo
+import android.app.job.JobScheduler
+import android.content.*
+import android.media.AudioManager
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
+import android.system.Os
+import android.util.Log
+import android.view.WindowManager
+import androidx.annotation.RequiresApi
+import com.bumptech.glide.Glide
+import cx.ring.BuildConfig
+import cx.ring.R
+import cx.ring.service.DRingService
+import cx.ring.service.JamiJobService
+import cx.ring.utils.AndroidFileUtils
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.daemon.JamiService
+import net.jami.services.*
+import java.io.File
+import java.util.concurrent.ScheduledExecutorService
+import javax.inject.Inject
+import javax.inject.Named
+
+abstract class JamiApplication : Application() {
+    companion object {
+        private val TAG = JamiApplication::class.java.simpleName
+        const val DRING_CONNECTION_CHANGED = BuildConfig.APPLICATION_ID + ".event.DRING_CONNECTION_CHANGE"
+        const val PERMISSIONS_REQUEST = 57
+        private val RINGER_FILTER = IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION)
+        @JvmStatic var instance: JamiApplication? = null
+    }
+
+    @Inject
+    @Named("DaemonExecutor") lateinit
+    var mExecutor: ScheduledExecutorService
+
+    @Inject lateinit
+    var daemon: DaemonService
+
+    @Inject lateinit
+    var mAccountService: AccountService
+
+    @Inject lateinit
+    var mCallService: CallService
+
+    //@Inject
+    //ConferenceService mConferenceService;
+    @Inject lateinit
+    var hardwareService: HardwareService
+
+    @Inject lateinit
+    var mPreferencesService: PreferencesService
+
+    @Inject lateinit
+    var mDeviceRuntimeService: DeviceRuntimeService
+
+    @Inject lateinit
+    var mContactService: ContactService
+
+    private val ringerModeListener: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            ringerModeChanged(intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, AudioManager.RINGER_MODE_NORMAL))
+        }
+    }
+    abstract val pushToken: String?
+    
+    private var mBound = false
+    private val mConnection: ServiceConnection = object : ServiceConnection {
+        override fun onServiceConnected(className: ComponentName, s: IBinder) {
+            Log.d(TAG, "onServiceConnected: " + className.className)
+            mBound = true
+            // bootstrap Daemon
+            //bootstrapDaemon();
+        }
+
+        override fun onServiceDisconnected(className: ComponentName) {
+            Log.d(TAG, "onServiceDisconnected: " + className.className)
+            mBound = false
+        }
+    }
+
+    private fun ringerModeChanged(newMode: Int) {
+        val mute = newMode == AudioManager.RINGER_MODE_VIBRATE || newMode == AudioManager.RINGER_MODE_SILENT
+        mCallService.muteRingTone(mute)
+    }
+
+    override fun onLowMemory() {
+        super.onLowMemory()
+        AvatarFactory.clearCache()
+        Glide.get(this).clearMemory()
+    }
+
+    fun bootstrapDaemon() {
+        if (daemon.isStarted) {
+            return
+        }
+        Log.d(TAG, "bootstrapDaemon")
+        mExecutor.execute {
+            try {
+                Log.d(TAG, "bootstrapDaemon: START")
+                if (daemon.isStarted) {
+                    return@execute
+                }
+                daemon.startDaemon()
+
+                // Check if the camera hardware feature is available.
+                if (mDeviceRuntimeService.hasVideoPermission()) {
+                    //initVideo is called here to give time to the application to initialize hardware cameras
+                    Log.d(TAG, "bootstrapDaemon: At least one camera available. Initializing video...")
+                    hardwareService.initVideo()
+                            .onErrorComplete()
+                            .subscribe()
+                } else {
+                    Log.d(TAG, "bootstrapDaemon: No camera available")
+                }
+                ringerModeChanged((getSystemService(AUDIO_SERVICE) as AudioManager).ringerMode)
+                registerReceiver(ringerModeListener, RINGER_FILTER)
+
+                // load accounts from Daemon
+                mAccountService.loadAccountsFromDaemon(mPreferencesService.hasNetworkConnected())
+                if (mPreferencesService.settings.isAllowPushNotifications) {
+                    pushToken?.let { token -> JamiService.setPushNotificationToken(token) }
+                } else {
+                    JamiService.setPushNotificationToken("")
+                }
+                val intent = Intent(DRING_CONNECTION_CHANGED)
+                intent.putExtra("connected", daemon.isStarted)
+                sendBroadcast(intent)
+                scheduleRefreshJob()
+            } catch (e: Exception) {
+                Log.e(TAG, "DRingService start failed", e)
+            }
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+    private fun scheduleRefreshJob() {
+        val scheduler = getSystemService(JOB_SCHEDULER_SERVICE) as JobScheduler
+        val jobBuilder = JobInfo.Builder(JamiJobService.JOB_ID, ComponentName(this, JamiJobService::class.java))
+                .setPersisted(true)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) jobBuilder.setPeriodic(JamiJobService.JOB_INTERVAL, JamiJobService.JOB_FLEX) else jobBuilder.setPeriodic(JamiJobService.JOB_INTERVAL)
+        Log.w(TAG, "JobScheduler: scheduling job")
+        scheduler.schedule(jobBuilder.build())
+    }
+
+    private fun terminateDaemon() {
+        val stopResult = mExecutor.submit<Boolean> {
+            unregisterReceiver(ringerModeListener)
+            daemon.stopDaemon()
+            val intent = Intent(DRING_CONNECTION_CHANGED)
+            intent.putExtra("connected", daemon.isStarted)
+            sendBroadcast(intent)
+            true
+        }
+        try {
+            stopResult.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "DRingService stop failed", e)
+        }
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        instance = this
+
+        //RxJavaPlugins.setErrorHandler(e -> Log.e(TAG, "Unhandled RxJava error", e));
+
+        // building injection dependency tree
+        /*injectionComponent = DaggerJamiInjectionComponent.builder()
+                .jamiInjectionModule(JamiInjectionModule(this))
+                .serviceInjectionModule(ServiceInjectionModule(this))
+                .build()
+
+        // we can now inject in our self whatever modules define
+        injectionComponent!!.inject(this)*/
+        bootstrapDaemon()
+        mPreferencesService.loadDarkMode()
+        Completable.fromAction {
+            val path = AndroidFileUtils.ringtonesPath(this)
+            val defaultRingtone = File(path, getString(R.string.ringtone_default_name))
+            val defaultLink = File(path, "default.opus")
+            if (!defaultRingtone.exists()) {
+                AndroidFileUtils.copyAssetFolder(assets, "ringtones", path)
+            }
+            if (!defaultLink.exists()) {
+                Os.symlink(defaultRingtone.absolutePath, defaultLink.absolutePath)
+            }
+            val caRootFile = getString(R.string.ca_root_file)
+            val dest = File(filesDir, caRootFile)
+            AndroidFileUtils.copyAsset(assets, caRootFile, dest)
+            Os.setenv("CA_ROOT_FILE", dest.absolutePath, true)
+        }
+                .subscribeOn(Schedulers.io())
+                .subscribe()
+        setupActivityListener()
+    }
+
+    fun startDaemon() {
+        if (!DRingService.isRunning) {
+            try {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                        && mPreferencesService.settings.isAllowPersistentNotification) {
+                    startForegroundService(Intent(this, DRingService::class.java))
+                } else {
+                    startService(Intent(this, DRingService::class.java))
+                }
+            } catch (e: Exception) {
+                Log.w(TAG, "Error starting daemon service")
+            }
+        }
+        bindDaemon()
+    }
+
+    fun bindDaemon() {
+        if (!mBound) {
+            try {
+                bindService(Intent(this, DRingService::class.java), mConnection, BIND_AUTO_CREATE or BIND_IMPORTANT or BIND_ABOVE_CLIENT)
+            } catch (e: Exception) {
+                Log.w(TAG, "Error binding daemon service")
+            }
+        }
+    }
+
+    override fun onTerminate() {
+        super.onTerminate()
+
+        // todo decide when to stop the daemon
+        terminateDaemon()
+        instance = null
+    }
+
+    private fun setupActivityListener() {
+        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
+            override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
+                if (mPreferencesService.settings.isRecordingBlocked) {
+                    activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
+                }
+            }
+
+            override fun onActivityStarted(activity: Activity) {}
+            override fun onActivityResumed(activity: Activity) {}
+            override fun onActivityPaused(activity: Activity) {}
+            override fun onActivityStopped(activity: Activity) {}
+            override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
+            override fun onActivityDestroyed(activity: Activity) {}
+        })
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.java b/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.java
deleted file mode 100644
index 99f8ff951..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.client;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.RelativeLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import net.jami.model.Account;
-
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.databinding.ItemToolbarSelectedBinding;
-import cx.ring.databinding.ItemToolbarSpinnerBinding;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class AccountSpinnerAdapter extends ArrayAdapter<Account> {
-    private static final String TAG = AccountSpinnerAdapter.class.getSimpleName();
-    public static final int TYPE_ACCOUNT = 0;
-    public static final int TYPE_CREATE_JAMI = 1;
-    public static final int TYPE_CREATE_SIP = 2;
-    private final LayoutInflater mInflater;
-    private final int logoSize;
-
-    public AccountSpinnerAdapter(@NonNull Context context, List<Account> accounts){
-        super(context, R.layout.item_toolbar_spinner, accounts);
-        mInflater = LayoutInflater.from(context);
-        logoSize = context.getResources().getDimensionPixelSize(R.dimen.list_medium_icon_size);
-    }
-
-    @NonNull
-    @Override
-    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
-        int type = getItemViewType(position);
-        ViewHolderHeader holder;
-        if (convertView == null) {
-            holder = new ViewHolderHeader();
-            holder.binding = ItemToolbarSelectedBinding.inflate(mInflater, parent, false);
-            convertView = holder.binding.getRoot();
-            convertView.setTag(holder);
-        } else {
-            holder = (ViewHolderHeader) convertView.getTag();
-            holder.loader.clear();
-        }
-
-        if (type == TYPE_ACCOUNT) {
-            Account account = getItem(position);
-            holder.loader.add(AvatarDrawable.load(getContext(), account)
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(avatar -> holder.binding.logo.setImageDrawable(avatar), e -> Log.e(TAG, "Error loading avatar", e)));
-            holder.loader.add(account.getAccountAlias()
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(alias -> holder.binding.title.setText(alias), e -> Log.e(TAG, "Error loading title", e)));
-        }
-        return convertView;
-    }
-
-    @Override
-    public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
-        int type = getItemViewType(position);
-        ViewHolder holder;
-        View rowView = convertView;
-        if (rowView == null) {
-            holder = new ViewHolder();
-            holder.binding = ItemToolbarSpinnerBinding.inflate(mInflater, parent, false);
-            rowView = holder.binding.getRoot();
-            rowView.setTag(holder);
-        } else {
-            holder = (ViewHolder) rowView.getTag();
-            holder.loader.clear();
-        }
-
-        holder.binding.logo.setVisibility(View.VISIBLE);
-        ViewGroup.LayoutParams logoParam = holder.binding.logo.getLayoutParams();
-        if (type == TYPE_ACCOUNT) {
-            Account account = getItem(position);
-            CharSequence ip2ipString = rowView.getContext().getString(R.string.account_type_ip2ip);
-            holder.loader.add(account.getAccountAlias()
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(alias -> {
-                        String subtitle = getUri(account, ip2ipString);
-                        holder.binding.title.setText(alias);
-                        if (alias.equals(subtitle)) {
-                            holder.binding.subtitle.setVisibility(View.GONE);
-                        } else {
-                            holder.binding.subtitle.setVisibility(View.VISIBLE);
-                            holder.binding.subtitle.setText(subtitle);
-                        }
-                    }));
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.binding.title.getLayoutParams();
-            params.removeRule(RelativeLayout.CENTER_VERTICAL);
-            holder.binding.title.setLayoutParams(params);
-            logoParam.width = logoSize;
-            logoParam.height = logoSize;
-            holder.binding.logo.setLayoutParams(logoParam);
-            holder.loader.add(AvatarDrawable.load(getContext(), account)
-                    .subscribeOn(Schedulers.io())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(avatar -> holder.binding.logo.setImageDrawable(avatar), e -> Log.e(TAG, "Error loading avatar", e)));
-        } else {
-            if (type == TYPE_CREATE_JAMI)
-                holder.binding.title.setText(R.string.add_ring_account_title);
-            else
-                holder.binding.title.setText(R.string.add_sip_account_title);
-            holder.binding.subtitle.setVisibility(View.GONE);
-            holder.binding.logo.setImageResource(R.drawable.baseline_add_24);
-            logoParam.width = ViewGroup.LayoutParams.WRAP_CONTENT;
-            logoParam.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-            holder.binding.logo.setLayoutParams(logoParam);
-
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.binding.title.getLayoutParams();
-            params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
-            holder.binding.title.setLayoutParams(params);
-        }
-
-        return rowView;
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (position == super.getCount()) {
-            return TYPE_CREATE_JAMI;
-        }
-        if (position == super.getCount() + 1) {
-            return TYPE_CREATE_SIP;
-        }
-        return TYPE_ACCOUNT;
-    }
-
-    @Override
-    public int getCount() {
-        return super.getCount() + 2;
-    }
-
-    private static class ViewHolder {
-        ItemToolbarSpinnerBinding binding;
-        final CompositeDisposable loader = new CompositeDisposable();
-    }
-    private static class ViewHolderHeader {
-        ItemToolbarSelectedBinding binding;
-        final CompositeDisposable loader = new CompositeDisposable();
-    }
-
-    private String getUri(Account account, CharSequence defaultNameSip) {
-        if (account.isIP2IP()) {
-            return defaultNameSip.toString();
-        }
-        return account.getDisplayUri();
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt b/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt
new file mode 100644
index 000000000..4565d7ed3
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt
@@ -0,0 +1,156 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.client
+
+import android.content.Context
+import android.util.Log
+import android.widget.ArrayAdapter
+import cx.ring.R
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import cx.ring.views.AvatarDrawable
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import android.widget.RelativeLayout
+import cx.ring.databinding.ItemToolbarSelectedBinding
+import cx.ring.databinding.ItemToolbarSpinnerBinding
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Account
+
+class AccountSpinnerAdapter(context: Context, accounts: List<Account>) :
+    ArrayAdapter<Account>(context, R.layout.item_toolbar_spinner, accounts) {
+    private val mInflater: LayoutInflater = LayoutInflater.from(context)
+    private val logoSize: Int = context.resources.getDimensionPixelSize(R.dimen.list_medium_icon_size)
+
+    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+        var view = convertView
+        val type = getItemViewType(position)
+        val holder: ViewHolderHeader
+        if (view == null) {
+            holder = ViewHolderHeader(ItemToolbarSelectedBinding.inflate(mInflater, parent, false))
+            view = holder.binding.root
+            view.setTag(holder)
+        } else {
+            holder = view.tag as ViewHolderHeader
+            holder.loader.clear()
+        }
+        if (type == TYPE_ACCOUNT) {
+            val account = getItem(position)!!
+            holder.loader.add(AvatarDrawable.load(context, account)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ avatar -> holder.binding.logo.setImageDrawable(avatar)
+                }) { e: Throwable -> Log.e(TAG, "Error loading avatar", e) })
+            holder.loader.add(account.accountAlias
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe({ alias -> holder.binding.title.text = alias.ifEmpty { context.getString(R.string.ring_account) }
+                    }) { e: Throwable -> Log.e(TAG, "Error loading title", e) })
+        }
+        return view
+    }
+
+    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+        val type = getItemViewType(position)
+        val holder: ViewHolder
+        var rowView = convertView
+        if (rowView == null) {
+            holder = ViewHolder(ItemToolbarSpinnerBinding.inflate(mInflater, parent, false))
+            rowView = holder.binding.root
+            rowView.setTag(holder)
+        } else {
+            holder = rowView.tag as ViewHolder
+            holder.loader.clear()
+        }
+        holder.binding.logo.visibility = View.VISIBLE
+        val logoParam = holder.binding.logo.layoutParams
+        if (type == TYPE_ACCOUNT) {
+            val account = getItem(position)!!
+            val ip2ipString = rowView.context.getString(R.string.account_type_ip2ip)
+            holder.loader.add(account.accountAlias
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe { alias ->
+                        val subtitle = getUri(account, ip2ipString)
+                        holder.binding.title.text = alias.ifEmpty { context.getString(R.string.ring_account) }
+                        if (alias == subtitle) {
+                            holder.binding.subtitle.visibility = View.GONE
+                        } else {
+                            holder.binding.subtitle.visibility = View.VISIBLE
+                            holder.binding.subtitle.text = subtitle
+                        }
+                    })
+            val params = holder.binding.title.layoutParams as RelativeLayout.LayoutParams
+            params.removeRule(RelativeLayout.CENTER_VERTICAL)
+            holder.binding.title.layoutParams = params
+            logoParam.width = logoSize
+            logoParam.height = logoSize
+            holder.binding.logo.layoutParams = logoParam
+            holder.loader.add(AvatarDrawable.load(context, account)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ avatar -> holder.binding.logo.setImageDrawable(avatar) })
+                { e -> Log.e(TAG, "Error loading avatar", e) })
+        } else {
+            holder.binding.title.setText(
+                if (type == TYPE_CREATE_JAMI) R.string.add_ring_account_title else R.string.add_sip_account_title)
+
+            holder.binding.subtitle.visibility = View.GONE
+            holder.binding.logo.setImageResource(R.drawable.baseline_add_24)
+            logoParam.width = ViewGroup.LayoutParams.WRAP_CONTENT
+            logoParam.height = ViewGroup.LayoutParams.WRAP_CONTENT
+            holder.binding.logo.layoutParams = logoParam
+            val params = holder.binding.title.layoutParams as RelativeLayout.LayoutParams
+            params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE)
+            holder.binding.title.layoutParams = params
+        }
+        return rowView
+    }
+
+    override fun getItemViewType(position: Int): Int {
+        if (position == super.getCount()) {
+            return TYPE_CREATE_JAMI
+        }
+        return if (position == super.getCount() + 1) {
+            TYPE_CREATE_SIP
+        } else TYPE_ACCOUNT
+    }
+
+    override fun getCount(): Int {
+        return super.getCount() + 2
+    }
+
+    private class ViewHolder(val binding: ItemToolbarSpinnerBinding) {
+        val loader = CompositeDisposable()
+    }
+
+    private class ViewHolderHeader(val binding: ItemToolbarSelectedBinding) {
+        val loader = CompositeDisposable()
+    }
+
+    private fun getUri(account: Account, defaultNameSip: CharSequence): String {
+        return if (account.isIP2IP) defaultNameSip.toString() else account.displayUri!!
+    }
+
+    companion object {
+        private val TAG = AccountSpinnerAdapter::class.simpleName!!
+        const val TYPE_ACCOUNT = 0
+        const val TYPE_CREATE_JAMI = 1
+        const val TYPE_CREATE_SIP = 2
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
deleted file mode 100644
index 7f6e7af2a..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.fragment.app.Fragment;
-
-import cx.ring.BuildConfig;
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.fragments.CallFragment;
-import net.jami.services.NotificationService;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.KeyboardVisibilityManager;
-import cx.ring.utils.MediaButtonsHelper;
-
-public class CallActivity extends AppCompatActivity {
-    public static final String ACTION_CALL = BuildConfig.APPLICATION_ID + ".action.call";
-    public static final String ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT";
-
-    private static final String CALL_FRAGMENT_TAG = "CALL_FRAGMENT_TAG";
-
-    /* result code sent in case of call failure */
-    public static int RESULT_FAILURE = -10;
-    private View mMainView;
-    private Handler handler;
-    private int currentOrientation = Configuration.ORIENTATION_PORTRAIT;
-    private boolean dimmed = false;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        JamiApplication.getInstance().startDaemon();
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
-            setTurnScreenOn(true);
-            setShowWhenLocked(true);
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        } else {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED|
-                    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON|
-                    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        }
-
-        setContentView(R.layout.activity_call_layout);
-        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
-
-        handler = new Handler(Looper.getMainLooper());
-
-        mMainView = findViewById(R.id.main_call_layout);
-        mMainView.setOnClickListener(v -> {
-            dimmed = !dimmed;
-            if (dimmed) {
-                hideSystemUI();
-            } else {
-                showSystemUI();
-            }
-        });
-
-        Intent intent = getIntent();
-        if(intent != null)
-            handleNewIntent(intent);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        restartNoInteractionTimer();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        if (handler != null) {
-            handler.removeCallbacks(onNoInteraction);
-        }
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        setIntent(intent);
-        handleNewIntent(intent);
-    }
-
-    private void handleNewIntent(Intent intent) {
-        String action = intent.getAction();
-        if (Intent.ACTION_CALL.equals(action) || ACTION_CALL.equals(action)) {
-            boolean audioOnly = intent.getBooleanExtra(CallFragment.KEY_AUDIO_ONLY, true);
-            String contactId = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
-            CallFragment callFragment = CallFragment.newInstance(CallFragment.ACTION_PLACE_CALL,
-                    ConversationPath.fromIntent(intent),
-                    contactId,
-                    audioOnly);
-            getSupportFragmentManager().beginTransaction().replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit();
-        } else if (Intent.ACTION_VIEW.equals(action) || ACTION_CALL_ACCEPT.equals(action)) {
-            String confId = intent.getStringExtra(NotificationService.KEY_CALL_ID);
-            CallFragment callFragment = CallFragment.newInstance(Intent.ACTION_VIEW.equals(action) ? CallFragment.ACTION_GET_CALL : ACTION_CALL_ACCEPT, confId);
-            getSupportFragmentManager().beginTransaction().replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit();
-        }
-    }
-
-    private final Runnable onNoInteraction = () -> {
-        if (!dimmed) {
-            dimmed = true;
-            hideSystemUI();
-        }
-    };
-
-    public void restartNoInteractionTimer() {
-        if (handler != null) {
-            handler.removeCallbacks(onNoInteraction);
-            handler.postDelayed(onNoInteraction, 4 * 1000);
-        }
-    }
-
-    @Override
-    public void onUserLeaveHint() {
-        CallFragment callFragment = getCallFragment();
-        if (callFragment != null) {
-            callFragment.onUserLeave();
-        }
-    }
-
-    @Override
-    public void onUserInteraction() {
-        restartNoInteractionTimer();
-    }
-
-    @Override
-    public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        if (currentOrientation != newConfig.orientation) {
-            currentOrientation = newConfig.orientation;
-            if (dimmed)
-                hideSystemUI();
-            else
-                showSystemUI();
-        } else {
-            restartNoInteractionTimer();
-        }
-        super.onConfigurationChanged(newConfig);
-    }
-
-    private void hideSystemUI() {
-        KeyboardVisibilityManager.hideKeyboard(this);
-        if (mMainView != null) {
-            mMainView.setSystemUiVisibility(
-                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE
-                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
-                            | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
-                            | View.SYSTEM_UI_FLAG_IMMERSIVE);
-
-            CallFragment callFragment = getCallFragment();
-            if(callFragment != null && !callFragment.isChoosePluginMode()) {
-                callFragment.toggleVideoPluginsCarousel(false);
-            }
-            if (handler != null)
-                handler.removeCallbacks(onNoInteraction);
-        }
-    }
-
-    public void showSystemUI() {
-        if (mMainView != null) {
-            mMainView.setSystemUiVisibility(
-                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LOW_PROFILE
-                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                            | View.SYSTEM_UI_FLAG_IMMERSIVE);
-
-            CallFragment callFragment = getCallFragment();
-            if(callFragment != null) {
-                callFragment.toggleVideoPluginsCarousel(true);
-            }
-            restartNoInteractionTimer();
-        }
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        CallFragment callFragment = getCallFragment();
-        if (callFragment != null) {
-            return MediaButtonsHelper.handleMediaKeyCode(keyCode, callFragment)
-                    || super.onKeyDown(keyCode, event);
-        }
-
-        return super.onKeyDown(keyCode, event);
-    }
-
-    private CallFragment getCallFragment() {
-        CallFragment callFragment = null;
-        // Get the call Fragment
-        Fragment fragment = getSupportFragmentManager().findFragmentByTag(CALL_FRAGMENT_TAG);
-        if (fragment instanceof CallFragment) {
-            callFragment = (CallFragment) fragment;
-        }
-        return callFragment;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.kt b/ring-android/app/src/main/java/cx/ring/client/CallActivity.kt
new file mode 100644
index 000000000..c75d471ea
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.kt
@@ -0,0 +1,215 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import cx.ring.utils.ConversationPath.Companion.fromIntent
+import dagger.hilt.android.AndroidEntryPoint
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.os.Build
+import android.view.WindowManager
+import cx.ring.R
+import android.media.AudioManager
+import android.os.Looper
+import android.content.Intent
+import android.content.res.Configuration
+import android.os.Handler
+import android.view.KeyEvent
+import android.view.View
+import cx.ring.BuildConfig
+import cx.ring.application.JamiApplication
+import cx.ring.fragments.CallFragment
+import net.jami.services.NotificationService
+import cx.ring.utils.KeyboardVisibilityManager
+import cx.ring.utils.MediaButtonsHelper
+
+@AndroidEntryPoint
+class CallActivity : AppCompatActivity() {
+    private var mMainView: View? = null
+    private var handler: Handler? = null
+    private var currentOrientation = Configuration.ORIENTATION_PORTRAIT
+    private var dimmed = false
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        JamiApplication.instance?.startDaemon()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+            setTurnScreenOn(true)
+            setShowWhenLocked(true)
+            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+        } else {
+            window.addFlags(
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+                        WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
+                        WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+            )
+        }
+        setContentView(R.layout.activity_call_layout)
+        volumeControlStream = AudioManager.STREAM_VOICE_CALL
+        handler = Handler(Looper.getMainLooper())
+        mMainView = findViewById<View>(R.id.main_call_layout)?.apply {
+            setOnClickListener {
+                dimmed = !dimmed
+                if (dimmed) {
+                    hideSystemUI()
+                } else {
+                    showSystemUI()
+                }
+            }
+        }
+        intent?.let { handleNewIntent(it) }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        restartNoInteractionTimer()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (handler != null) {
+            handler!!.removeCallbacks(onNoInteraction)
+        }
+    }
+
+    public override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        setIntent(intent)
+        handleNewIntent(intent)
+    }
+
+    private fun handleNewIntent(intent: Intent) {
+        val action = intent.action
+        if (Intent.ACTION_CALL == action || ACTION_CALL == action) {
+            val audioOnly = intent.getBooleanExtra(CallFragment.KEY_AUDIO_ONLY, true)
+            val contactId = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
+            val callFragment = CallFragment.newInstance(
+                CallFragment.ACTION_PLACE_CALL,
+                fromIntent(intent),
+                contactId,
+                audioOnly
+            )
+            supportFragmentManager.beginTransaction()
+                .replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit()
+        } else if (Intent.ACTION_VIEW == action || ACTION_CALL_ACCEPT == action) {
+            val confId = intent.getStringExtra(NotificationService.KEY_CALL_ID)
+            val callFragment = CallFragment.newInstance(
+                if (Intent.ACTION_VIEW == action) CallFragment.ACTION_GET_CALL else ACTION_CALL_ACCEPT,
+                confId
+            )
+            supportFragmentManager.beginTransaction()
+                .replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit()
+        }
+    }
+
+    private val onNoInteraction = Runnable {
+        if (!dimmed) {
+            dimmed = true
+            hideSystemUI()
+        }
+    }
+
+    private fun restartNoInteractionTimer() {
+        if (handler != null) {
+            handler!!.removeCallbacks(onNoInteraction)
+            handler!!.postDelayed(onNoInteraction, (4 * 1000).toLong())
+        }
+    }
+
+    public override fun onUserLeaveHint() {
+        val callFragment = callFragment
+        callFragment?.onUserLeave()
+    }
+
+    override fun onUserInteraction() {
+        restartNoInteractionTimer()
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        if (currentOrientation != newConfig.orientation) {
+            currentOrientation = newConfig.orientation
+            if (dimmed) hideSystemUI() else showSystemUI()
+        } else {
+            restartNoInteractionTimer()
+        }
+        super.onConfigurationChanged(newConfig)
+    }
+
+    private fun hideSystemUI() {
+        KeyboardVisibilityManager.hideKeyboard(this)
+        if (mMainView != null) {
+            mMainView!!.systemUiVisibility =
+                (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LOW_PROFILE
+                        or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                        or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
+                        or View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
+                        or View.SYSTEM_UI_FLAG_IMMERSIVE)
+            val callFragment = callFragment
+            if (callFragment != null && !callFragment.isChoosePluginMode) {
+                callFragment.toggleVideoPluginsCarousel(false)
+            }
+            if (handler != null) handler!!.removeCallbacks(onNoInteraction)
+        }
+    }
+
+    fun showSystemUI() {
+        if (mMainView != null) {
+            mMainView!!.systemUiVisibility =
+                (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LOW_PROFILE
+                        or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                        or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                        or View.SYSTEM_UI_FLAG_IMMERSIVE)
+            val callFragment = callFragment
+            callFragment?.toggleVideoPluginsCarousel(true)
+            restartNoInteractionTimer()
+        }
+    }
+
+    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+        val callFragment = callFragment
+        return if (callFragment != null) {
+            (MediaButtonsHelper.handleMediaKeyCode(keyCode, callFragment)
+                    || super.onKeyDown(keyCode, event))
+        } else super.onKeyDown(keyCode, event)
+    }
+
+    // Get the call Fragment
+    private val callFragment: CallFragment?
+        get() {
+            var callFragment: CallFragment? = null
+            // Get the call Fragment
+            val fragment = supportFragmentManager.findFragmentByTag(CALL_FRAGMENT_TAG)
+            if (fragment is CallFragment) {
+                callFragment = fragment
+            }
+            return callFragment
+        }
+
+    companion object {
+        const val ACTION_CALL = BuildConfig.APPLICATION_ID + ".action.call"
+        const val ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT"
+        private const val CALL_FRAGMENT_TAG = "CALL_FRAGMENT_TAG"
+
+        /* result code sent in case of call failure */
+        var RESULT_FAILURE = -10
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.java b/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.java
deleted file mode 100644
index a985f0f68..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package cx.ring.client;
-
-import android.content.res.ColorStateList;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.widget.ImageViewCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import cx.ring.R;
-
-public class ColorChooserBottomSheet extends BottomSheetDialogFragment {
-
-    private static final int[] colors = {
-            R.color.pink_500,
-            R.color.purple_500, R.color.deep_purple_500,
-            R.color.indigo_500, R.color.blue_500,
-            R.color.cyan_500, R.color.teal_500,
-            R.color.green_500, R.color.light_green_500,
-            R.color.grey_500, R.color.lime_500,
-            R.color.amber_500, R.color.deep_orange_500,
-            R.color.brown_500, R.color.blue_grey_500
-    };
-
-    interface IColorSelected {
-        void onColorSelected(int color);
-    }
-
-    private IColorSelected callback;
-
-    public void setCallback(IColorSelected cb) {
-        callback = cb;
-    }
-
-    private class ColorView extends RecyclerView.ViewHolder {
-        ImageView view;
-        int color;
-        ColorView(@NonNull View itemView) {
-            super(itemView);
-            view = (ImageView) itemView;
-            itemView.setOnClickListener(v -> {
-                if (callback != null)
-                    callback.onColorSelected(color);
-                dismiss();
-            });
-        }
-    }
-
-    class ColorAdapter extends RecyclerView.Adapter<ColorView>  {
-        @NonNull
-        @Override
-        public ColorView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_color, parent, false);
-            return new ColorView(v);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull ColorView holder, int position) {
-            int color = colors[position];
-            holder.color = getResources().getColor(color);
-            ImageViewCompat.setImageTintList(holder.view, ColorStateList.valueOf(holder.color));
-        }
-
-        @Override
-        public int getItemCount() {
-            return colors.length;
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        RecyclerView view = (RecyclerView) inflater.inflate(R.layout.frag_color_chooser, container);
-        view.setAdapter(new ColorAdapter());
-        return view;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.kt b/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.kt
new file mode 100644
index 000000000..05f076a52
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/ColorChooserBottomSheet.kt
@@ -0,0 +1,76 @@
+package cx.ring.client
+
+import android.content.res.ColorStateList
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.core.widget.ImageViewCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import cx.ring.R
+
+class ColorChooserBottomSheet : BottomSheetDialogFragment() {
+    interface IColorSelected {
+        fun onColorSelected(color: Int)
+    }
+
+    private var callback: IColorSelected? = null
+    fun setCallback(cb: IColorSelected?) {
+        callback = cb
+    }
+
+    private inner class ColorView(itemView: View) :
+        RecyclerView.ViewHolder(itemView) {
+        val view: ImageView = itemView as ImageView
+        var color = 0
+
+        init {
+            itemView.setOnClickListener {
+                if (callback != null) callback!!.onColorSelected(color)
+                dismiss()
+            }
+        }
+    }
+
+    private inner class ColorAdapter : RecyclerView.Adapter<ColorView>() {
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorView {
+            val v = LayoutInflater.from(parent.context).inflate(R.layout.item_color, parent, false)
+            return ColorView(v)
+        }
+
+        override fun onBindViewHolder(holder: ColorView, position: Int) {
+            val color = colors[position]
+            holder.color = resources.getColor(color)
+            ImageViewCompat.setImageTintList(holder.view, ColorStateList.valueOf(holder.color))
+        }
+
+        override fun getItemCount(): Int {
+            return colors.size
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val view = inflater.inflate(R.layout.frag_color_chooser, container) as RecyclerView
+        view.adapter = ColorAdapter()
+        return view
+    }
+
+    companion object {
+        private val colors = intArrayOf(
+            R.color.pink_500,
+            R.color.purple_500, R.color.deep_purple_500,
+            R.color.indigo_500, R.color.blue_500,
+            R.color.cyan_500, R.color.teal_500,
+            R.color.green_500, R.color.light_green_500,
+            R.color.grey_500, R.color.lime_500,
+            R.color.amber_500, R.color.deep_orange_500,
+            R.color.brown_500, R.color.blue_grey_500
+        )
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.java b/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.java
deleted file mode 100644
index 677bf3790..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.view.ViewCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Call;
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.Uri;
-import net.jami.services.AccountService;
-import net.jami.services.NotificationService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.views.AvatarFactory;
-import cx.ring.databinding.ActivityContactDetailsBinding;
-import cx.ring.databinding.ItemContactActionBinding;
-import cx.ring.databinding.ItemContactHorizontalBinding;
-import cx.ring.fragments.CallFragment;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.services.SharedPreferencesServiceImpl;
-import cx.ring.utils.ConversationPath;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class ContactDetailsActivity extends AppCompatActivity {
-    private static final String TAG = ContactDetailsActivity.class.getName();
-
-    @Inject
-    @Singleton
-    ConversationFacade mConversationFacade;
-
-    @Inject
-    @Singleton
-    AccountService mAccountService;
-
-    private SharedPreferences mPreferences;
-    private ActivityContactDetailsBinding binding;
-    private Conversation mConversation;
-
-    interface IContactAction {
-        void onAction();
-    }
-
-    static class ContactAction {
-        @DrawableRes
-        final int icon;
-        final Single<Drawable> drawable;
-
-        final CharSequence title;
-        final IContactAction callback;
-
-        int iconTint;
-        CharSequence iconSymbol;
-
-        ContactAction(@DrawableRes int i, int tint, CharSequence t, IContactAction cb) {
-            icon = i;
-            iconTint = tint;
-            title = t;
-            callback = cb;
-            drawable = null;
-        }
-
-        ContactAction(@DrawableRes int i, CharSequence t, IContactAction cb) {
-            icon = i;
-            iconTint = Color.BLACK;
-            title = t;
-            callback = cb;
-            drawable = null;
-        }
-        ContactAction(Single<Drawable> d, CharSequence t, IContactAction cb) {
-            drawable = d;
-            icon = 0;
-            iconTint = Color.BLACK;
-            title = t;
-            callback = cb;
-        }
-
-        void setIconTint(int tint) {
-            iconTint = tint;
-        }
-
-        void setSymbol(CharSequence t) {
-            iconSymbol = t;
-        }
-    }
-
-    static class ContactActionView extends RecyclerView.ViewHolder {
-        final ItemContactActionBinding binding;
-        IContactAction callback;
-        final CompositeDisposable disposable = new CompositeDisposable();
-
-        ContactActionView(@NonNull ItemContactActionBinding b, CompositeDisposable parentDisposable) {
-            super(b.getRoot());
-            binding = b;
-            parentDisposable.add(disposable);
-            itemView.setOnClickListener(view -> {
-                try {
-                    if (callback != null)
-                        callback.onAction();
-                } catch (Exception e) {
-                    Log.w(TAG, "Error performing action", e);
-                }
-            });
-        }
-    }
-
-    private static class ContactActionAdapter extends RecyclerView.Adapter<ContactActionView> {
-        private final ArrayList<ContactAction> actions = new ArrayList<>();
-        private final CompositeDisposable disposable;
-
-        private ContactActionAdapter(CompositeDisposable disposable) {
-            this.disposable = disposable;
-        }
-
-        @NonNull
-        @Override
-        public ContactActionView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
-            ItemContactActionBinding itemBinding = ItemContactActionBinding.inflate(layoutInflater, parent, false);
-            return new ContactActionView(itemBinding, disposable);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull ContactActionView holder, int position) {
-            ContactAction action = actions.get(position);
-            holder.disposable.clear();
-            if (action.drawable != null) {
-                holder.disposable.add(action.drawable.subscribe(holder.binding.actionIcon::setBackground));
-            } else {
-                holder.binding.actionIcon.setBackgroundResource(action.icon);
-                holder.binding.actionIcon.setText(action.iconSymbol);
-                if (action.iconTint != Color.BLACK)
-                    ViewCompat.setBackgroundTintList(holder.binding.actionIcon, ColorStateList.valueOf(action.iconTint));
-            }
-            holder.binding.actionTitle.setText(action.title);
-            holder.callback = action.callback;
-        }
-
-        @Override
-        public void onViewRecycled(@NonNull ContactActionView holder) {
-            holder.disposable.clear();
-            holder.binding.actionIcon.setBackground(null);
-        }
-
-        @Override
-        public int getItemCount() {
-            return actions.size();
-        }
-    }
-
-    static class ContactView extends RecyclerView.ViewHolder {
-        final ItemContactHorizontalBinding binding;
-        IContactAction callback;
-        final CompositeDisposable disposable = new CompositeDisposable();
-
-        ContactView(@NonNull ItemContactHorizontalBinding b, CompositeDisposable parentDisposable) {
-            super(b.getRoot());
-            binding = b;
-            parentDisposable.add(disposable);
-            itemView.setOnClickListener(view -> {
-                try {
-                    if (callback != null)
-                        callback.onAction();
-                } catch (Exception e) {
-                    Log.w(TAG, "Error performing action", e);
-                }
-            });
-        }
-    }
-    private static class ContactViewAdapter extends RecyclerView.Adapter<ContactView> {
-        private final List<Contact> contacts;
-        private final CompositeDisposable disposable;
-        interface ContactCallback {
-            void onContactClicked(Contact contact);
-        }
-        private final ContactCallback callback;
-
-        private ContactViewAdapter(CompositeDisposable disposable, List<Contact> contacts, ContactCallback cb) {
-            this.disposable = disposable;
-            this.contacts = contacts;
-            this.callback = cb;
-        }
-
-        @NonNull
-        @Override
-        public ContactView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-            LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
-            ItemContactHorizontalBinding itemBinding = ItemContactHorizontalBinding.inflate(layoutInflater, parent, false);
-            return new ContactView(itemBinding, disposable);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull ContactView holder, int position) {
-            Contact contact = contacts.get(position);
-            holder.disposable.clear();
-            holder.disposable.add(AvatarFactory.getAvatar(holder.itemView.getContext(), contact, false).subscribe(holder.binding.photo::setImageDrawable));
-            holder.binding.displayName.setText(contact.isUser() ? holder.itemView.getContext().getText(R.string.conversation_info_contact_you) : contact.getDisplayName());
-            holder.itemView.setOnClickListener(v -> callback.onContactClicked(contact));
-        }
-
-        @Override
-        public void onViewRecycled(@NonNull ContactView holder) {
-            holder.disposable.clear();
-            holder.binding.photo.setImageDrawable(null);
-        }
-
-        @Override
-        public int getItemCount() {
-            return contacts.size();
-        }
-    }
-
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-    private final ContactActionAdapter adapter = new ContactActionAdapter(mDisposableBag);
-
-    private ContactAction colorAction;
-    private ContactAction symbolAction;
-    private int colorActionPosition;
-    private int symbolActionPosition;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ConversationPath path = ConversationPath.fromIntent(getIntent());
-        if (path == null) {
-            finish();
-            return;
-        }
-        binding = ActivityContactDetailsBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-
-        //CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.toolbar_layout);
-        //collapsingToolbarLayout.setTitle("");
-
-        setSupportActionBar(binding.toolbar);
-        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-        getSupportActionBar().setDisplayShowHomeEnabled(true);
-
-        //FloatingActionButton fab = binding.sendMessage;
-        //fab.setOnClickListener(view -> goToConversationActivity(mConversation.getAccountId(), mConversation.getUri()));
-
-        colorActionPosition = 0;
-        symbolActionPosition = 1;
-
-        Conversation conversation = mConversationFacade
-                .startConversation(path.getAccountId(), path.getConversationUri())
-                .blockingGet();
-
-        mConversation = conversation;
-        mPreferences = SharedPreferencesServiceImpl.getConversationPreferences(this, conversation.getAccountId(), conversation.getUri());
-        binding.contactImage.setImageDrawable(
-                new AvatarDrawable.Builder()
-                        .withConversation(conversation)
-                        .withPresence(false)
-                        .withCircleCrop(true)
-                        .build(this)
-        );
-
-        /*Map<String, String> details = Ringservice.getCertificateDetails(conversation.getContact().getUri().getRawRingId());
-        for (Map.Entry<String, String> e : details.entrySet()) {
-            Log.w(TAG, e.getKey() + " -> " + e.getValue());
-        }*/
-
-        @StringRes int infoString = conversation.isSwarm()
-                ? (conversation.getMode() == Conversation.Mode.OneToOne
-                ? R.string.conversation_type_private
-                : R.string.conversation_type_group)
-                : R.string.conversation_type_contact;
-        /*@DrawableRes int infoIcon = conversation.isSwarm()
-                ? (conversation.getMode() == Conversation.Mode.OneToOne
-                ? R.drawable.baseline_person_24
-                : R.drawable.baseline_group_24)
-                : R.drawable.baseline_person_24;*/
-        //adapter.actions.add(new ContactAction(R.drawable.baseline_info_24, getText(infoString), () -> {}));
-        binding.conversationType.setText(infoString);
-        //binding.conversationType.setCompoundDrawables(getDrawable(infoIcon), null, null, null);
-
-        colorAction = new ContactAction(R.drawable.item_color_background, 0, getText(R.string.conversation_preference_color), () -> {
-            ColorChooserBottomSheet frag = new ColorChooserBottomSheet();
-            frag.setCallback(color -> {
-                /*collapsingToolbarLayout.setBackgroundColor(color);
-                collapsingToolbarLayout.setContentScrimColor(color);
-                collapsingToolbarLayout.setStatusBarScrimColor(color);*/
-                colorAction.setIconTint(color);
-                adapter.notifyItemChanged(colorActionPosition);
-                mPreferences.edit().putInt(ConversationFragment.KEY_PREFERENCE_CONVERSATION_COLOR, color).apply();
-            });
-            frag.show(getSupportFragmentManager(), "colorChooser");
-        });
-        int color = mPreferences.getInt(ConversationFragment.KEY_PREFERENCE_CONVERSATION_COLOR, getResources().getColor(R.color.color_primary_light));
-        colorAction.setIconTint(color);
-        /*collapsingToolbarLayout.setBackgroundColor(color);
-        collapsingToolbarLayout.setTitle(conversation.getTitle());
-        collapsingToolbarLayout.setContentScrimColor(color);
-        collapsingToolbarLayout.setStatusBarScrimColor(color);*/
-        adapter.actions.add(colorAction);
-
-        symbolAction = new ContactAction(0, getText(R.string.conversation_preference_emoji), () -> {
-            EmojiChooserBottomSheet frag = new EmojiChooserBottomSheet();
-            frag.setCallback(s -> {
-                symbolAction.setSymbol(s);
-                adapter.notifyItemChanged(symbolActionPosition);
-                mPreferences.edit().putString(ConversationFragment.KEY_PREFERENCE_CONVERSATION_SYMBOL, s).apply();
-            });
-            frag.show(getSupportFragmentManager(), "colorChooser");
-        });
-        symbolAction.setSymbol(mPreferences.getString(ConversationFragment.KEY_PREFERENCE_CONVERSATION_SYMBOL, getResources().getString(R.string.conversation_default_emoji)));
-        adapter.actions.add(symbolAction);
-
-        String conversationUri = conversation.isSwarm() ? conversation.getUri().toString() : conversation.getUriTitle();
-        if (conversation.getContacts().size() <= 2) {
-            Contact contact = conversation.getContact();
-            adapter.actions.add(new ContactAction(R.drawable.baseline_call_24, getText(R.string.ab_action_audio_call), () ->
-                    goToCallActivity(conversation, contact.getUri(), true)));
-            adapter.actions.add(new ContactAction(R.drawable.baseline_videocam_24, getText(R.string.ab_action_video_call), () ->
-                    goToCallActivity(conversation, contact.getUri(), false)));
-            if (!conversation.isSwarm()) {
-                adapter.actions.add(new ContactAction(R.drawable.baseline_clear_all_24, getText(R.string.conversation_action_history_clear), () ->
-                        new MaterialAlertDialogBuilder(ContactDetailsActivity.this)
-                                .setTitle(R.string.clear_history_dialog_title)
-                                .setMessage(R.string.clear_history_dialog_message)
-                                .setPositiveButton(R.string.conversation_action_history_clear, (b, i) -> {
-                                    mConversationFacade.clearHistory(conversation.getAccountId(), contact.getUri()).subscribe();
-                                    Snackbar.make(binding.getRoot(), R.string.clear_history_completed, Snackbar.LENGTH_LONG).show();
-                                })
-                                .setNegativeButton(android.R.string.cancel, null)
-                                .create()
-                                .show()));
-            }
-            adapter.actions.add(new ContactAction(R.drawable.baseline_block_24, getText(R.string.conversation_action_block_this), () ->
-                    new MaterialAlertDialogBuilder(ContactDetailsActivity.this)
-                            .setTitle(getString(R.string.block_contact_dialog_title, conversationUri))
-                            .setMessage(getString(R.string.block_contact_dialog_message, conversationUri))
-                            .setPositiveButton(R.string.conversation_action_block_this, (b, i) -> {
-                                mAccountService.removeContact(conversation.getAccountId(), contact.getUri().getRawRingId(), true);
-                                Toast.makeText(getApplicationContext(), getString(R.string.block_contact_completed, conversationUri), Toast.LENGTH_LONG).show();
-                                finish();
-                            })
-                            .setNegativeButton(android.R.string.cancel, null)
-                            .create()
-                            .show()));
-        }
-        getSupportActionBar().setTitle(conversation.getTitle());
-        //new ContactAction(conversation.isSwarm() ? R.drawable.baseline_group_24 : R.drawable.baseline_person_24, conversationUri, () -> {});
-        binding.conversationId.setText(conversationUri);
-        binding.infoCard.setOnClickListener(v -> copyAndShow(path.getConversationId()));
-        //adapter.actions.add(contactAction);
-        binding.contactActionList.setAdapter(adapter);
-
-        binding.contactListLayout.setVisibility(conversation.isSwarm() ? View.VISIBLE : View.GONE);
-        if (conversation.isSwarm()) {
-            binding.contactList.setAdapter(new ContactViewAdapter(mDisposableBag, conversation.getContacts(), contact -> copyAndShow(contact.getUri().getRawUriString())));
-        }
-    }
-
-    void copyAndShow(String toCopy) {
-        ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
-        if (clipboard != null) {
-            clipboard.setPrimaryClip(ClipData.newPlainText(getText(R.string.clip_contact_uri), toCopy));
-            Snackbar.make(binding.getRoot(), getString(R.string.conversation_action_copied_peer_number_clipboard, toCopy), Snackbar.LENGTH_LONG).show();
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
-        if (item.getItemId() == android.R.id.home) {
-            finishAfterTransition();
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    protected void onDestroy() {
-        adapter.actions.clear();
-        mDisposableBag.dispose();
-        super.onDestroy();
-        colorAction = null;
-        mPreferences = null;
-        binding = null;
-    }
-
-    private void goToCallActivity(Conversation conversation, Uri contactUri, boolean audioOnly) {
-        Conference conf = mConversation.getCurrentCall();
-        if (conf != null
-                && !conf.getParticipants().isEmpty()
-                && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.INACTIVE
-                && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.FAILURE) {
-            startActivity(new Intent(Intent.ACTION_VIEW)
-                    .setClass(getApplicationContext(), CallActivity.class)
-                    .putExtra(NotificationService.KEY_CALL_ID, conf.getId()));
-        } else {
-            Intent intent = new Intent(Intent.ACTION_CALL)
-                    .setClass(getApplicationContext(), CallActivity.class)
-                    .putExtras(ConversationPath.toBundle(conversation))
-                    .putExtra(Intent.EXTRA_PHONE_NUMBER, contactUri.getUri())
-                    .putExtra(CallFragment.KEY_AUDIO_ONLY, audioOnly);
-            startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
-        }
-    }
-
-    private void goToConversationActivity(String accountId, Uri conversationUri) {
-        startActivity(new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, conversationUri), getApplicationContext(), ConversationActivity.class));
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt
new file mode 100644
index 000000000..681b9b4f5
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt
@@ -0,0 +1,439 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import android.content.*
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.client.ColorChooserBottomSheet.IColorSelected
+import cx.ring.client.EmojiChooserBottomSheet.IEmojiSelected
+import cx.ring.databinding.ActivityContactDetailsBinding
+import cx.ring.databinding.ItemContactActionBinding
+import cx.ring.databinding.ItemContactHorizontalBinding
+import cx.ring.fragments.CallFragment
+import cx.ring.fragments.ConversationFragment
+import cx.ring.services.SharedPreferencesServiceImpl.Companion.getConversationPreferences
+import cx.ring.utils.ConversationPath
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.functions.Consumer
+import net.jami.services.ConversationFacade
+import net.jami.model.Call
+import net.jami.model.Contact
+import net.jami.model.Conversation
+import net.jami.model.Uri
+import net.jami.services.AccountService
+import net.jami.services.NotificationService
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class ContactDetailsActivity : AppCompatActivity() {
+    @Inject
+    @Singleton lateinit
+    var mConversationFacade: ConversationFacade
+
+    @Inject
+    @Singleton lateinit
+    var mAccountService: AccountService
+
+    private var binding: ActivityContactDetailsBinding? = null
+
+    internal class ContactAction {
+        @DrawableRes
+        val icon: Int
+        val drawable: Single<Drawable>?
+        val title: CharSequence
+        val callback: () -> Unit
+        var iconTint: Int
+        var iconSymbol: CharSequence? = null
+
+        constructor(@DrawableRes i: Int, tint: Int, t: CharSequence, cb: () -> Unit) {
+            icon = i
+            iconTint = tint
+            title = t
+            callback = cb
+            drawable = null
+        }
+
+        constructor(@DrawableRes i: Int, t: CharSequence, cb: () -> Unit) {
+            icon = i
+            iconTint = Color.BLACK
+            title = t
+            callback = cb
+            drawable = null
+        }
+
+        constructor(d: Single<Drawable>?, t: CharSequence, cb: () -> Unit) {
+            drawable = d
+            icon = 0
+            iconTint = Color.BLACK
+            title = t
+            callback = cb
+        }
+
+        fun setSymbol(t: CharSequence?) {
+            iconSymbol = t
+        }
+    }
+
+    internal class ContactActionView(
+        val binding: ItemContactActionBinding,
+        parentDisposable: CompositeDisposable
+    ) : RecyclerView.ViewHolder(
+        binding.root
+    ) {
+        var callback: (() -> Unit)? = null
+        val disposable = CompositeDisposable()
+
+        init {
+            parentDisposable.add(disposable)
+            itemView.setOnClickListener {
+                try {
+                    callback?.invoke()
+                } catch (e: Exception) {
+                    Log.w(TAG, "Error performing action", e)
+                }
+            }
+        }
+    }
+
+    private class ContactActionAdapter(private val disposable: CompositeDisposable) :
+        RecyclerView.Adapter<ContactActionView>() {
+        val actions = ArrayList<ContactAction>()
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactActionView {
+            val layoutInflater = LayoutInflater.from(parent.context)
+            val itemBinding = ItemContactActionBinding.inflate(layoutInflater, parent, false)
+            return ContactActionView(itemBinding, disposable)
+        }
+
+        override fun onBindViewHolder(holder: ContactActionView, position: Int) {
+            val action = actions[position]
+            holder.disposable.clear()
+            if (action.drawable != null) {
+                holder.disposable.add(action.drawable.subscribe(Consumer { background: Drawable? ->
+                    holder.binding.actionIcon.background = background
+                }))
+            } else {
+                holder.binding.actionIcon.setBackgroundResource(action.icon)
+                holder.binding.actionIcon.text = action.iconSymbol
+                if (action.iconTint != Color.BLACK) ViewCompat.setBackgroundTintList(
+                    holder.binding.actionIcon,
+                    ColorStateList.valueOf(action.iconTint)
+                )
+            }
+            holder.binding.actionTitle.text = action.title
+            holder.callback = action.callback
+        }
+
+        override fun onViewRecycled(holder: ContactActionView) {
+            holder.disposable.clear()
+            holder.binding.actionIcon.background = null
+        }
+
+        override fun getItemCount(): Int {
+            return actions.size
+        }
+    }
+
+    internal class ContactView(
+        val binding: ItemContactHorizontalBinding,
+        parentDisposable: CompositeDisposable
+    ) : RecyclerView.ViewHolder(
+        binding.root
+    ) {
+        var callback: (() -> Unit)? = null
+        val disposable = CompositeDisposable()
+
+        init {
+            parentDisposable.add(disposable)
+            itemView.setOnClickListener {
+                try {
+                    callback?.invoke()
+                } catch (e: Exception) {
+                    Log.w(TAG, "Error performing action", e)
+                }
+            }
+        }
+    }
+
+    private class ContactViewAdapter(
+        private val disposable: CompositeDisposable,
+        private val contacts: List<Contact>,
+        private val callback: (Contact) -> Unit
+    ) : RecyclerView.Adapter<ContactView>() {
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactView {
+            val layoutInflater = LayoutInflater.from(parent.context)
+            val itemBinding = ItemContactHorizontalBinding.inflate(layoutInflater, parent, false)
+            return ContactView(itemBinding, disposable)
+        }
+
+        override fun onBindViewHolder(holder: ContactView, position: Int) {
+            val contact = contacts[position]
+            holder.disposable.clear()
+            holder.disposable.add(
+                AvatarFactory.getAvatar(holder.itemView.context, contact, false)
+                    .subscribe { drawable: Drawable ->
+                        holder.binding.photo.setImageDrawable(drawable)
+                    })
+            holder.binding.displayName.text =
+                if (contact.isUser) holder.itemView.context.getText(R.string.conversation_info_contact_you) else contact.displayName
+            holder.itemView.setOnClickListener { callback.invoke(contact) }
+        }
+
+        override fun onViewRecycled(holder: ContactView) {
+            holder.disposable.clear()
+            holder.binding.photo.setImageDrawable(null)
+        }
+
+        override fun getItemCount(): Int {
+            return contacts.size
+        }
+    }
+
+    private val mDisposableBag = CompositeDisposable()
+    private val adapter = ContactActionAdapter(mDisposableBag)
+    private var colorAction: ContactAction? = null
+    private var symbolAction: ContactAction? = null
+    private var colorActionPosition = 0
+    private var symbolActionPosition = 0
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val path = ConversationPath.fromIntent(intent)
+        if (path == null) {
+            finish()
+            return
+        }
+        binding = ActivityContactDetailsBinding.inflate(layoutInflater)
+        setContentView(binding!!.root)
+        //JamiApplication.getInstance().getInjectionComponent().inject(this);
+
+        //CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.toolbar_layout);
+        //collapsingToolbarLayout.setTitle("");
+        setSupportActionBar(binding!!.toolbar)
+        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+        supportActionBar!!.setDisplayShowHomeEnabled(true)
+
+        //FloatingActionButton fab = binding.sendMessage;
+        //fab.setOnClickListener(view -> goToConversationActivity(mConversation.getAccountId(), mConversation.getUri()));
+        colorActionPosition = 0
+        symbolActionPosition = 1
+        val conversation = mConversationFacade
+            .startConversation(path.accountId, path.conversationUri)
+            .blockingGet()
+        val preferences = getConversationPreferences(this, conversation.accountId, conversation.uri)
+        binding!!.contactImage.setImageDrawable(
+            AvatarDrawable.Builder()
+                .withConversation(conversation)
+                .withPresence(false)
+                .withCircleCrop(true)
+                .build(this)
+        )
+
+        /*Map<String, String> details = Ringservice.getCertificateDetails(conversation.getContact().getUri().getRawRingId());
+        for (Map.Entry<String, String> e : details.entrySet()) {
+            Log.w(TAG, e.getKey() + " -> " + e.getValue());
+        }*/
+        @StringRes val infoString =
+            if (conversation.isSwarm)
+                if (conversation.mode.blockingFirst() == Conversation.Mode.OneToOne)
+                    R.string.conversation_type_private
+                else
+                    R.string.conversation_type_group
+            else R.string.conversation_type_contact
+        /*@DrawableRes int infoIcon = conversation.isSwarm()
+                ? (conversation.getMode() == Conversation.Mode.OneToOne
+                ? R.drawable.baseline_person_24
+                : R.drawable.baseline_group_24)
+                : R.drawable.baseline_person_24;*/
+        //adapter.actions.add(new ContactAction(R.drawable.baseline_info_24, getText(infoString), () -> {}));
+        binding!!.conversationType.setText(infoString)
+        //binding.conversationType.setCompoundDrawables(getDrawable(infoIcon), null, null, null);
+        colorAction = ContactAction(
+            R.drawable.item_color_background,
+            0,
+            getText(R.string.conversation_preference_color)
+        ) {
+            val frag = ColorChooserBottomSheet()
+            frag.setCallback(object : IColorSelected {
+                override fun onColorSelected(color: Int) {
+                    /*collapsingToolbarLayout.setBackgroundColor(color);
+                                   collapsingToolbarLayout.setContentScrimColor(color);
+                                   collapsingToolbarLayout.setStatusBarScrimColor(color);*/
+                    colorAction!!.iconTint = color
+                    adapter.notifyItemChanged(colorActionPosition)
+                    preferences.edit()
+                        .putInt(ConversationFragment.KEY_PREFERENCE_CONVERSATION_COLOR, color)
+                        .apply()
+                }
+            })
+            frag.show(supportFragmentManager, "colorChooser")
+        }
+        val color = preferences.getInt(
+            ConversationFragment.KEY_PREFERENCE_CONVERSATION_COLOR,
+            resources.getColor(R.color.color_primary_light)
+        )
+        colorAction!!.iconTint = color
+        /*collapsingToolbarLayout.setBackgroundColor(color);
+        collapsingToolbarLayout.setTitle(conversation.getTitle());
+        collapsingToolbarLayout.setContentScrimColor(color);
+        collapsingToolbarLayout.setStatusBarScrimColor(color);*/
+        adapter.actions.add(colorAction!!)
+        symbolAction = ContactAction(0, getText(R.string.conversation_preference_emoji)) {
+            EmojiChooserBottomSheet().apply {
+                setCallback(object : IEmojiSelected {
+                    override fun onEmojiSelected(emoji: String?) {
+                        symbolAction?.setSymbol(emoji)
+                        adapter.notifyItemChanged(symbolActionPosition)
+                        preferences.edit()
+                            .putString(ConversationFragment.KEY_PREFERENCE_CONVERSATION_SYMBOL, emoji)
+                            .apply()
+                    }
+                })
+                show(supportFragmentManager, "colorChooser")
+            }
+        }.apply {
+            setSymbol(preferences.getString(ConversationFragment.KEY_PREFERENCE_CONVERSATION_SYMBOL, resources.getString(R.string.conversation_default_emoji)))
+            adapter.actions.add(this)
+        }
+        val conversationUri = if (conversation.isSwarm) conversation.uri.toString() else conversation.uriTitle
+        if (conversation.contacts.size <= 2 && conversation.contacts.isNotEmpty()) {
+            val contact = conversation.contact!!
+            adapter.actions.add(ContactAction(R.drawable.baseline_call_24, getText(R.string.ab_action_audio_call)) {
+                goToCallActivity(conversation, contact.uri, true)
+            })
+            adapter.actions.add(ContactAction(R.drawable.baseline_videocam_24, getText(R.string.ab_action_video_call)) {
+                goToCallActivity(conversation, contact.uri, false)
+            })
+            if (!conversation.isSwarm) {
+                adapter.actions.add(ContactAction(R.drawable.baseline_clear_all_24, getText(R.string.conversation_action_history_clear)) {
+                    MaterialAlertDialogBuilder(this@ContactDetailsActivity)
+                        .setTitle(R.string.clear_history_dialog_title)
+                        .setMessage(R.string.clear_history_dialog_message)
+                        .setPositiveButton(R.string.conversation_action_history_clear) { _: DialogInterface?, _: Int ->
+                            mConversationFacade.clearHistory(conversation.accountId, contact.uri).subscribe()
+                            Snackbar.make(binding!!.root, R.string.clear_history_completed, Snackbar.LENGTH_LONG).show()
+                        }
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .create()
+                        .show()
+                })
+            }
+            adapter.actions.add(ContactAction(R.drawable.baseline_block_24, getText(R.string.conversation_action_block_this)) {
+                MaterialAlertDialogBuilder(this@ContactDetailsActivity)
+                    .setTitle(getString(R.string.block_contact_dialog_title, conversationUri))
+                    .setMessage(getString(R.string.block_contact_dialog_message, conversationUri))
+                    .setPositiveButton(R.string.conversation_action_block_this) { _: DialogInterface?, _: Int ->
+                        mAccountService.removeContact(conversation.accountId, contact.uri.rawRingId,true)
+                        Toast.makeText(applicationContext, getString(R.string.block_contact_completed, conversationUri), Toast.LENGTH_LONG).show()
+                        finish()
+                    }
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create()
+                    .show()
+            })
+        }
+        supportActionBar?.title = conversation.title
+        //new ContactAction(conversation.isSwarm() ? R.drawable.baseline_group_24 : R.drawable.baseline_person_24, conversationUri, () -> {});
+        binding!!.conversationId.text = conversationUri
+        binding!!.infoCard.setOnClickListener { copyAndShow(path.conversationId) }
+        //adapter.actions.add(contactAction);
+        binding!!.contactActionList.adapter = adapter
+        binding!!.contactListLayout.visibility =
+            if (conversation.isSwarm) View.VISIBLE else View.GONE
+        if (conversation.isSwarm) {
+            binding!!.contactList.adapter = ContactViewAdapter(mDisposableBag, conversation.contacts)
+                { contact -> copyAndShow(contact.uri.rawUriString) }
+        }
+    }
+
+    private fun copyAndShow(toCopy: String) {
+        val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
+        clipboard.setPrimaryClip(ClipData.newPlainText(getText(R.string.clip_contact_uri), toCopy))
+        Snackbar.make(binding!!.root, getString(R.string.conversation_action_copied_peer_number_clipboard, toCopy), Snackbar.LENGTH_LONG).show()
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        if (item.itemId == android.R.id.home) {
+            finishAfterTransition()
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    override fun onDestroy() {
+        adapter.actions.clear()
+        mDisposableBag.dispose()
+        super.onDestroy()
+        colorAction = null
+        binding = null
+    }
+
+    private fun goToCallActivity(conversation: Conversation, contactUri: Uri, audioOnly: Boolean) {
+        val conf = conversation.currentCall
+        if (conf != null && conf.participants.isNotEmpty()
+            && conf.participants[0].callStatus != Call.CallStatus.INACTIVE
+            && conf.participants[0].callStatus != Call.CallStatus.FAILURE) {
+            startActivity(Intent(Intent.ACTION_VIEW)
+                .setClass(applicationContext, CallActivity::class.java)
+                .putExtra(NotificationService.KEY_CALL_ID, conf.id))
+        } else {
+            val intent = Intent(Intent.ACTION_CALL)
+                .setClass(applicationContext, CallActivity::class.java)
+                .putExtras(ConversationPath.toBundle(conversation))
+                .putExtra(Intent.EXTRA_PHONE_NUMBER, contactUri.uri)
+                .putExtra(CallFragment.KEY_AUDIO_ONLY, audioOnly)
+            startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL)
+        }
+    }
+
+    private fun goToConversationActivity(accountId: String, conversationUri: Uri) {
+        startActivity(
+            Intent(
+                Intent.ACTION_VIEW,
+                ConversationPath.toUri(accountId, conversationUri),
+                applicationContext,
+                ConversationActivity::class.java
+            )
+        )
+    }
+
+    companion object {
+        private val TAG = ContactDetailsActivity::class.simpleName!!
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
deleted file mode 100644
index 267189898..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors:    Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *              Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.Menu;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.ActivityConversationBinding;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.interfaces.Colorable;
-import cx.ring.services.NotificationServiceImpl;
-import cx.ring.utils.ConversationPath;
-
-public class ConversationActivity extends AppCompatActivity implements Colorable {
-
-    private ConversationFragment mConversationFragment;
-    private ConversationPath conversationPath = null;
-
-    private Intent mPendingIntent = null;
-    private ActivityConversationBinding binding;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        String action = intent == null ? null : intent.getAction();
-        if (intent != null) {
-            conversationPath = ConversationPath.fromIntent(intent);
-        } else if (savedInstanceState != null) {
-            conversationPath = ConversationPath.fromBundle(savedInstanceState);
-        }
-        if (conversationPath == null) {
-            finish();
-            return;
-        }
-        boolean isBubble = getIntent().getBooleanExtra(NotificationServiceImpl.EXTRA_BUBBLE, false);
-
-        JamiApplication.getInstance().startDaemon();
-        binding = ActivityConversationBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-
-        setSupportActionBar(binding.mainToolbar);
-        ActionBar ab = getSupportActionBar();
-        if (ab != null)
-            ab.setDisplayHomeAsUpEnabled(true);
-
-        binding.contactImage.setOnClickListener(v -> {
-            if (mConversationFragment != null)
-                mConversationFragment.openContact();
-        });
-
-        if (mConversationFragment == null) {
-            Bundle bundle = conversationPath.toBundle();
-            bundle.putBoolean(NotificationServiceImpl.EXTRA_BUBBLE, isBubble);
-
-            mConversationFragment = new ConversationFragment();
-            mConversationFragment.setArguments(bundle);
-            getSupportFragmentManager().beginTransaction()
-                    .replace(R.id.main_frame, mConversationFragment, null)
-                    .commitNow();
-        }
-        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action) || Intent.ACTION_VIEW.equals(action)) {
-            mPendingIntent = intent;
-        }
-    }
-
-    @Override
-    public void onContextMenuClosed(@NonNull Menu menu) {
-        mConversationFragment.updateAdapterItem();
-        super.onContextMenuClosed(menu);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        if (mPendingIntent != null) {
-            handleShareIntent(mPendingIntent);
-            mPendingIntent = null;
-        }
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        handleShareIntent(intent);
-    }
-
-    private void handleShareIntent(Intent intent) {
-        if (mConversationFragment != null)
-            mConversationFragment.handleShareIntent(intent);
-    }
-
-    @Override
-    protected void onSaveInstanceState(@NonNull Bundle outState) {
-        conversationPath.toBundle(outState);
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
-        if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed()) {
-            if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
-                if (mConversationFragment != null)
-                    mConversationFragment.sendMessageText();
-                return true;
-            }
-        }
-        return super.dispatchKeyEvent(event);
-    }
-
-    public void setColor(@ColorInt int color) {
-        //colouriseToolbar(binding.mainToolbar, color);
-        //mToolbar.setBackground(new ColorDrawable(color));
-        //getWindow().setStatusBarColor(color);
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt
new file mode 100644
index 000000000..8cf134f01
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt
@@ -0,0 +1,124 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors:    Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *              Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.View
+import androidx.annotation.ColorInt
+import androidx.appcompat.app.AppCompatActivity
+import cx.ring.R
+import cx.ring.application.JamiApplication
+import cx.ring.databinding.ActivityConversationBinding
+import cx.ring.fragments.ConversationFragment
+import cx.ring.interfaces.Colorable
+import cx.ring.services.NotificationServiceImpl
+import cx.ring.utils.ConversationPath
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ConversationActivity : AppCompatActivity(), Colorable {
+    private var mConversationFragment: ConversationFragment? = null
+    private lateinit var conversationPath: ConversationPath
+    private var mPendingIntent: Intent? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val intent = intent
+        val action = intent?.action
+        var path: ConversationPath? = null
+        if (intent != null) {
+            path = ConversationPath.fromIntent(intent)
+        } else if (savedInstanceState != null) {
+            path = ConversationPath.fromBundle(savedInstanceState)
+        }
+        if (path == null) {
+            finish()
+            return
+        }
+        conversationPath = path;
+        val isBubble = getIntent().getBooleanExtra(NotificationServiceImpl.EXTRA_BUBBLE, false)
+        JamiApplication.instance!!.startDaemon()
+        val binding = ActivityConversationBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+        setSupportActionBar(binding.mainToolbar)
+        val ab = supportActionBar
+        ab?.setDisplayHomeAsUpEnabled(true)
+        binding.contactImage.setOnClickListener { v: View? -> if (mConversationFragment != null) mConversationFragment!!.openContact() }
+        if (mConversationFragment == null) {
+            val bundle = conversationPath.toBundle()
+            bundle.putBoolean(NotificationServiceImpl.EXTRA_BUBBLE, isBubble)
+            mConversationFragment = ConversationFragment()
+            mConversationFragment!!.arguments = bundle
+            supportFragmentManager.beginTransaction()
+                .replace(R.id.main_frame, mConversationFragment!!, null)
+                .commitNow()
+        }
+        if (Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action || Intent.ACTION_VIEW == action) {
+            mPendingIntent = intent
+        }
+    }
+
+    override fun onContextMenuClosed(menu: Menu) {
+        mConversationFragment!!.updateAdapterItem()
+        super.onContextMenuClosed(menu)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (mPendingIntent != null) {
+            handleShareIntent(mPendingIntent!!)
+            mPendingIntent = null
+        }
+    }
+
+    override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        handleShareIntent(intent)
+    }
+
+    private fun handleShareIntent(intent: Intent) {
+        if (mConversationFragment != null) mConversationFragment!!.handleShareIntent(intent)
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        conversationPath.toBundle(outState)
+        super.onSaveInstanceState(outState)
+    }
+
+    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+        if (event.action == KeyEvent.ACTION_DOWN && event.isCtrlPressed) {
+            if (event.keyCode == KeyEvent.KEYCODE_ENTER) {
+                if (mConversationFragment != null) mConversationFragment!!.sendMessageText()
+                return true
+            }
+        }
+        return super.dispatchKeyEvent(event)
+    }
+
+    override fun setColor(@ColorInt color: Int) {
+        //colouriseToolbar(binding.mainToolbar, color);
+        //mToolbar.setBackground(new ColorDrawable(color));
+        //getWindow().setStatusBarColor(color);
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.java
deleted file mode 100644
index 77b60d7ca..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.client;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.R;
-import cx.ring.adapters.SmartListAdapter;
-import cx.ring.application.JamiApplication;
-import net.jami.facades.ConversationFacade;
-import cx.ring.fragments.CallFragment;
-
-import net.jami.model.Contact;
-import net.jami.model.Conference;
-import net.jami.model.Call;
-import net.jami.services.CallService;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.utils.ConversationPath;
-import cx.ring.viewholders.SmartListViewHolder;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class ConversationSelectionActivity extends AppCompatActivity {
-    private final static String TAG = ConversationSelectionActivity.class.getSimpleName();
-
-    private final CompositeDisposable mDisposable = new CompositeDisposable();
-
-    @Inject
-    @Singleton
-    ConversationFacade mConversationFacade;
-
-    @Inject
-    @Singleton
-    CallService mCallService;
-
-    private SmartListAdapter adapter;
-
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        setContentView(R.layout.frag_selectconv);
-        RecyclerView list = findViewById(R.id.conversationList);
-
-        /*Toolbar toolbar = findViewById(R.id.toolbar);
-        setSupportActionBar(toolbar);
-        ActionBar ab = getSupportActionBar();
-        if (ab != null)
-            ab.setDisplayHomeAsUpEnabled(true);*/
-
-        adapter = new SmartListAdapter(null, new SmartListViewHolder.SmartListListeners() {
-            @Override
-            public void onItemClick(SmartListViewModel smartListViewModel) {
-                Intent intent = new Intent();
-                intent.setData(ConversationPath.toUri(smartListViewModel.getAccountId(), smartListViewModel.getUri()));
-                setResult(Activity.RESULT_OK, intent);
-                finish();
-            }
-
-            @Override
-            public void onItemLongClick(SmartListViewModel smartListViewModel) {
-            }
-        }, mDisposable);
-        list.setLayoutManager(new LinearLayoutManager(this));
-        list.setAdapter(adapter);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        Conference conference = null;
-        Intent intent = getIntent();
-        if (intent != null) {
-            String confId = intent.getStringExtra(CallFragment.KEY_CONF_ID);
-            if (!TextUtils.isEmpty(confId)) {
-                conference = mCallService.getConference(confId);
-            }
-        }
-
-        final Conference conf = conference;
-        mDisposable.add(mConversationFacade
-                .getCurrentAccountSubject()
-                .switchMap(a -> a.getConversationsViewModels(false))
-                .map(vm -> {
-                    if (conf == null)
-                        return vm;
-                    List<SmartListViewModel> filteredVms = new ArrayList<>(vm.size());
-                    models: for (SmartListViewModel v : vm) {
-                        List<Contact> contacts = v.getContacts();
-                        if (contacts.size() != 1)
-                            continue;
-                        for (Call call : conf.getParticipants()) {
-                            if (call.getContact() == v.getContacts().get(0)) {
-                                continue models;
-                            }
-                        }
-                        filteredVms.add(v);
-                    }
-                    return filteredVms;
-                })
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(list -> {
-                    if (adapter != null)
-                        adapter.update(list);
-                }));
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mDisposable.clear();
-    }
-
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        adapter = null;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt
new file mode 100644
index 000000000..673b3cb95
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt
@@ -0,0 +1,108 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.client
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.TextUtils
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.R
+import cx.ring.adapters.SmartListAdapter
+import cx.ring.fragments.CallFragment
+import cx.ring.utils.ConversationPath
+import cx.ring.viewholders.SmartListViewHolder.SmartListListeners
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.services.ConversationFacade
+import net.jami.model.Account
+import net.jami.model.Conference
+import net.jami.services.CallService
+import net.jami.smartlist.SmartListViewModel
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class ConversationSelectionActivity : AppCompatActivity() {
+    private val mDisposable = CompositeDisposable()
+
+    @Inject
+    @Singleton lateinit
+    var mConversationFacade: ConversationFacade
+
+    @Inject
+    @Singleton lateinit
+    var mCallService: CallService
+
+    private val adapter: SmartListAdapter = SmartListAdapter(null, object : SmartListListeners {
+        override fun onItemClick(smartListViewModel: SmartListViewModel) {
+            val intent = Intent()
+            intent.data = ConversationPath.toUri(smartListViewModel.accountId, smartListViewModel.uri)
+            setResult(RESULT_OK, intent)
+            finish()
+        }
+
+        override fun onItemLongClick(smartListViewModel: SmartListViewModel) {}
+    }, mDisposable)
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.frag_selectconv)
+        val list = findViewById<RecyclerView>(R.id.conversationList)
+        list.layoutManager = LinearLayoutManager(this)
+        list.adapter = adapter
+    }
+
+    public override fun onStart() {
+        super.onStart()
+        val conference: Conference? = intent?.getStringExtra(CallFragment.KEY_CONF_ID)?.let { confId -> mCallService.getConference(confId) }
+        mDisposable.add(mConversationFacade
+            .currentAccountSubject
+            .switchMap { a: Account -> a.getConversationsViewModels(false) }
+            .map { vm: MutableList<SmartListViewModel> ->
+                if (conference == null) return@map vm
+                val filteredVms: MutableList<SmartListViewModel> = ArrayList(vm.size)
+                models@ for (v in vm) {
+                    val contact = v.contact ?: continue // We only add contacts and one to one
+                    for (call in conference.participants) {
+                        if (call.contact === contact) {
+                            continue@models
+                        }
+                    }
+                    filteredVms.add(v)
+                }
+                filteredVms
+            }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { list -> adapter.update(list) })
+    }
+
+    public override fun onStop() {
+        super.onStop()
+        mDisposable.clear()
+    }
+
+    public override fun onDestroy() {
+        super.onDestroy()
+        findViewById<RecyclerView>(R.id.conversationList).adapter = null
+        adapter.update(ArrayList())
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.java b/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.java
deleted file mode 100644
index ee7e294cd..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ArrayRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-
-import cx.ring.R;
-
-public class EmojiChooserBottomSheet extends BottomSheetDialogFragment {
-
-    interface IEmojiSelected {
-        void onEmojiSelected(String emoji);
-    }
-
-    private IEmojiSelected callback;
-
-    public void setCallback(IEmojiSelected cb) {
-        callback = cb;
-    }
-
-    private class EmojiView extends RecyclerView.ViewHolder {
-        TextView view;
-        String emoji;
-
-        EmojiView(@NonNull View itemView) {
-            super(itemView);
-            view = (TextView) itemView;
-            itemView.setOnClickListener(v -> {
-                if (callback != null)
-                    callback.onEmojiSelected(emoji);
-                dismiss();
-            });
-        }
-    }
-
-    class ColorAdapter extends RecyclerView.Adapter<EmojiView>  {
-        private final String[] emojis;
-
-        public ColorAdapter(@ArrayRes int arrayResId) {
-            emojis = getResources().getStringArray(arrayResId);
-        }
-
-        @NonNull
-        @Override
-        public EmojiView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_emoji, parent, false);
-            return new EmojiView(v);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull EmojiView holder, int position) {
-            holder.emoji = emojis[position];
-            holder.view.setText(holder.emoji);
-        }
-
-        @Override
-        public int getItemCount() {
-            return emojis.length;
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        RecyclerView view = (RecyclerView) inflater.inflate(R.layout.frag_color_chooser, container);
-        view.setAdapter(new ColorAdapter(R.array.conversation_emojis));
-        return view;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.kt b/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.kt
new file mode 100644
index 000000000..a9492f0da
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/EmojiChooserBottomSheet.kt
@@ -0,0 +1,83 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.annotation.ArrayRes
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import cx.ring.R
+
+class EmojiChooserBottomSheet : BottomSheetDialogFragment() {
+    interface IEmojiSelected {
+        fun onEmojiSelected(emoji: String?)
+    }
+
+    private var callback: IEmojiSelected? = null
+    fun setCallback(cb: IEmojiSelected?) {
+        callback = cb
+    }
+
+    private inner class EmojiView constructor(itemView: View) :
+        RecyclerView.ViewHolder(itemView) {
+        val view: TextView = itemView as TextView
+        var emoji: String? = null
+
+        init {
+            itemView.setOnClickListener { v: View? ->
+                if (callback != null) callback!!.onEmojiSelected(emoji)
+                dismiss()
+            }
+        }
+    }
+
+    private inner class ColorAdapter(@ArrayRes arrayResId: Int) :
+        RecyclerView.Adapter<EmojiView>() {
+        private val emojis: Array<String> = resources.getStringArray(arrayResId)
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojiView {
+            val v = LayoutInflater.from(parent.context).inflate(R.layout.item_emoji, parent, false)
+            return EmojiView(v)
+        }
+
+        override fun onBindViewHolder(holder: EmojiView, position: Int) {
+            holder.emoji = emojis[position]
+            holder.view.text = holder.emoji
+        }
+
+        override fun getItemCount(): Int {
+            return emojis.size
+        }
+
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val view = inflater.inflate(R.layout.frag_color_chooser, container) as RecyclerView
+        view.adapter = ColorAdapter(R.array.conversation_emojis)
+        return view
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
deleted file mode 100644
index 12a722d7f..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
+++ /dev/null
@@ -1,835 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *          Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.client;
-
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.AdapterView;
-import android.widget.Spinner;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.Person;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.pm.ShortcutInfoCompat;
-import androidx.core.content.pm.ShortcutManagerCompat;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-
-import com.google.android.material.bottomnavigation.BottomNavigationView;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Account;
-import net.jami.model.AccountConfig;
-import net.jami.model.Conversation;
-import net.jami.services.AccountService;
-import net.jami.services.ContactService;
-import net.jami.services.NotificationService;
-import net.jami.smartlist.SmartListViewModel;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Future;
-
-import javax.inject.Inject;
-
-import cx.ring.BuildConfig;
-import cx.ring.R;
-import cx.ring.about.AboutFragment;
-import cx.ring.account.AccountEditionFragment;
-import cx.ring.account.AccountWizardActivity;
-import cx.ring.application.JamiApplication;
-import cx.ring.contactrequests.ContactRequestsFragment;
-import cx.ring.services.ContactServiceImpl;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.views.AvatarFactory;
-import cx.ring.databinding.ActivityHomeBinding;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.fragments.SmartListFragment;
-import cx.ring.interfaces.BackHandlerInterface;
-import cx.ring.interfaces.Colorable;
-import cx.ring.service.DRingService;
-import cx.ring.settings.SettingsFragment;
-import cx.ring.settings.VideoSettingsFragment;
-import cx.ring.settings.pluginssettings.PluginDetails;
-import cx.ring.settings.pluginssettings.PluginPathPreferenceFragment;
-import cx.ring.settings.pluginssettings.PluginSettingsFragment;
-import cx.ring.settings.pluginssettings.PluginsListSettingsFragment;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import cx.ring.views.SwitchButton;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class HomeActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener,
-        Spinner.OnItemSelectedListener, Colorable {
-    static final String TAG = HomeActivity.class.getSimpleName();
-
-    public static final int REQUEST_CODE_CALL = 3;
-    public static final int REQUEST_CODE_CONVERSATION = 4;
-    public static final int REQUEST_CODE_PHOTO = 5;
-    public static final int REQUEST_CODE_GALLERY = 6;
-    public static final int REQUEST_CODE_QR_CONVERSATION = 7;
-    public static final int REQUEST_PERMISSION_CAMERA = 113;
-    public static final int REQUEST_PERMISSION_READ_STORAGE = 114;
-
-    private static final int NAVIGATION_CONTACT_REQUESTS = 0;
-    private static final int NAVIGATION_CONVERSATIONS = 1;
-    private static final int NAVIGATION_ACCOUNT = 2;
-
-    public static final String HOME_TAG = "Home";
-    public static final String CONTACT_REQUESTS_TAG = "Trust request";
-    public static final String ACCOUNTS_TAG = "Accounts";
-    public static final String ABOUT_TAG = "About";
-    public static final String SETTINGS_TAG = "Prefs";
-    public static final String VIDEO_SETTINGS_TAG = "VideoPrefs";
-
-    public static final String ACTION_PRESENT_TRUST_REQUEST_FRAGMENT = BuildConfig.APPLICATION_ID + "presentTrustRequestFragment";
-
-    public static final String PLUGINS_LIST_SETTINGS_TAG = "PluginsListSettings";
-    public static final String PLUGIN_SETTINGS_TAG = "PluginSettings";
-    public static final String PLUGIN_PATH_PREFERENCE_TAG = "PluginPathPreference";
-
-    private static final String CONVERSATIONS_CATEGORY = "conversations";
-
-    protected Fragment fContent;
-    protected ConversationFragment fConversation;
-
-    private AccountSpinnerAdapter mAccountAdapter;
-    private BackHandlerInterface mAccountFragmentBackHandlerInterface;
-
-    private ViewOutlineProvider mOutlineProvider;
-
-    private int mOrientation;
-
-    @Inject
-    ContactService mContactService;
-    @Inject
-    AccountService mAccountService;
-    @Inject
-    ConversationFacade mConversationFacade;
-    @Inject
-    NotificationService mNotificationService;
-
-    private ActivityHomeBinding mBinding;
-
-    private AlertDialog mMigrationDialog;
-    //private String mAccountWithPendingrequests = null;
-
-    private final CompositeDisposable mDisposable = new CompositeDisposable();
-
-    /* called before activity is killed, e.g. rotation */
-    @Override
-    protected void onSaveInstanceState(@NonNull Bundle bundle) {
-        super.onSaveInstanceState(bundle);
-        bundle.putInt("orientation", mOrientation);
-    }
-
-    @Override
-    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
-        super.onRestoreInstanceState(savedInstanceState);
-        mOrientation = savedInstanceState.getInt("orientation");
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        JamiApplication.getInstance().startDaemon();
-
-        mBinding = ActivityHomeBinding.inflate(getLayoutInflater());
-        setContentView(mBinding.getRoot());
-
-        // dependency injection
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-
-        setSupportActionBar(mBinding.mainToolbar);
-        ActionBar ab = getSupportActionBar();
-        if (ab != null) {
-            ab.setTitle("");
-        }
-
-        mBinding.navigationView.setOnNavigationItemSelectedListener(this);
-        mBinding.navigationView.getMenu().getItem(NAVIGATION_CONVERSATIONS).setChecked(true);
-
-        mOutlineProvider = mBinding.appBar.getOutlineProvider();
-
-        if (!DeviceUtils.isTablet(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.bottom_navigation));
-        }
-
-        mBinding.spinnerToolbar.setOnItemSelectedListener(this);
-        mBinding.accountSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> enableAccount(isChecked));
-
-        if (mBinding.contactImage != null) {
-            mBinding.contactImage.setOnClickListener(v -> {
-                if (fConversation != null) {
-                    fConversation.openContact();
-                }
-            });
-        }
-
-        handleIntent(getIntent());
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (mMigrationDialog != null) {
-            if (mMigrationDialog.isShowing())
-                mMigrationDialog.dismiss();
-            mMigrationDialog = null;
-        }
-        fContent = null;
-        mDisposable.dispose();
-        mBinding = null;
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        handleIntent(intent);
-    }
-
-    private void handleIntent(Intent intent) {
-        Log.d(TAG, "handleIntent: " + intent);
-        Bundle extra = intent.getExtras();
-        String action = intent.getAction();
-        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT.equals(action)) {
-            if (extra == null || extra.getString(ContactRequestsFragment.ACCOUNT_ID) == null) {
-                return;
-            }
-            //mAccountWithPendingrequests = extra.getString(ContactRequestsFragment.ACCOUNT_ID);
-            presentTrustRequestFragment(extra.getString(ContactRequestsFragment.ACCOUNT_ID));
-        } else if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
-            ConversationPath path = ConversationPath.fromBundle(extra);
-            if (path != null) {
-                startConversation(path);
-            } else {
-                intent.setClass(getApplicationContext(), ShareActivity.class);
-                startActivity(intent);
-            }
-        } else if (DRingService.ACTION_CONV_ACCEPT.equals(action) || Intent.ACTION_VIEW.equals(action))  {
-            startConversation(ConversationPath.fromIntent(intent));
-        } //else {
-            FragmentManager fragmentManager = getSupportFragmentManager();
-            fContent = fragmentManager.findFragmentById(R.id.main_frame);
-            if (fContent == null || Intent.ACTION_SEARCH.equals(action)) {
-                if (fContent instanceof SmartListFragment) {
-                    ((SmartListFragment)fContent).handleIntent(intent);
-                } else {
-                    fContent = new SmartListFragment();
-                    fragmentManager.beginTransaction()
-                            .replace(R.id.main_frame, fContent, HOME_TAG)
-                            .commitNow();
-                }
-            }
-            /*if (mAccountWithPendingrequests != null) {
-                presentTrustRequestFragment(mAccountWithPendingrequests);
-                mAccountWithPendingrequests = null;
-            }*/
-        //}
-    }
-
-    private void showMigrationDialog() {
-        if (mMigrationDialog != null) {
-            return;
-        }
-        mMigrationDialog = new MaterialAlertDialogBuilder(this)
-                .setTitle(R.string.account_migration_title_dialog)
-                .setMessage(R.string.account_migration_message_dialog)
-                .setIcon(R.drawable.baseline_warning_24)
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> selectNavigationItem(R.id.navigation_settings))
-                .setNegativeButton(android.R.string.cancel, null)
-                .show();
-    }
-
-    public void setToolbarState(@StringRes int titleRes) {
-        setToolbarState(getString(titleRes) , null);
-    }
-
-    public void setToolbarState(String title, String subtitle) {
-        mBinding.mainToolbar.setLogo(null);
-        mBinding.mainToolbar.setTitle(title);
-        mBinding.mainToolbar.setSubtitle(subtitle);
-    }
-
-    private void showProfileInfo() {
-        mBinding.spinnerToolbar.setVisibility(View.VISIBLE);
-        mBinding.mainToolbar.setTitle(null);
-        mBinding.mainToolbar.setSubtitle(null);
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mDisposable.add(mAccountService.getObservableAccountList()
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(accounts -> {
-                    if (accounts.isEmpty()) {
-                        startActivity(new Intent(this, AccountWizardActivity.class));
-                    }
-                    for (Account account : accounts) {
-                        if (account.needsMigration()) {
-                            showMigrationDialog();
-                            break;
-                        }
-                    }
-                }));
-
-        mDisposable.add(mAccountService.getObservableAccountList()
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(accounts -> {
-                    if (mAccountAdapter == null) {
-                        mAccountAdapter = new AccountSpinnerAdapter(HomeActivity.this, new ArrayList<>(accounts));
-                        mAccountAdapter.setNotifyOnChange(false);
-                        mBinding.spinnerToolbar.setAdapter(mAccountAdapter);
-                    } else {
-                        mAccountAdapter.clear();
-                        mAccountAdapter.addAll(accounts);
-                        mAccountAdapter.notifyDataSetChanged();
-                        if (accounts.size() > 0) {
-                            mBinding.spinnerToolbar.setSelection(0);
-                        }
-                    }
-                    if (fContent instanceof SmartListFragment) {
-                        showProfileInfo();
-                    }
-                }, e ->  Log.e(TAG, "Error loading account list !", e)));
-
-        mDisposable.add((mAccountService
-                .getCurrentAccountSubject()
-                .switchMap(Account::getUnreadPending)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(count -> setBadge(R.id.navigation_requests, count))));
-
-        mDisposable.add((mAccountService
-                .getCurrentAccountSubject()
-                .switchMap(Account::getUnreadConversations)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(count -> setBadge(R.id.navigation_home, count))));
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            mDisposable.add((mAccountService
-                    .getCurrentAccountSubject()
-                    .flatMap(Account::getConversationsSubject)
-                    .observeOn(Schedulers.io())
-                    .subscribe(this::setShareShortcuts, e -> Log.e(TAG, "Error generating conversation shortcuts", e))));
-        }
-
-        if (fConversation == null)
-            fConversation = (ConversationFragment) getSupportFragmentManager().findFragmentByTag(ConversationFragment.class.getSimpleName());
-
-        int newOrientation = getResources().getConfiguration().orientation;
-        if (mOrientation != newOrientation) {
-            mOrientation = newOrientation;
-            hideTabletToolbar();
-            if (DeviceUtils.isTablet(this)) {
-                selectNavigationItem(R.id.navigation_home);
-                showTabletToolbar();
-            } else {
-                // Remove ConversationFragment that might have been restored after an orientation change
-                if (fConversation != null) {
-                    getSupportFragmentManager()
-                            .beginTransaction()
-                            .remove(fConversation)
-                            .commitNow();
-                    fConversation = null;
-                }
-            }
-        }
-
-        // Select first conversation in tablet mode
-        if (DeviceUtils.isTablet(this)) {
-            Intent intent = getIntent();
-            Uri uri = intent == null ? null : intent.getData();
-            if ((intent == null || uri == null) && fConversation == null) {
-                Observable<List<Observable<SmartListViewModel>>> smartlist = null;
-                if (fContent instanceof SmartListFragment)
-                    smartlist = mConversationFacade.getSmartList(false);
-                else if (fContent instanceof ContactRequestsFragment)
-                    smartlist = mConversationFacade.getPendingList();
-
-                if (smartlist != null) {
-                    mDisposable.add(smartlist
-                            .filter(list -> !list.isEmpty())
-                            .map(list -> list.get(0).firstOrError())
-                            .firstElement()
-                            .flatMapSingle(e -> e)
-                            .subscribe(element -> startConversation(element.getAccountId(), element.getUri())));
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mDisposable.clear();
-    }
-
-    public void startConversation(String conversationId) {
-        mDisposable.add(mAccountService.getCurrentAccountSubject()
-                .firstElement()
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(account -> startConversation(account.getAccountID(), net.jami.model.Uri.fromString(conversationId))));
-    }
-    public void startConversation(String accountId, net.jami.model.Uri conversationId) {
-        startConversation(new ConversationPath(accountId, conversationId));
-    }
-    public void startConversation(ConversationPath path) {
-        Log.w(TAG, "startConversation " + path);
-        if (!DeviceUtils.isTablet(this)) {
-            startActivity(new Intent(Intent.ACTION_VIEW, path.toUri(), this, ConversationActivity.class));
-        } else {
-            startConversationTablet(path.toBundle());
-        }
-    }
-
-    public void startConversationTablet(Bundle bundle) {
-        fConversation = new ConversationFragment();
-        fConversation.setArguments(bundle);
-
-        if (!(fContent instanceof ContactRequestsFragment)) {
-            selectNavigationItem(R.id.navigation_home);
-        }
-
-        showTabletToolbar();
-
-        getSupportFragmentManager()
-                .beginTransaction()
-                .replace(R.id.conversation_container, fConversation, ConversationFragment.class.getSimpleName())
-                .commit();
-    }
-
-    private void presentTrustRequestFragment(String accountID) {
-        mNotificationService.cancelTrustRequestNotification(accountID);
-        if (fContent instanceof ContactRequestsFragment) {
-            ((ContactRequestsFragment) fContent).presentForAccount(accountID);
-            return;
-        }
-        Bundle bundle = new Bundle();
-        bundle.putString(ContactRequestsFragment.ACCOUNT_ID, accountID);
-        fContent = new ContactRequestsFragment();
-        fContent.setArguments(bundle);
-        mBinding.navigationView.getMenu().getItem(NAVIGATION_CONTACT_REQUESTS).setChecked(true);
-        getSupportFragmentManager().beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(R.id.main_frame, fContent, CONTACT_REQUESTS_TAG)
-                .addToBackStack(CONTACT_REQUESTS_TAG).commit();
-    }
-
-    @Override
-    public void onBackPressed() {
-        if (mAccountFragmentBackHandlerInterface != null && mAccountFragmentBackHandlerInterface.onBackPressed()) {
-            return;
-        }
-        super.onBackPressed();
-        fContent = getSupportFragmentManager().findFragmentById(R.id.main_frame);
-        if (fContent instanceof SmartListFragment) {
-            mBinding.navigationView.getMenu().getItem(NAVIGATION_CONVERSATIONS).setChecked(true);
-            //showProfileInfo();
-            showToolbarSpinner();
-            hideTabletToolbar();
-        }
-    }
-
-    private void popCustomBackStack() {
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        int entryCount = fragmentManager.getBackStackEntryCount();
-        for (int i = 0; i < entryCount; ++i) {
-            fragmentManager.popBackStack();
-        }
-        //fContent = fragmentManager.findFragmentById(R.id.main_frame);
-        hideTabletToolbar();
-        setToolbarElevation(false);
-    }
-
-    public void goToSettings() {
-        if (fContent instanceof SettingsFragment) {
-            return;
-        }
-        popCustomBackStack();
-        hideToolbarSpinner();
-        fContent = new SettingsFragment();
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, SETTINGS_TAG)
-                .addToBackStack(SETTINGS_TAG).commit();
-    }
-
-    public void goToAbout() {
-        if (fContent instanceof AboutFragment) {
-            return;
-        }
-        popCustomBackStack();
-        hideToolbarSpinner();
-        fContent = new AboutFragment();
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, ABOUT_TAG)
-                .addToBackStack(ABOUT_TAG).commit();
-    }
-
-    public void goToVideoSettings() {
-        if (fContent instanceof VideoSettingsFragment) {
-            return;
-        }
-        fContent = new VideoSettingsFragment();
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, VIDEO_SETTINGS_TAG)
-                .addToBackStack(VIDEO_SETTINGS_TAG).commit();
-    }
-
-    @Override
-    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
-        Account account = mAccountService.getCurrentAccount();
-        if (account == null)
-            return false;
-
-        Bundle bundle = new Bundle();
-        int itemId = item.getItemId();
-        if (itemId == R.id.navigation_requests) {
-            if (fContent instanceof ContactRequestsFragment) {
-                ((ContactRequestsFragment) fContent).presentForAccount(null);
-                return true;
-            }
-            popCustomBackStack();
-            fContent = new ContactRequestsFragment();
-            getSupportFragmentManager().beginTransaction()
-                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                    .replace(R.id.main_frame, fContent, CONTACT_REQUESTS_TAG)
-                    .setReorderingAllowed(true)
-                    .addToBackStack(CONTACT_REQUESTS_TAG)
-                    .commit();
-            //showProfileInfo();
-            showToolbarSpinner();
-        } else if (itemId == R.id.navigation_home) {
-            if (fContent instanceof SmartListFragment) {
-                return true;
-            }
-            popCustomBackStack();
-            fContent = new SmartListFragment();
-            getSupportFragmentManager().beginTransaction()
-                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                    .replace(R.id.main_frame, fContent, HOME_TAG)
-                    .setReorderingAllowed(true)
-                    .commit();
-            //showProfileInfo();
-            showToolbarSpinner();
-        } else if (itemId == R.id.navigation_settings) {
-            if (account.needsMigration()) {
-                Log.d(TAG, "launchAccountMigrationActivity: Launch account migration activity");
-
-                Intent intent = new Intent()
-                        .setClass(this, AccountWizardActivity.class)
-                        .setData(Uri.withAppendedPath(ContentUriHandler.ACCOUNTS_CONTENT_URI, account.getAccountID()));
-                startActivityForResult(intent, 1);
-            } else {
-                Log.d(TAG, "launchAccountEditFragment: Launch account edit fragment");
-                bundle.putString(AccountEditionFragment.ACCOUNT_ID, account.getAccountID());
-
-                if (fContent instanceof AccountEditionFragment) {
-                    return true;
-                }
-                popCustomBackStack();
-                fContent = new AccountEditionFragment();
-                fContent.setArguments(bundle);
-                getSupportFragmentManager().beginTransaction()
-                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                        .replace(getFragmentContainerId(), fContent, ACCOUNTS_TAG)
-                        .addToBackStack(ACCOUNTS_TAG)
-                        .commit();
-                //showProfileInfo();
-                showToolbarSpinner();
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
-        int type = mAccountAdapter.getItemViewType(position);
-        if (type == AccountSpinnerAdapter.TYPE_ACCOUNT) {
-            Account account = mAccountAdapter.getItem(position);
-            mAccountService.setCurrentAccount(account);
-            showAccountStatus(fContent instanceof AccountEditionFragment && !account.isSip());
-        } else {
-            Intent intent = new Intent(HomeActivity.this, AccountWizardActivity.class);
-            if (type == AccountSpinnerAdapter.TYPE_CREATE_SIP) {
-                intent.setAction(AccountConfig.ACCOUNT_TYPE_SIP);
-            }
-            startActivity(intent);
-            mBinding.spinnerToolbar.setSelection(mAccountService.getCurrentAccountIndex());
-        }
-    }
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) {
-
-    }
-
-    public void setBadge(int menuId, int number) {
-        if (number == 0)
-            mBinding.navigationView.removeBadge(menuId);
-        else
-            mBinding.navigationView.getOrCreateBadge(menuId).setNumber(number);
-    }
-
-    private void hideTabletToolbar() {
-        if (mBinding != null && mBinding.tabletToolbar != null) {
-            mBinding.contactTitle.setText(null);
-            mBinding.contactSubtitle.setText(null);
-            mBinding.contactImage.setImageDrawable(null);
-            mBinding.tabletToolbar.setVisibility(View.GONE);
-        }
-    }
-
-    private void showTabletToolbar() {
-        if (mBinding != null && mBinding.tabletToolbar != null && DeviceUtils.isTablet(this)) {
-            mBinding.tabletToolbar.setVisibility(View.VISIBLE);
-        }
-    }
-
-    public void setTabletTitle(@StringRes int titleRes) {
-        if (mBinding.tabletToolbar != null) {
-            mBinding.tabletToolbar.setVisibility(View.VISIBLE);
-            mBinding.contactTitle.setText(titleRes);
-            mBinding.contactTitle.setTextSize(19);
-            mBinding.contactTitle.setTypeface(null, Typeface.BOLD);
-            mBinding.contactImage.setVisibility(View.GONE);
-        }
-        /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.contactTitle.getLayoutParams();
-        params.removeRule(RelativeLayout.ALIGN_TOP);
-        params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
-        binding.contactTitle.setLayoutParams(params);*/
-    }
-
-    public void setToolbarTitle(@StringRes int titleRes) {
-        if (DeviceUtils.isTablet(this)) {
-            setTabletTitle(titleRes);
-        } else {
-            setToolbarState(titleRes);
-        }
-    }
-
-    public void showAccountStatus(boolean show){
-        mBinding.accountSwitch.setVisibility(show? View.VISIBLE : View.GONE);
-    }
-
-    private void showToolbarSpinner() {
-        mBinding.spinnerToolbar.setVisibility(View.VISIBLE);
-    }
-
-    private void hideToolbarSpinner() {
-        if (mBinding != null && !DeviceUtils.isTablet(this)) {
-            mBinding.spinnerToolbar.setVisibility(View.GONE);
-        }
-    }
-
-    private int getFragmentContainerId() {
-        if (DeviceUtils.isTablet(HomeActivity.this)) {
-            return R.id.conversation_container;
-        }
-
-        return R.id.main_frame;
-    }
-
-    public void setAccountFragmentOnBackPressedListener(BackHandlerInterface backPressedListener) {
-        mAccountFragmentBackHandlerInterface = backPressedListener;
-    }
-
-    /**
-     * Changes the current main fragment to a plugins list settings fragment
-     */
-    public void goToPluginsListSettings() {
-        if (fContent instanceof PluginsListSettingsFragment) {
-            return;
-        }
-
-        fContent = new PluginsListSettingsFragment();
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, PLUGINS_LIST_SETTINGS_TAG)
-                .addToBackStack(PLUGINS_LIST_SETTINGS_TAG).commit();
-    }
-
-    /**
-     * Changes the current main fragment to a plugin settings fragment
-     * @param pluginDetails
-     */
-    public void gotToPluginSettings(PluginDetails pluginDetails){
-        if (fContent instanceof PluginSettingsFragment) {
-            return;
-        }
-        fContent = PluginSettingsFragment.newInstance(pluginDetails);
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, PLUGIN_SETTINGS_TAG)
-                .addToBackStack(PLUGIN_SETTINGS_TAG).commit();
-    }
-
-    /**
-     * Changes the current main fragment to a plugin PATH preference fragment
-     */
-    public void gotToPluginPathPreference(PluginDetails pluginDetails, String preferenceKey){
-        if (fContent instanceof PluginPathPreferenceFragment) {
-            return;
-        }
-        fContent = PluginPathPreferenceFragment.newInstance(pluginDetails, preferenceKey);
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
-                .replace(getFragmentContainerId(), fContent, PLUGIN_PATH_PREFERENCE_TAG)
-                .addToBackStack(PLUGIN_PATH_PREFERENCE_TAG).commit();
-    }
-
-    @Override
-    public void setColor(int color) {
-        //mToolbar.setBackground(new ColorDrawable(color));
-    }
-
-    public void setToolbarElevation(boolean enable) {
-        if (mBinding != null)
-            mBinding.appBar.setElevation(enable ? getResources().getDimension(R.dimen.toolbar_elevation) : 0);
-    }
-
-    public void setToolbarOutlineState(boolean enabled) {
-        if (mBinding != null) {
-            if (!enabled) {
-                mBinding.appBar.setOutlineProvider(null);
-            } else {
-                mBinding.appBar.setOutlineProvider(mOutlineProvider);
-            }
-        }
-	}
-	
-    public void popFragmentImmediate() {
-        FragmentManager fm = getSupportFragmentManager();
-        fm.popBackStackImmediate();
-        FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(fm.getBackStackEntryCount()-1);
-        fContent = fm.findFragmentById(entry.getId());
-    }
-
-    public void selectNavigationItem(int id) {
-        if (mBinding != null)
-            mBinding.navigationView.setSelectedItemId(id);
-    }
-
-    public void enableAccount(boolean newValue) {
-        Account account = mAccountService.getCurrentAccount();
-        if (account == null) {
-            Log.w(TAG, "account not found!");
-            return;
-        }
-
-        account.setEnabled(newValue);
-        mAccountService.setAccountEnabled(account.getAccountID(), newValue);
-    }
-
-    public SwitchButton getSwitchButton() {
-        return mBinding.accountSwitch;
-    }
-
-    private void setShareShortcuts(Collection<Conversation> conversations) {
-        int targetSize = (int) (AvatarFactory.SIZE_NOTIF * getResources().getDisplayMetrics().density);
-        int i = 0;
-        int maxCount = ShortcutManagerCompat.getMaxShortcutCountPerActivity(this);
-        if (maxCount == 0)
-            maxCount = 4;
-
-        List<Future<Bitmap>> futureIcons = new ArrayList<>(Math.min(conversations.size(), maxCount));
-        for (Conversation conversation : conversations) {
-            futureIcons.add(((ContactServiceImpl)mContactService).loadConversationAvatar(this, conversation)
-                    .map(d -> BitmapUtils.drawableToBitmap(d, targetSize))
-                    .subscribeOn(Schedulers.computation())
-                    .toFuture());
-            if (++i == maxCount)
-                break;
-        }
-        List<ShortcutInfoCompat> shortcutInfoList = new ArrayList<>(futureIcons.size());
-
-        i = 0;
-        for (Conversation conversation : conversations) {
-            IconCompat icon = null;
-            try {
-                icon = IconCompat.createWithBitmap(futureIcons.get(i).get());
-            } catch (Exception e) {
-                Log.w(TAG, "Failed to load icon", e);
-            }
-
-            ConversationPath path = new ConversationPath(conversation);
-            String key = path.toKey();
-
-            Person person = new Person.Builder()
-                    .setName(conversation.getTitle())
-                    .setKey(key)
-                    .build();
-
-            ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(this, key)
-                    .setShortLabel(conversation.getTitle())
-                    .setPerson(person)
-                    .setLongLived(true)
-                    .setIcon(icon)
-                    .setCategories(Collections.singleton(CONVERSATIONS_CATEGORY))
-                    .setIntent(new Intent(Intent.ACTION_SEND, Uri.EMPTY, this, HomeActivity.class)
-                            .putExtras(path.toBundle()))
-                    .build();
-
-            shortcutInfoList.add(shortcutInfo);
-            if (++i == maxCount)
-                break;
-        }
-
-        try {
-            Log.d(TAG, "Adding shortcuts: " + shortcutInfoList.size());
-            ShortcutManagerCompat.removeAllDynamicShortcuts(this);
-            ShortcutManagerCompat.addDynamicShortcuts(this, shortcutInfoList);
-        } catch (Exception e) {
-            Log.w(TAG, "Error adding shortcuts", e);
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt
new file mode 100644
index 000000000..40643d7b6
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt
@@ -0,0 +1,792 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *          Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.client
+
+import android.content.DialogInterface
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Typeface
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.AdapterView
+import android.widget.CompoundButton
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.Person
+import androidx.core.content.ContextCompat
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentTransaction
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.navigation.NavigationBarView
+import cx.ring.BuildConfig
+import cx.ring.R
+import cx.ring.about.AboutFragment
+import cx.ring.account.AccountEditionFragment
+import cx.ring.account.AccountWizardActivity
+import cx.ring.application.JamiApplication
+import cx.ring.contactrequests.ContactRequestsFragment
+import cx.ring.databinding.ActivityHomeBinding
+import cx.ring.fragments.ConversationFragment
+import cx.ring.fragments.SmartListFragment
+import cx.ring.interfaces.BackHandlerInterface
+import cx.ring.interfaces.Colorable
+import cx.ring.service.DRingService
+import cx.ring.services.ContactServiceImpl
+import cx.ring.settings.SettingsFragment
+import cx.ring.settings.VideoSettingsFragment
+import cx.ring.settings.pluginssettings.PluginDetails
+import cx.ring.settings.pluginssettings.PluginPathPreferenceFragment
+import cx.ring.settings.pluginssettings.PluginSettingsFragment
+import cx.ring.settings.pluginssettings.PluginsListSettingsFragment
+import cx.ring.utils.BitmapUtils
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.DeviceUtils
+import cx.ring.views.AvatarFactory
+import cx.ring.views.SwitchButton
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.services.ConversationFacade
+import net.jami.model.Account
+import net.jami.model.AccountConfig
+import net.jami.model.Conversation
+import net.jami.model.Uri
+import net.jami.services.AccountService
+import net.jami.services.ContactService
+import net.jami.services.NotificationService
+import net.jami.smartlist.SmartListViewModel
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListener,
+    AdapterView.OnItemSelectedListener, Colorable {
+    private var fContent: Fragment? = null
+    private var fConversation: ConversationFragment? = null
+    private var mAccountAdapter: AccountSpinnerAdapter? = null
+    private var mAccountFragmentBackHandlerInterface: BackHandlerInterface? = null
+    private var mOutlineProvider: ViewOutlineProvider? = null
+    private var mOrientation = 0
+
+    @Inject lateinit
+    var mContactService: ContactService
+
+    @Inject lateinit
+    var mAccountService: AccountService
+
+    @Inject lateinit
+    var mConversationFacade: ConversationFacade
+
+    @Inject lateinit
+    var mNotificationService: NotificationService
+
+    private var mBinding: ActivityHomeBinding? = null
+    private var mMigrationDialog: AlertDialog? = null
+
+    //private String mAccountWithPendingrequests = null;
+    private val mDisposable = CompositeDisposable()
+
+    /* called before activity is killed, e.g. rotation */
+    override fun onSaveInstanceState(bundle: Bundle) {
+        super.onSaveInstanceState(bundle)
+        bundle.putInt("orientation", mOrientation)
+    }
+
+    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+        super.onRestoreInstanceState(savedInstanceState)
+        mOrientation = savedInstanceState.getInt("orientation")
+    }
+
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        JamiApplication.instance?.startDaemon()
+        mBinding = ActivityHomeBinding.inflate(layoutInflater).also { binding ->
+            setContentView(binding.root)
+            setSupportActionBar(binding.mainToolbar)
+            supportActionBar?.title = ""
+            binding.navigationView.setOnItemSelectedListener(this)
+            binding.navigationView.menu.getItem(NAVIGATION_CONVERSATIONS).isChecked = true
+            mOutlineProvider = binding.appBar.outlineProvider
+            binding.spinnerToolbar.onItemSelectedListener = this
+            binding.accountSwitch.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
+                enableAccount(isChecked)
+            }
+            binding.contactImage?.setOnClickListener { fConversation?.openContact() }
+        }
+        if (!DeviceUtils.isTablet(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            window.navigationBarColor = ContextCompat.getColor(this, R.color.bottom_navigation)
+        }
+        handleIntent(intent)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mMigrationDialog?.apply {
+            if (isShowing) dismiss()
+            mMigrationDialog = null
+        }
+        fContent = null
+        mDisposable.dispose()
+        mBinding = null
+    }
+
+    override fun onNewIntent(intent: Intent) {
+        super.onNewIntent(intent)
+        handleIntent(intent)
+    }
+
+    private fun handleIntent(intent: Intent) {
+        Log.d(TAG, "handleIntent: $intent")
+        val extra = intent.extras
+        val action = intent.action
+        if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT == action) {
+            if (extra?.getString(ContactRequestsFragment.ACCOUNT_ID) == null) {
+                return
+            }
+            //mAccountWithPendingrequests = extra.getString(ContactRequestsFragment.ACCOUNT_ID);
+            presentTrustRequestFragment(extra.getString(ContactRequestsFragment.ACCOUNT_ID))
+        } else if (Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action) {
+            val path = ConversationPath.fromBundle(extra)
+            if (path != null) {
+                startConversation(path)
+            } else {
+                intent.setClass(applicationContext, ShareActivity::class.java)
+                startActivity(intent)
+            }
+        } else if (DRingService.ACTION_CONV_ACCEPT == action || Intent.ACTION_VIEW == action) {
+            startConversation(ConversationPath.fromIntent(intent)!!)
+        } //else {
+        val fragmentManager = supportFragmentManager
+        fContent = fragmentManager.findFragmentById(R.id.main_frame)
+        if (fContent == null || Intent.ACTION_SEARCH == action) {
+            if (fContent is SmartListFragment) {
+                (fContent as SmartListFragment).handleIntent(intent)
+            } else {
+                val content = SmartListFragment()
+                fContent = content
+                fragmentManager.beginTransaction()
+                    .replace(R.id.main_frame, content, HOME_TAG)
+                    .commitNow()
+            }
+        }
+        /*if (mAccountWithPendingrequests != null) {
+                presentTrustRequestFragment(mAccountWithPendingrequests);
+                mAccountWithPendingrequests = null;
+            }*/
+        //}
+    }
+
+    private fun showMigrationDialog() {
+        if (mMigrationDialog != null) {
+            return
+        }
+        mMigrationDialog = MaterialAlertDialogBuilder(this)
+            .setTitle(R.string.account_migration_title_dialog)
+            .setMessage(R.string.account_migration_message_dialog)
+            .setIcon(R.drawable.baseline_warning_24)
+            .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
+                selectNavigationItem(R.id.navigation_settings)
+            }
+            .setNegativeButton(android.R.string.cancel, null)
+            .show()
+    }
+
+    private fun setToolbarState(@StringRes titleRes: Int) {
+        setToolbarState(getString(titleRes), null)
+    }
+
+    private fun setToolbarState(title: String?, subtitle: String?) {
+        mBinding?.mainToolbar?.let { toolbar ->
+            toolbar.logo = null
+            toolbar.title = title
+            toolbar.subtitle = subtitle
+        }
+    }
+
+    private fun showProfileInfo() {
+        mBinding?.apply {
+            spinnerToolbar.visibility = View.VISIBLE
+            mainToolbar.title = null
+            mainToolbar.subtitle = null
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        mDisposable.add(
+            mAccountService.observableAccountList
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe { accounts: List<Account> ->
+                    if (accounts.isEmpty()) {
+                        startActivity(Intent(this, AccountWizardActivity::class.java))
+                    }
+                    for (account in accounts) {
+                        if (account.needsMigration()) {
+                            showMigrationDialog()
+                            break
+                        }
+                    }
+                })
+        mDisposable.add(
+            mAccountService.observableAccountList
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ accounts ->
+                    if (mAccountAdapter == null) {
+                        mAccountAdapter = AccountSpinnerAdapter(this@HomeActivity, ArrayList(accounts)).apply {
+                            setNotifyOnChange(false)
+                            mBinding?.spinnerToolbar?.adapter = this
+                        }
+                    } else {
+                        mAccountAdapter!!.clear()
+                        mAccountAdapter!!.addAll(accounts)
+                        mAccountAdapter!!.notifyDataSetChanged()
+                        if (accounts.isNotEmpty()) {
+                            mBinding!!.spinnerToolbar.setSelection(0)
+                        }
+                    }
+                    if (fContent is SmartListFragment) {
+                        showProfileInfo()
+                    }
+                }) { e -> Log.e(TAG, "Error loading account list !", e) })
+        mDisposable.add(mAccountService
+            .currentAccountSubject
+            .switchMap { obj -> obj.unreadPending }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { count -> setBadge(R.id.navigation_requests, count) })
+        mDisposable.add(mAccountService
+            .currentAccountSubject
+            .switchMap { obj -> obj.unreadConversations }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { count -> setBadge(R.id.navigation_home, count) })
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            mDisposable.add(mAccountService
+                .currentAccountSubject
+                .switchMap { obj -> obj.getConversationsSubject() }
+                .debounce(10, TimeUnit.SECONDS)
+                .observeOn(Schedulers.io())
+                .subscribe({ conversations -> setShareShortcuts(conversations) })
+                { e -> Log.e(TAG, "Error generating conversation shortcuts", e) })
+        }
+        if (fConversation == null)
+            fConversation = supportFragmentManager.findFragmentByTag(ConversationFragment::class.java.simpleName) as ConversationFragment?
+        val newOrientation = resources.configuration.orientation
+        if (mOrientation != newOrientation) {
+            mOrientation = newOrientation
+            hideTabletToolbar()
+            if (DeviceUtils.isTablet(this)) {
+                selectNavigationItem(R.id.navigation_home)
+                showTabletToolbar()
+            } else {
+                // Remove ConversationFragment that might have been restored after an orientation change
+                if (fConversation != null) {
+                    supportFragmentManager
+                        .beginTransaction()
+                        .remove(fConversation!!)
+                        .commitNow()
+                    fConversation = null
+                }
+            }
+        }
+
+        // Select first conversation in tablet mode
+        if (DeviceUtils.isTablet(this)) {
+            val intent = intent
+            val uri = intent?.data
+            if ((intent == null || uri == null) && fConversation == null) {
+                var smartlist: Observable<List<Observable<SmartListViewModel>>>? = null
+                if (fContent is SmartListFragment) smartlist =
+                    mConversationFacade.getSmartList(false) else if (fContent is ContactRequestsFragment) smartlist =
+                    mConversationFacade.pendingList
+                if (smartlist != null) {
+                    mDisposable.add(smartlist
+                        .filter { list -> list.isNotEmpty() }
+                        .map { list -> list[0].firstOrError() }
+                        .firstElement()
+                        .flatMapSingle { e -> e }
+                        .subscribe { element -> startConversation(element.accountId, element.uri) })
+                }
+            }
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        mDisposable.clear()
+    }
+
+    fun startConversation(conversationId: String) {
+        mDisposable.add(mAccountService.currentAccountSubject
+                .firstElement()
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe { account -> startConversation(account.accountID, Uri.fromString(conversationId)) })
+    }
+
+    fun startConversation(accountId: String, conversationId: Uri) {
+        startConversation(ConversationPath(accountId, conversationId))
+    }
+
+    private fun startConversation(path: ConversationPath) {
+        Log.w(TAG, "startConversation $path")
+        if (!DeviceUtils.isTablet(this)) {
+            startActivity(Intent(Intent.ACTION_VIEW, path.toUri(), this, ConversationActivity::class.java))
+        } else {
+            startConversationTablet(path.toBundle())
+        }
+    }
+
+    private fun startConversationTablet(bundle: Bundle?) {
+        fConversation = ConversationFragment()
+        fConversation!!.arguments = bundle
+        if (fContent !is ContactRequestsFragment) {
+            selectNavigationItem(R.id.navigation_home)
+        }
+        showTabletToolbar()
+        supportFragmentManager.beginTransaction()
+            .replace(R.id.conversation_container, fConversation!!, ConversationFragment::class.java.simpleName)
+            .commit()
+    }
+
+    private fun presentTrustRequestFragment(accountID: String?) {
+        mNotificationService.cancelTrustRequestNotification(accountID)
+        if (fContent is ContactRequestsFragment) {
+            (fContent as ContactRequestsFragment).presentForAccount(accountID)
+            return
+        }
+        val content = ContactRequestsFragment().apply {
+            arguments = Bundle().apply { putString(ContactRequestsFragment.ACCOUNT_ID, accountID) }
+        }
+        fContent = content
+        mBinding!!.navigationView.menu.getItem(NAVIGATION_CONTACT_REQUESTS).isChecked = true
+        supportFragmentManager.beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(R.id.main_frame, content, CONTACT_REQUESTS_TAG)
+            .addToBackStack(CONTACT_REQUESTS_TAG).commit()
+    }
+
+    override fun onBackPressed() {
+        if (mAccountFragmentBackHandlerInterface != null && mAccountFragmentBackHandlerInterface!!.onBackPressed()) {
+            return
+        }
+        super.onBackPressed()
+        fContent = supportFragmentManager.findFragmentById(R.id.main_frame)
+        if (fContent is SmartListFragment) {
+            mBinding!!.navigationView.menu.getItem(NAVIGATION_CONVERSATIONS).isChecked =
+                true
+            //showProfileInfo();
+            showToolbarSpinner()
+            hideTabletToolbar()
+        }
+    }
+
+    private fun popCustomBackStack() {
+        val fragmentManager = supportFragmentManager
+        val entryCount = fragmentManager.backStackEntryCount
+        for (i in 0 until entryCount) {
+            fragmentManager.popBackStackImmediate()
+        }
+        //fContent = fragmentManager.findFragmentById(R.id.main_frame);
+        hideTabletToolbar()
+        setToolbarElevation(false)
+    }
+
+    fun goToSettings() {
+        if (fContent is SettingsFragment) {
+            return
+        }
+        popCustomBackStack()
+        hideToolbarSpinner()
+        val content = SettingsFragment()
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, SETTINGS_TAG)
+            .addToBackStack(SETTINGS_TAG).commit()
+    }
+
+    fun goToAbout() {
+        if (fContent is AboutFragment) {
+            return
+        }
+        popCustomBackStack()
+        hideToolbarSpinner()
+        val content = AboutFragment()
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, ABOUT_TAG)
+            .addToBackStack(ABOUT_TAG).commit()
+    }
+
+    fun goToVideoSettings() {
+        if (fContent is VideoSettingsFragment) {
+            return
+        }
+        val content = VideoSettingsFragment()
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, VIDEO_SETTINGS_TAG)
+            .addToBackStack(VIDEO_SETTINGS_TAG).commit()
+    }
+
+    override fun onNavigationItemSelected(item: MenuItem): Boolean {
+        val account = mAccountService.currentAccount ?: return false
+        val bundle = Bundle()
+        val itemId = item.itemId
+        if (itemId == R.id.navigation_requests) {
+            if (fContent is ContactRequestsFragment) {
+                (fContent as ContactRequestsFragment).presentForAccount(null)
+                return true
+            }
+            popCustomBackStack()
+            val content = ContactRequestsFragment()
+            fContent = content
+            supportFragmentManager.beginTransaction()
+                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+                .replace(R.id.main_frame, content, CONTACT_REQUESTS_TAG)
+                .setReorderingAllowed(true)
+                .addToBackStack(CONTACT_REQUESTS_TAG)
+                .commit()
+            //showProfileInfo();
+            showToolbarSpinner()
+        } else if (itemId == R.id.navigation_home) {
+            if (fContent is SmartListFragment) {
+                return true
+            }
+            popCustomBackStack()
+            val fcontent = supportFragmentManager.findFragmentById(R.id.main_frame)
+            if (fcontent is SmartListFragment) {
+                fContent = fcontent
+                return true
+            }
+            val content = SmartListFragment()
+            fContent = content
+            supportFragmentManager.beginTransaction()
+                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+                .replace(R.id.main_frame, content, HOME_TAG)
+                .setReorderingAllowed(true)
+                .commit()
+            //showProfileInfo();
+            showToolbarSpinner()
+        } else if (itemId == R.id.navigation_settings) {
+            if (account.needsMigration()) {
+                Log.d(TAG, "launchAccountMigrationActivity: Launch account migration activity")
+                val intent = Intent()
+                    .setClass(this, AccountWizardActivity::class.java)
+                    .setData(android.net.Uri.withAppendedPath(ContentUriHandler.ACCOUNTS_CONTENT_URI, account.accountID))
+                startActivityForResult(intent, 1)
+            } else {
+                Log.d(TAG, "launchAccountEditFragment: Launch account edit fragment")
+                bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, account.accountID)
+                if (fContent is AccountEditionFragment) {
+                    return true
+                }
+                popCustomBackStack()
+                val content = AccountEditionFragment()
+                content.arguments = bundle
+                fContent = content
+                supportFragmentManager.beginTransaction()
+                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+                    .replace(fragmentContainerId, content, ACCOUNTS_TAG)
+                    .addToBackStack(ACCOUNTS_TAG)
+                    .commit()
+                //showProfileInfo();
+                showToolbarSpinner()
+            }
+        }
+        return true
+    }
+
+    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+        val adapter = mAccountAdapter ?: return
+        val type = adapter.getItemViewType(position)
+        if (type == AccountSpinnerAdapter.TYPE_ACCOUNT) {
+            adapter.getItem(position)?.let { account ->
+                mAccountService.currentAccount = account
+                showAccountStatus(fContent is AccountEditionFragment && !account.isSip)
+            }
+        } else {
+            val intent = Intent(this@HomeActivity, AccountWizardActivity::class.java)
+            if (type == AccountSpinnerAdapter.TYPE_CREATE_SIP) {
+                intent.action = AccountConfig.ACCOUNT_TYPE_SIP
+            }
+            startActivity(intent)
+            mBinding!!.spinnerToolbar.setSelection(mAccountService.currentAccountIndex)
+        }
+    }
+
+    override fun onNothingSelected(parent: AdapterView<*>?) {}
+
+    private fun setBadge(menuId: Int, number: Int) {
+        if (number == 0) mBinding!!.navigationView.removeBadge(menuId) else mBinding!!.navigationView.getOrCreateBadge(
+            menuId
+        ).number = number
+    }
+
+    private fun hideTabletToolbar() {
+        mBinding?.let { binding -> binding.tabletToolbar?.let { toolbar ->
+            binding.contactTitle?.text = null
+            binding.contactSubtitle?.text = null
+            binding.contactImage?.setImageDrawable(null)
+            toolbar.visibility = View.GONE
+        }}
+    }
+
+    private fun showTabletToolbar() {
+        if (DeviceUtils.isTablet(this))
+            mBinding?.let { binding -> binding.tabletToolbar?.let { toolbar ->
+                toolbar.visibility = View.VISIBLE
+            }}
+    }
+
+    fun setTabletTitle(@StringRes titleRes: Int) {
+        mBinding?.let { binding -> binding.tabletToolbar?.let { toolbar ->
+            binding.contactTitle?.setText(titleRes)
+            binding.contactTitle?.textSize = 19f
+            binding.contactTitle?.setTypeface(null, Typeface.BOLD)
+            binding.contactImage?.visibility = View.GONE
+            toolbar.visibility = View.VISIBLE
+        }}
+        /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.contactTitle.getLayoutParams();
+        params.removeRule(RelativeLayout.ALIGN_TOP);
+        params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
+        binding.contactTitle.setLayoutParams(params);*/
+    }
+
+    fun setToolbarTitle(@StringRes titleRes: Int) {
+        if (DeviceUtils.isTablet(this)) {
+            setTabletTitle(titleRes)
+        } else {
+            setToolbarState(titleRes)
+        }
+    }
+
+    fun showAccountStatus(show: Boolean) {
+        mBinding!!.accountSwitch.visibility = if (show) View.VISIBLE else View.GONE
+    }
+
+    private fun showToolbarSpinner() {
+        mBinding!!.spinnerToolbar.visibility = View.VISIBLE
+    }
+
+    private fun hideToolbarSpinner() {
+        if (mBinding != null && !DeviceUtils.isTablet(this)) {
+            mBinding!!.spinnerToolbar.visibility = View.GONE
+        }
+    }
+
+    private val fragmentContainerId: Int
+        get() = if (DeviceUtils.isTablet(this@HomeActivity))
+            R.id.conversation_container else R.id.main_frame
+
+    fun setAccountFragmentOnBackPressedListener(backPressedListener: BackHandlerInterface?) {
+        mAccountFragmentBackHandlerInterface = backPressedListener
+    }
+
+    /**
+     * Changes the current main fragment to a plugins list settings fragment
+     */
+    fun goToPluginsListSettings() {
+        if (fContent is PluginsListSettingsFragment) {
+            return
+        }
+        val content = PluginsListSettingsFragment()
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, PLUGINS_LIST_SETTINGS_TAG)
+            .addToBackStack(PLUGINS_LIST_SETTINGS_TAG).commit()
+    }
+
+    /**
+     * Changes the current main fragment to a plugin settings fragment
+     * @param pluginDetails
+     */
+    fun gotToPluginSettings(pluginDetails: PluginDetails?) {
+        if (fContent is PluginSettingsFragment) {
+            return
+        }
+        val content = PluginSettingsFragment.newInstance(pluginDetails)
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, PLUGIN_SETTINGS_TAG)
+            .addToBackStack(PLUGIN_SETTINGS_TAG).commit()
+    }
+
+    /**
+     * Changes the current main fragment to a plugin PATH preference fragment
+     */
+    fun gotToPluginPathPreference(pluginDetails: PluginDetails?, preferenceKey: String?) {
+        if (fContent is PluginPathPreferenceFragment) {
+            return
+        }
+        val content = PluginPathPreferenceFragment.newInstance(pluginDetails, preferenceKey)
+        fContent = content
+        supportFragmentManager
+            .beginTransaction()
+            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+            .replace(fragmentContainerId, content, PLUGIN_PATH_PREFERENCE_TAG)
+            .addToBackStack(PLUGIN_PATH_PREFERENCE_TAG).commit()
+    }
+
+    override fun setColor(color: Int) {
+        //mToolbar.setBackground(new ColorDrawable(color));
+    }
+
+    fun setToolbarElevation(enable: Boolean) {
+        if (mBinding != null) mBinding!!.appBar.elevation = if (enable) resources.getDimension(R.dimen.toolbar_elevation) else 0f
+    }
+
+    fun setToolbarOutlineState(enabled: Boolean) {
+        if (mBinding != null) {
+            if (!enabled) {
+                mBinding!!.appBar.outlineProvider = null
+            } else {
+                mBinding!!.appBar.outlineProvider = mOutlineProvider
+            }
+        }
+    }
+
+    fun popFragmentImmediate() {
+        val fm = supportFragmentManager
+        fm.popBackStackImmediate()
+        val entry = fm.getBackStackEntryAt(fm.backStackEntryCount - 1)
+        fContent = fm.findFragmentById(entry.id)
+    }
+
+    private fun selectNavigationItem(id: Int) {
+        if (mBinding != null) mBinding!!.navigationView.selectedItemId = id
+    }
+
+    private fun enableAccount(newValue: Boolean) {
+        val account = mAccountService.currentAccount
+        if (account == null) {
+            Log.w(TAG, "account not found!")
+            return
+        }
+        account.isEnabled = newValue
+        mAccountService.setAccountEnabled(account.accountID, newValue)
+    }
+
+    val switchButton: SwitchButton
+        get() = mBinding!!.accountSwitch
+
+    private fun setShareShortcuts(conversations: Collection<Conversation>) {
+        val targetSize = (AvatarFactory.SIZE_NOTIF * resources.displayMetrics.density).toInt()
+        var i = 0
+        var maxCount = ShortcutManagerCompat.getMaxShortcutCountPerActivity(this)
+        if (maxCount == 0) maxCount = 4
+        val futureIcons: MutableList<Future<Bitmap>> =
+            ArrayList(conversations.size.coerceAtMost(maxCount))
+        for (conversation in conversations) {
+            val mode = conversation.mode.blockingFirst()
+            if (mode == Conversation.Mode.Syncing)
+                continue
+            futureIcons.add(
+                (mContactService as ContactServiceImpl?)!!.loadConversationAvatar(this,conversation)
+                    .map { d -> BitmapUtils.drawableToBitmap(d, targetSize) }
+                    .subscribeOn(Schedulers.computation())
+                    .toFuture())
+            if (++i == maxCount) break
+        }
+        val shortcutInfoList: MutableList<ShortcutInfoCompat> = ArrayList(futureIcons.size)
+        i = 0
+        for (conversation in conversations) {
+            val mode = conversation.mode.blockingFirst()
+            if (mode == Conversation.Mode.Syncing)
+                continue
+            var icon: IconCompat? = null
+            try {
+                icon = IconCompat.createWithBitmap(futureIcons[i].get())
+            } catch (e: Exception) {
+                Log.w(TAG, "Failed to load icon", e)
+            }
+            val title = conversation.title ?: continue
+            if (title.isEmpty()) continue
+            val path = ConversationPath(conversation)
+            val key = path.toKey()
+            val person = Person.Builder()
+                .setName(conversation.title)
+                .setKey(key)
+                .build()
+            val shortcutInfo = ShortcutInfoCompat.Builder(this, key)
+                .setShortLabel(conversation.title  ?: "")
+                .setPerson(person)
+                .setLongLived(true)
+                .setIcon(icon)
+                .setCategories(setOf(CONVERSATIONS_CATEGORY))
+                .setIntent(Intent(Intent.ACTION_SEND, android.net.Uri.EMPTY, this, HomeActivity::class.java)
+                        .putExtras(path.toBundle()))
+                .build()
+            shortcutInfoList.add(shortcutInfo)
+            if (++i == maxCount) break
+        }
+        try {
+            Log.d(TAG, "Adding shortcuts: " + shortcutInfoList.size)
+            ShortcutManagerCompat.removeAllDynamicShortcuts(this)
+            ShortcutManagerCompat.addDynamicShortcuts(this, shortcutInfoList)
+        } catch (e: Exception) {
+            Log.w(TAG, "Error adding shortcuts", e)
+        }
+    }
+
+    companion object {
+        val TAG: String = HomeActivity::class.simpleName!!
+        const val REQUEST_CODE_CALL = 3
+        const val REQUEST_CODE_CONVERSATION = 4
+        const val REQUEST_CODE_PHOTO = 5
+        const val REQUEST_CODE_GALLERY = 6
+        const val REQUEST_CODE_QR_CONVERSATION = 7
+        const val REQUEST_PERMISSION_CAMERA = 113
+        const val REQUEST_PERMISSION_READ_STORAGE = 114
+        private const val NAVIGATION_CONTACT_REQUESTS = 0
+        private const val NAVIGATION_CONVERSATIONS = 1
+        private const val NAVIGATION_ACCOUNT = 2
+        const val HOME_TAG = "Home"
+        const val CONTACT_REQUESTS_TAG = "Trust request"
+        const val ACCOUNTS_TAG = "Accounts"
+        const val ABOUT_TAG = "About"
+        const val SETTINGS_TAG = "Prefs"
+        const val VIDEO_SETTINGS_TAG = "VideoPrefs"
+        const val ACTION_PRESENT_TRUST_REQUEST_FRAGMENT = BuildConfig.APPLICATION_ID + "presentTrustRequestFragment"
+        const val PLUGINS_LIST_SETTINGS_TAG = "PluginsListSettings"
+        const val PLUGIN_SETTINGS_TAG = "PluginSettings"
+        const val PLUGIN_PATH_PREFERENCE_TAG = "PluginPathPreference"
+        private const val CONVERSATIONS_CATEGORY = "conversations"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java b/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java
deleted file mode 100644
index 4cc27d633..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/LogsActivity.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.client;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.ContextCompat;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.services.HardwareService;
-import net.jami.utils.StringUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.ActivityLogsBinding;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.ContentUriHandler;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class LogsActivity extends AppCompatActivity {
-    private static final String TAG = LogsActivity.class.getSimpleName();
-
-    private ActivityLogsBinding binding;
-
-    private final CompositeDisposable compositeDisposable = new CompositeDisposable();
-    private Disposable disposable;
-
-    private ActivityResultLauncher<String> fileSaver;
-    private File mCurrentFile = null;
-
-    @Inject
-    @Singleton
-    HardwareService mHardwareService;
-
-    public LogsActivity() {
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        JamiApplication.getInstance().startDaemon();
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        binding = ActivityLogsBinding.inflate(getLayoutInflater());
-        setContentView(binding.getRoot());
-        setSupportActionBar(binding.toolbar);
-        ActionBar ab = getSupportActionBar();
-        if (ab != null)
-            ab.setDisplayHomeAsUpEnabled(true);
-
-        fileSaver = registerForActivityResult(new ActivityResultContracts.CreateDocument(), result -> compositeDisposable.add(
-                AndroidFileUtils.copyFileToUri(getContentResolver(), mCurrentFile, result).
-                        observeOn(AndroidSchedulers.mainThread()).
-                        subscribe(() -> {
-                            if (!mCurrentFile.delete())
-                                Log.w(TAG, "Can't delete temp file");
-                            mCurrentFile = null;
-                            Snackbar.make(binding.getRoot(), R.string.file_saved_successfully, Snackbar.LENGTH_SHORT).show();
-                        }, error -> Snackbar.make(binding.getRoot(), R.string.generic_error, Snackbar.LENGTH_SHORT).show())));
-
-        binding.fab.setOnClickListener(view -> {
-            if (disposable == null)
-                startLogging();
-            else
-                stopLogging();
-        });
-        if (mHardwareService.isLogging())
-            startLogging();
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.logs_menu, menu);
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    private Maybe<String> getLog() {
-        if (mHardwareService.isLogging())
-            return mHardwareService.startLogs()
-                .firstElement();
-        CharSequence log = binding.logView.getText();
-        if (StringUtils.isEmpty(log))
-            return Maybe.empty();
-        return Maybe.just(log.toString());
-    }
-
-    private Maybe<File> getLogFile() {
-        return getLog()
-                .observeOn(Schedulers.io())
-                .map(log -> {
-                    File file = AndroidFileUtils.createLogFile(this);
-                    OutputStream os = new FileOutputStream(file);
-                    os.write(log.getBytes());
-                    return file;
-                });
-    }
-
-    private Maybe<Uri> getLogUri() {
-        return getLogFile().map(file -> ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file));
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
-        int id = item.getItemId();
-        if (id == android.R.id.home) {
-            finish();
-            return true;
-        } else if (id == R.id.menu_log_share) {
-            compositeDisposable.add(getLogUri()
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(uri -> {
-                        Log.w(TAG, "saved logs to " + uri);
-                        Intent sendIntent = new Intent(Intent.ACTION_SEND);
-                        sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                        String type = getContentResolver().getType(uri);
-                        sendIntent.setDataAndType(uri, type);
-                        sendIntent.putExtra(Intent.EXTRA_STREAM, uri);
-                        startActivity(Intent.createChooser(sendIntent, null));
-                    }, e -> Snackbar.make(binding.getRoot(), "Error sharing logs: " + e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show()));
-            return true;
-        } else if (id == R.id.menu_log_save) {
-            compositeDisposable.add(getLogFile()
-                    .subscribe(file -> {
-                        mCurrentFile = file;
-                        fileSaver.launch(file.getName());
-                    }));
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    void startLogging() {
-        binding.logView.setText("");
-        disposable = mHardwareService.startLogs()
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(message -> {
-                    binding.logView.setText(message);
-                    binding.scroll.post(() -> binding.scroll.fullScroll(View.FOCUS_DOWN));
-                }, e -> Log.w(TAG, "Error in logger", e));
-        compositeDisposable.add(disposable);
-        setButtonState(true);
-    }
-
-    void stopLogging() {
-        disposable.dispose();
-        disposable = null;
-        mHardwareService.stopLogs();
-        setButtonState(false);
-    }
-
-    void setButtonState(boolean logging) {
-        binding.fab.setText(logging ? R.string.pref_logs_stop : R.string.pref_logs_start);
-        binding.fab.setBackgroundColor(ContextCompat.getColor(this, logging ? R.color.red_400 : R.color.colorSecondary));
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (disposable != null) {
-            disposable.dispose();
-            disposable = null;
-        }
-        compositeDisposable.clear();
-        super.onDestroy();
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/LogsActivity.kt b/ring-android/app/src/main/java/cx/ring/client/LogsActivity.kt
new file mode 100644
index 000000000..ab7c29c55
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/LogsActivity.kt
@@ -0,0 +1,181 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.client
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.application.JamiApplication
+import cx.ring.databinding.ActivityLogsBinding
+import cx.ring.utils.AndroidFileUtils
+import cx.ring.utils.ContentUriHandler
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.services.HardwareService
+import net.jami.utils.StringUtils
+import java.io.File
+import java.io.FileOutputStream
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class LogsActivity : AppCompatActivity() {
+    private lateinit var binding: ActivityLogsBinding
+    private val compositeDisposable = CompositeDisposable()
+    private var disposable: Disposable? = null
+    private lateinit var fileSaver: ActivityResultLauncher<String>
+    private var mCurrentFile: File? = null
+
+    @Inject
+    @Singleton
+    lateinit var mHardwareService: HardwareService
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        JamiApplication.instance?.startDaemon()
+        binding = ActivityLogsBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+        setSupportActionBar(binding.toolbar)
+        supportActionBar?.setDisplayHomeAsUpEnabled(true)
+        fileSaver = registerForActivityResult(ActivityResultContracts.CreateDocument()) { result: Uri? ->
+            if (result != null)
+                compositeDisposable.add(AndroidFileUtils.copyFileToUri(contentResolver, mCurrentFile, result)
+                        .observeOn(AndroidSchedulers.mainThread()).subscribe({
+                            if (!mCurrentFile!!.delete())
+                                Log.w(TAG, "Can't delete temp file")
+                            mCurrentFile = null
+                            Snackbar.make(binding.root, R.string.file_saved_successfully, Snackbar.LENGTH_SHORT).show()
+                        }) { Snackbar.make(binding.root, R.string.generic_error, Snackbar.LENGTH_SHORT).show()
+                    })
+        }
+        binding.fab.setOnClickListener { if (disposable == null) startLogging() else stopLogging() }
+        if (mHardwareService.isLogging) startLogging()
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu): Boolean {
+        menuInflater.inflate(R.menu.logs_menu, menu)
+        return super.onCreateOptionsMenu(menu)
+    }
+
+    private val log: Maybe<String>
+        get() {
+            if (mHardwareService.isLogging)
+                return mHardwareService.startLogs().firstElement()
+            val log = binding.logView.text
+            return if (StringUtils.isEmpty(log)) Maybe.empty()
+            else Maybe.just(log.toString())
+        }
+    private val logFile: Maybe<File>
+        get() = log
+            .observeOn(Schedulers.io())
+            .map { log: String ->
+                val file = AndroidFileUtils.createLogFile(this)
+                FileOutputStream(file).use { os -> os.write(log.toByteArray()) }
+                file
+            }
+    private val logUri: Maybe<Uri>
+        get() = logFile.map { file: File ->
+            ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file)
+        }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            android.R.id.home -> {
+                finish()
+                return true
+            }
+            R.id.menu_log_share -> {
+                compositeDisposable.add(logUri.observeOn(AndroidSchedulers.mainThread())
+                        .subscribe({ uri: Uri ->
+                            Log.w(TAG, "saved logs to $uri")
+                            val sendIntent = Intent(Intent.ACTION_SEND).apply {
+                                addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                                setDataAndType(uri, contentResolver.getType(uri))
+                                putExtra(Intent.EXTRA_STREAM, uri)
+                            }
+                            startActivity(Intent.createChooser(sendIntent, null))
+                        }) { e: Throwable ->
+                            Snackbar.make(binding.root, "Error sharing logs: " + e.localizedMessage, Snackbar.LENGTH_SHORT).show()
+                        })
+                return true
+            }
+            R.id.menu_log_save -> {
+                compositeDisposable.add(logFile.subscribe { file: File ->
+                    mCurrentFile = file
+                    fileSaver.launch(file.name)
+                })
+                return true
+            }
+            else -> return super.onOptionsItemSelected(item)
+        }
+    }
+
+    private fun startLogging() {
+        binding.logView.text = ""
+        disposable = mHardwareService.startLogs()
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ message: String ->
+                binding.logView.text = message
+                binding.scroll.post { binding.scroll.fullScroll(View.FOCUS_DOWN) }
+            }) { e -> Log.w(TAG, "Error in logger", e) }
+        compositeDisposable.add(disposable)
+        setButtonState(true)
+    }
+
+    private fun stopLogging() {
+        disposable?.let {
+            it.dispose()
+            disposable = null
+        }
+        mHardwareService.stopLogs()
+        setButtonState(false)
+    }
+
+    private fun setButtonState(logging: Boolean) {
+        binding.fab.setText(if (logging) R.string.pref_logs_stop else R.string.pref_logs_start)
+        binding.fab.setBackgroundColor(ContextCompat.getColor(this, if (logging) R.color.red_400 else R.color.colorSecondary))
+    }
+
+    override fun onDestroy() {
+        disposable?.let {
+            it.dispose()
+            disposable = null
+        }
+        compositeDisposable.clear()
+        super.onDestroy()
+    }
+
+    companion object {
+        private val TAG = LogsActivity::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.java b/ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.kt
similarity index 68%
rename from ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.java
rename to ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.kt
index be06c637e..164322f92 100644
--- a/ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/MediaViewerActivity.kt
@@ -17,20 +17,15 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package cx.ring.client;
+package cx.ring.client
 
-import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import cx.ring.R
 
-import cx.ring.R;
-
-public class MediaViewerActivity extends AppCompatActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_media_viewer);
+class MediaViewerActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_media_viewer)
     }
-
-}
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.java b/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.java
deleted file mode 100644
index d66a11cf2..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.net.Uri;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import com.bumptech.glide.load.resource.bitmap.CenterInside;
-
-import cx.ring.R;
-import cx.ring.utils.GlideApp;
-import cx.ring.utils.GlideOptions;
-
-/**
- * A placeholder fragment containing a simple view.
- */
-public class MediaViewerFragment extends Fragment {
-    private final static String TAG = MediaViewerFragment.class.getSimpleName();
-
-    private Uri mUri = null;
-
-    protected ImageView mImage;
-
-    private final GlideOptions PICTURE_OPTIONS = new GlideOptions().transform(new CenterInside());
-
-    public MediaViewerFragment() {
-    }
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
-        ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_media_viewer, container, false);
-        mImage = view.findViewById(R.id.image);
-        showImage();
-        return view;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        Activity activity = getActivity();
-        if (activity == null)
-            return;
-        mUri = activity.getIntent().getData();
-        showImage();
-    }
-
-    private void showImage() {
-        if (mUri == null) {
-            Log.w(TAG, "showImage(): null URI");
-            return;
-        }
-        Activity a = getActivity();
-        if (a == null) {
-            Log.w(TAG, "showImage(): null Activity");
-            return;
-        }
-        if (mImage == null) {
-            Log.w(TAG, "showImage(): null image view");
-            return;
-        }
-        GlideApp.with(a)
-                .load(mUri)
-                .apply(PICTURE_OPTIONS)
-                .into(mImage);
-    }
-
-    @Override
-    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt b/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt
new file mode 100644
index 000000000..77c2d8271
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.fragment.app.Fragment
+import com.bumptech.glide.load.resource.bitmap.CenterInside
+import cx.ring.R
+import cx.ring.utils.GlideApp
+import cx.ring.utils.GlideOptions
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+class MediaViewerFragment : Fragment() {
+    private var mUri: Uri? = null
+    private var mImage: ImageView? = null
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val view = inflater.inflate(R.layout.fragment_media_viewer, container, false) as ViewGroup
+        mImage = view.findViewById(R.id.image)
+        showImage()
+        return view
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mImage = null
+    }
+
+    override fun onStart() {
+        super.onStart()
+        val activity = activity ?: return
+        mUri = activity.intent.data
+        showImage()
+    }
+
+    private fun showImage() {
+        mUri?.let {uri ->
+            activity?.let {a ->
+                mImage?.let {image ->
+                    GlideApp.with(a)
+                        .load(uri)
+                        .apply(PICTURE_OPTIONS)
+                        .into(image)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val PICTURE_OPTIONS = GlideOptions().transform(CenterInside())
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.java b/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.java
deleted file mode 100644
index 069fbdaaf..000000000
--- a/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.client;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.target.DrawableImageViewTarget;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.R;
-import cx.ring.account.AccountEditionFragment;
-import cx.ring.adapters.RingtoneAdapter;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Account;
-import net.jami.model.ConfigKey;
-import net.jami.model.Ringtone;
-import net.jami.services.AccountService;
-import cx.ring.utils.AndroidFileUtils;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.disposables.Disposable;
-
-import net.jami.utils.Log;
-
-public class RingtoneActivity extends AppCompatActivity {
-
-    private final String TAG = RingtoneActivity.class.getSimpleName();
-
-    private RingtoneAdapter adapter;
-    private Account mAccount;
-    private TextView customRingtone;
-    private ImageView customPlaying, customSelected;
-    private final MediaPlayer mediaPlayer = new MediaPlayer();
-    private Disposable disposable;
-
-    @Inject
-    @Singleton
-    AccountService mAccountService;
-
-    public static final int MAX_SIZE_RINGTONE = 64 * 1024;
-    private static final int SELECT_RINGTONE_PATH = 40;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        setContentView(R.layout.activity_ringtone);
-        super.onCreate(savedInstanceState);
-        mAccount = mAccountService.getAccount(getIntent().getExtras().getString(AccountEditionFragment.ACCOUNT_ID_KEY));
-        if (mAccount == null) {
-            finish();
-            return;
-        }
-
-        /*Toolbar toolbar = findViewById(R.id.ringtoneToolbar);
-        toolbar.setNavigationOnClickListener(view -> finish());*/
-
-        RecyclerView recycler = findViewById(R.id.ringToneRecycler);
-        ConstraintLayout customRingtoneLayout = findViewById(R.id.customRingtoneLayout);
-        customRingtone = findViewById(R.id.customRingtoneName);
-        customPlaying = findViewById(R.id.custom_ringtone_playing);
-        customSelected = findViewById(R.id.custom_ringtone_selected);
-        adapter = new RingtoneAdapter(prepareRingtones());
-
-        RecyclerView.LayoutManager upcomingLayoutManager = new LinearLayoutManager(this);
-        recycler.setLayoutManager(upcomingLayoutManager);
-        recycler.setItemAnimator(new DefaultItemAnimator());
-        recycler.setAdapter(adapter);
-
-        // loads the user's settings
-        setPreference();
-
-        customRingtoneLayout.setOnClickListener(v ->
-                displayFileSearchDialog());
-
-        customRingtoneLayout.setOnLongClickListener(view -> {
-            displayRemoveDialog();
-            return true;
-        });
-
-        disposable = adapter.getRingtoneSubject().subscribe(ringtone -> {
-            setJamiRingtone(ringtone);
-            removeCustomRingtone();
-        }, e -> Log.e(TAG, "Error updating ringtone status"));
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        disposable.dispose();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        stopCustomPreview();
-    }
-
-    @Override
-    public void finish() {
-        super.finish();
-        adapter.releaseMediaPlayer();
-        mediaPlayer.release();
-    }
-
-    /**
-     * Gets the name of a file without its extension
-     *
-     * @param fileName the name of the file
-     * @return the base name
-     */
-    public static String stripFileNameExtension(String fileName) {
-        int index = fileName.lastIndexOf('.');
-        if (index == -1) {
-            return fileName;
-        } else {
-            return fileName.substring(0, index);
-        }
-    }
-
-    private List<Ringtone> prepareRingtones() {
-        List<Ringtone> ringtoneList = new ArrayList<>();
-        File ringtoneFolder = new File(getFilesDir(), "ringtones");
-        File[] ringtones = ringtoneFolder.listFiles();
-        Drawable ringtoneIcon = getDrawable(R.drawable.baseline_notifications_active_24);
-
-        if(ringtones == null)
-            return ringtoneList;
-        Arrays.sort(ringtones, (a, b) -> a.getName().compareTo(b.getName()));
-
-        ringtoneList.add(new Ringtone("Silent", null, getDrawable(R.drawable.baseline_notifications_off_24)));
-
-        for(File file : ringtones) {
-            String name = stripFileNameExtension(file.getName());
-            ringtoneList.add(new Ringtone(name, file.getAbsolutePath(), ringtoneIcon));
-        }
-
-        return ringtoneList;
-    }
-
-    /**
-     * Sets the selected ringtone (Jami or custom) on activity startup
-     */
-    private void setPreference() {
-        File path = new File(mAccount.getConfig().get(ConfigKey.RINGTONE_PATH));
-        boolean customEnabled = mAccount.getConfig().getBool(ConfigKey.RINGTONE_CUSTOM);
-        if (customEnabled && path.exists()) {
-            customRingtone.setText(path.getName());
-            customSelected.setVisibility(View.VISIBLE);
-        } else if(path.exists()) {
-            adapter.selectDefaultItem(path.getAbsolutePath(), mAccount.getConfig().getBool(ConfigKey.RINGTONE_ENABLED));
-        }
-        else {
-           setDefaultRingtone();
-        }
-    }
-
-    private void setDefaultRingtone() {
-        File ringtonesDir = new File(getFilesDir(), "ringtones");
-        String ringtonePath = new File(ringtonesDir, getString(R.string.ringtone_default_name)).getAbsolutePath();
-        adapter.selectDefaultItem(ringtonePath, mAccount.getConfig().getBool(ConfigKey.RINGTONE_ENABLED));
-        mAccount.setDetail(ConfigKey.RINGTONE_PATH, ringtonePath);
-        mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, false);
-        updateAccount();
-    }
-
-    /**
-     * Sets a Jami ringtone as the default
-     *
-     * @param ringtone the ringtone object
-     */
-    private void setJamiRingtone(Ringtone ringtone) {
-        String path = ringtone.getRingtonePath();
-        if (path == null) {
-            mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, false);
-            mAccount.setDetail(ConfigKey.RINGTONE_PATH, "");
-        } else {
-            mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, true);
-            mAccount.setDetail(ConfigKey.RINGTONE_PATH, ringtone.getRingtonePath());
-            mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, false);
-        }
-        updateAccount();
-    }
-
-    /**
-     * Sets a custom ringtone selected by the user
-     *
-     * @param path the ringtoen path
-     * @see #onFileFound(File) onFileFound
-     * @see #displayFileSearchDialog() displayFileSearchDialog
-     */
-    private void setCustomRingtone(String path) {
-        mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, true);
-        mAccount.setDetail(ConfigKey.RINGTONE_PATH, path);
-        mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, true);
-        updateAccount();
-    }
-
-    /**
-     * Updates an account with new details
-     */
-    private void updateAccount() {
-        mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList());
-        mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails());
-    }
-
-    /**
-     * Previews a custom ringtone
-     *
-     * @param ringtone the ringtone file
-     */
-    private void previewRingtone(File ringtone) {
-        try {
-            mediaPlayer.setDataSource(ringtone.getAbsolutePath());
-            mediaPlayer.prepare();
-            mediaPlayer.start();
-        } catch (IOException | NullPointerException e) {
-            stopCustomPreview();
-            Log.e(TAG, "Error previewing ringtone", e);
-        }
-        mediaPlayer.setOnCompletionListener(mp -> stopCustomPreview());
-    }
-
-    /**
-     * Removes a custom ringtone and updates the view
-     */
-    private void removeCustomRingtone() {
-        customSelected.setVisibility(View.INVISIBLE);
-        customPlaying.setVisibility(View.INVISIBLE);
-        customRingtone.setText(R.string.ringtone_custom_prompt);
-        stopCustomPreview();
-    }
-
-    /**
-     * Stops audio previews from all possible sources
-     */
-    private void stopCustomPreview() {
-        try {
-            if (mediaPlayer != null) {
-                if (mediaPlayer.isPlaying())
-                    mediaPlayer.stop();
-                mediaPlayer.reset();
-            }
-        } catch (Exception e) {
-        }
-    }
-
-    /**
-     * Handles playing and setting a custom ringtone or displaying an error if it is too large
-     *
-     * @param ringtone the ringtone path
-     */
-    private void onFileFound(File ringtone) {
-        if (ringtone.length() / 1024 > MAX_SIZE_RINGTONE) {
-            displayFileTooBigDialog();
-        } else {
-            // resetState will stop the preview
-            adapter.resetState();
-            customRingtone.setText(ringtone.getName());
-            customSelected.setVisibility(View.VISIBLE);
-            customPlaying.setVisibility(View.VISIBLE);
-            Glide.with(this)
-                    .load(R.raw.baseline_graphic_eq_black_24dp)
-                    .placeholder(R.drawable.baseline_graphic_eq_24)
-                    .into(new DrawableImageViewTarget(customPlaying));
-            previewRingtone(ringtone);
-            setCustomRingtone(ringtone.getAbsolutePath());
-        }
-    }
-
-    /**
-     * Displays the native file browser to select a ringtone
-     */
-    private void displayFileSearchDialog() {
-        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType("audio/*");
-        startActivityForResult(intent, SELECT_RINGTONE_PATH);
-    }
-
-    /**
-     * Displays a dialog if the selected ringtone is too large
-     */
-    private void displayFileTooBigDialog() {
-        new AlertDialog.Builder(this)
-                .setTitle(R.string.ringtone_error_title)
-                .setMessage(getString(R.string.ringtone_error_size_too_big, MAX_SIZE_RINGTONE))
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    /**
-     * Displays a dialog that prompts the user to remove a custom ringtone
-     */
-    private void displayRemoveDialog() {
-        if (!mAccount.getConfig().getBool(ConfigKey.RINGTONE_CUSTOM))
-            return;
-        String[] item = {"Remove"};
-        // subject callback from adapter will update the view
-        new AlertDialog.Builder(this)
-                .setItems(item, (dialog, which) ->
-                        setDefaultRingtone()).show();
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (data == null)
-            return;
-        Uri uri = data.getData();
-        if (resultCode == Activity.RESULT_CANCELED || uri == null) {
-            return;
-        }
-
-        ContentResolver cr = getContentResolver();
-        if (requestCode == SELECT_RINGTONE_PATH) {
-            try {
-                String path = AndroidFileUtils.getRealPathFromURI(this, uri);
-                if (path == null)
-                    throw new IllegalArgumentException();
-                onFileFound(new File(path));
-            } catch (Exception e) {
-                final int takeFlags = data.getFlags()
-                        & (Intent.FLAG_GRANT_READ_URI_PERMISSION
-                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-                cr.takePersistableUriPermission(uri, takeFlags);
-                AndroidFileUtils.getCacheFile(this, uri)
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .subscribe(this::onFileFound,
-                                err -> Toast.makeText(this, "Can't load ringtone !", Toast.LENGTH_SHORT).show());
-            }
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt b/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt
new file mode 100644
index 000000000..e604f5a7c
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt
@@ -0,0 +1,354 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.client
+
+import android.annotation.SuppressLint
+import android.content.DialogInterface
+import android.content.Intent
+import android.media.MediaPlayer
+import android.os.Bundle
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.LayoutManager
+import com.bumptech.glide.Glide
+import com.bumptech.glide.request.target.DrawableImageViewTarget
+import cx.ring.R
+import cx.ring.account.AccountEditionFragment
+import cx.ring.adapters.RingtoneAdapter
+import cx.ring.utils.AndroidFileUtils
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.Disposable
+import net.jami.model.Account
+import net.jami.model.ConfigKey
+import net.jami.model.Ringtone
+import net.jami.services.AccountService
+import net.jami.utils.Log
+import java.io.File
+import java.io.IOException
+import java.util.*
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class RingtoneActivity : AppCompatActivity() {
+    private var adapter: RingtoneAdapter? = null
+    private lateinit var mAccount: Account
+    private var customRingtone: TextView? = null
+    private var customPlaying: ImageView? = null
+    private var customSelected: ImageView? = null
+    private val mediaPlayer: MediaPlayer = MediaPlayer()
+    private var disposable: Disposable? = null
+
+    @Inject
+    @Singleton
+    lateinit var mAccountService: AccountService
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        setContentView(R.layout.activity_ringtone)
+        super.onCreate(savedInstanceState)
+        val account = mAccountService.getAccount(intent.extras!!.getString(AccountEditionFragment.ACCOUNT_ID_KEY)!!)
+        if (account == null) {
+            finish()
+            return
+        }
+        mAccount = account
+
+        /*Toolbar toolbar = findViewById(R.id.ringtoneToolbar);
+        toolbar.setNavigationOnClickListener(view -> finish());*/
+        val recycler = findViewById<RecyclerView>(R.id.ringToneRecycler)
+        val customRingtoneLayout = findViewById<ConstraintLayout>(R.id.customRingtoneLayout)
+        customRingtone = findViewById(R.id.customRingtoneName)
+        customPlaying = findViewById(R.id.custom_ringtone_playing)
+        customSelected = findViewById(R.id.custom_ringtone_selected)
+        adapter = RingtoneAdapter(prepareRingtones())
+        val upcomingLayoutManager: LayoutManager = LinearLayoutManager(this)
+        recycler.layoutManager = upcomingLayoutManager
+        recycler.itemAnimator = DefaultItemAnimator()
+        recycler.adapter = adapter
+
+        // loads the user's settings
+        setPreference()
+        customRingtoneLayout.setOnClickListener { displayFileSearchDialog() }
+        customRingtoneLayout.setOnLongClickListener {
+            displayRemoveDialog()
+            true
+        }
+        disposable = adapter!!.ringtoneSubject.subscribe({ ringtone: Ringtone ->
+            setJamiRingtone(ringtone)
+            removeCustomRingtone()
+        }) { Log.e(TAG, "Error updating ringtone status") }
+    }
+
+    public override fun onDestroy() {
+        super.onDestroy()
+        disposable!!.dispose()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        stopCustomPreview()
+    }
+
+    override fun finish() {
+        super.finish()
+        adapter!!.releaseMediaPlayer()
+        mediaPlayer.release()
+    }
+
+    private fun prepareRingtones(): List<Ringtone> {
+        val ringtoneList: MutableList<Ringtone> = ArrayList()
+        val ringtoneFolder = File(filesDir, "ringtones")
+        val ringtones = ringtoneFolder.listFiles()
+        val ringtoneIcon = getDrawable(R.drawable.baseline_notifications_active_24)
+        if (ringtones == null) return ringtoneList
+        Arrays.sort(ringtones) { a: File, b: File -> a.name.compareTo(b.name) }
+        ringtoneList.add(Ringtone("Silent", null, getDrawable(R.drawable.baseline_notifications_off_24)))
+        for (file in ringtones) {
+            val name = stripFileNameExtension(file.name)
+            ringtoneList.add(Ringtone(name, file.absolutePath, ringtoneIcon))
+        }
+        return ringtoneList
+    }
+
+    /**
+     * Sets the selected ringtone (Jami or custom) on activity startup
+     */
+    private fun setPreference() {
+        val path = File(mAccount.config[ConfigKey.RINGTONE_PATH])
+        val customEnabled = mAccount.config.getBool(ConfigKey.RINGTONE_CUSTOM)
+        if (customEnabled && path.exists()) {
+            customRingtone!!.text = path.name
+            customSelected!!.visibility = View.VISIBLE
+        } else if (path.exists()) {
+            adapter!!.selectDefaultItem(path.absolutePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED))
+        } else {
+            setDefaultRingtone()
+        }
+    }
+
+    private fun setDefaultRingtone() {
+        val ringtonesDir = File(filesDir, "ringtones")
+        val ringtonePath = File(ringtonesDir, getString(R.string.ringtone_default_name)).absolutePath
+        adapter!!.selectDefaultItem(ringtonePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED))
+        mAccount.setDetail(ConfigKey.RINGTONE_PATH, ringtonePath)
+        mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, false)
+        updateAccount()
+    }
+
+    /**
+     * Sets a Jami ringtone as the default
+     *
+     * @param ringtone the ringtone object
+     */
+    private fun setJamiRingtone(ringtone: Ringtone) {
+        val path = ringtone.ringtonePath
+        if (path == null) {
+            mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, false)
+            mAccount.setDetail(ConfigKey.RINGTONE_PATH, "")
+        } else {
+            mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, true)
+            mAccount.setDetail(ConfigKey.RINGTONE_PATH, ringtone.ringtonePath)
+            mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, false)
+        }
+        updateAccount()
+    }
+
+    /**
+     * Sets a custom ringtone selected by the user
+     *
+     * @param path the ringtoen path
+     * @see .onFileFound
+     * @see .displayFileSearchDialog
+     */
+    private fun setCustomRingtone(path: String) {
+        mAccount.setDetail(ConfigKey.RINGTONE_ENABLED, true)
+        mAccount.setDetail(ConfigKey.RINGTONE_PATH, path)
+        mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, true)
+        updateAccount()
+    }
+
+    /**
+     * Updates an account with new details
+     */
+    private fun updateAccount() {
+        mAccountService.setCredentials(mAccount.accountID, mAccount.credentialsHashMapList)
+        mAccountService.setAccountDetails(mAccount.accountID, mAccount.details)
+    }
+
+    /**
+     * Previews a custom ringtone
+     *
+     * @param ringtone the ringtone file
+     */
+    private fun previewRingtone(ringtone: File) {
+        try {
+            mediaPlayer.setDataSource(ringtone.absolutePath)
+            mediaPlayer.prepare()
+            mediaPlayer.start()
+        } catch (e: IOException) {
+            stopCustomPreview()
+            Log.e(TAG, "Error previewing ringtone", e)
+        } catch (e: NullPointerException) {
+            stopCustomPreview()
+            Log.e(TAG, "Error previewing ringtone", e)
+        }
+        mediaPlayer.setOnCompletionListener { stopCustomPreview() }
+    }
+
+    /**
+     * Removes a custom ringtone and updates the view
+     */
+    private fun removeCustomRingtone() {
+        customSelected!!.visibility = View.INVISIBLE
+        customPlaying!!.visibility = View.INVISIBLE
+        customRingtone!!.setText(R.string.ringtone_custom_prompt)
+        stopCustomPreview()
+    }
+
+    /**
+     * Stops audio previews from all possible sources
+     */
+    private fun stopCustomPreview() {
+        try {
+            if (mediaPlayer.isPlaying) mediaPlayer.stop()
+            mediaPlayer.reset()
+        } catch (e: Exception) {
+        }
+    }
+
+    /**
+     * Handles playing and setting a custom ringtone or displaying an error if it is too large
+     *
+     * @param ringtone the ringtone path
+     */
+    private fun onFileFound(ringtone: File) {
+        if (ringtone.length() / 1024 > MAX_SIZE_RINGTONE) {
+            displayFileTooBigDialog()
+        } else {
+            // resetState will stop the preview
+            adapter!!.resetState()
+            customRingtone!!.text = ringtone.name
+            customSelected!!.visibility = View.VISIBLE
+            customPlaying!!.visibility = View.VISIBLE
+            Glide.with(this)
+                .load(R.raw.baseline_graphic_eq_black_24dp)
+                .placeholder(R.drawable.baseline_graphic_eq_24)
+                .into(DrawableImageViewTarget(customPlaying))
+            previewRingtone(ringtone)
+            setCustomRingtone(ringtone.absolutePath)
+        }
+    }
+
+    /**
+     * Displays the native file browser to select a ringtone
+     */
+    private fun displayFileSearchDialog() {
+        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+        intent.addCategory(Intent.CATEGORY_OPENABLE)
+        intent.type = "audio/*"
+        startActivityForResult(intent, SELECT_RINGTONE_PATH)
+    }
+
+    /**
+     * Displays a dialog if the selected ringtone is too large
+     */
+    private fun displayFileTooBigDialog() {
+        AlertDialog.Builder(this)
+            .setTitle(R.string.ringtone_error_title)
+            .setMessage(getString(R.string.ringtone_error_size_too_big, MAX_SIZE_RINGTONE))
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    /**
+     * Displays a dialog that prompts the user to remove a custom ringtone
+     */
+    private fun displayRemoveDialog() {
+        if (!mAccount.config.getBool(ConfigKey.RINGTONE_CUSTOM)) return
+        val item = arrayOf("Remove")
+        // subject callback from adapter will update the view
+        AlertDialog.Builder(this)
+            .setItems(item) { _: DialogInterface?, _: Int -> setDefaultRingtone() }.show()
+    }
+
+    @SuppressLint("WrongConstant")
+    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (data == null) return
+        val uri = data.data
+        if (resultCode == RESULT_CANCELED || uri == null) {
+            return
+        }
+        val cr = contentResolver
+        if (requestCode == SELECT_RINGTONE_PATH) {
+            try {
+                val path = AndroidFileUtils.getRealPathFromURI(this, uri) ?: throw IllegalArgumentException()
+                onFileFound(File(path))
+            } catch (e: Exception) {
+                val takeFlags = (data.flags
+                        and (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                        or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
+                cr.takePersistableUriPermission(uri, takeFlags)
+                AndroidFileUtils.getCacheFile(this, uri)
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe(
+                        { ringtone: File -> onFileFound(ringtone) }
+                    ) {
+                        Toast.makeText(
+                            this,
+                            "Can't load ringtone !",
+                            Toast.LENGTH_SHORT
+                        ).show()
+                    }
+            }
+        }
+    }
+
+    companion object {
+        const val MAX_SIZE_RINGTONE = 64 * 1024
+        private const val SELECT_RINGTONE_PATH = 40
+
+        /**
+         * Gets the name of a file without its extension
+         *
+         * @param fileName the name of the file
+         * @return the base name
+         */
+        fun stripFileNameExtension(fileName: String): String {
+            val index = fileName.lastIndexOf('.')
+            return if (index == -1) {
+                fileName
+            } else {
+                fileName.substring(0, index)
+            }
+        }
+
+        private val TAG = RingtoneActivity::class.java.simpleName
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/client/ShareActivity.java b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt
similarity index 57%
rename from ring-android/app/src/main/java/cx/ring/client/ShareActivity.java
rename to ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt
index c8f01c11a..f996fbf03 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ShareActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt
@@ -16,28 +16,26 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package cx.ring.client;
+package cx.ring.client
 
-import android.content.Intent;
-import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import cx.ring.utils.ConversationPath
+import cx.ring.R
+import dagger.hilt.android.AndroidEntryPoint
 
-import cx.ring.R;
-import cx.ring.utils.ConversationPath;
-
-public class ShareActivity extends AppCompatActivity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        Bundle extra = intent.getExtras();
+@AndroidEntryPoint
+class ShareActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val intent = intent
+        val extra = intent.extras
         if (ConversationPath.fromBundle(extra) != null) {
-            intent.setClass(this, ConversationActivity.class);
-            startActivity(intent);
-            finish();
-            return;
+            intent.setClass(this, ConversationActivity::class.java)
+            startActivity(intent)
+            finish()
+            return
         }
-        setContentView(R.layout.activity_share);
+        setContentView(R.layout.activity_share)
     }
-
-}
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.java b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.java
deleted file mode 100644
index ec070b45f..000000000
--- a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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.contactrequests;
-
-import androidx.recyclerview.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import cx.ring.R;
-import net.jami.model.Contact;
-
-public class BlockListAdapter extends RecyclerView.Adapter<BlockListViewHolder> {
-
-    private final BlockListViewHolder.BlockListListeners mListener;
-    private final ArrayList<Contact> mBlacklisted;
-
-    public BlockListAdapter(Collection<Contact> viewModels, BlockListViewHolder.BlockListListeners listener) {
-        mBlacklisted = new ArrayList<>(viewModels);
-        mListener = listener;
-    }
-
-    public void replaceAll(Collection<Contact> viewModels) {
-        mBlacklisted.clear();
-        mBlacklisted.addAll(viewModels);
-        notifyDataSetChanged();
-    }
-
-    @Override
-    public BlockListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View holderView = LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.item_contact_blacklist, parent, false);
-
-        return new BlockListViewHolder(holderView);
-    }
-
-    @Override
-    public void onBindViewHolder(BlockListViewHolder holder, int position) {
-        final Contact contact = mBlacklisted.get(position);
-        holder.bind(mListener, contact);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mBlacklisted.size();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.kt b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.kt
new file mode 100644
index 000000000..c0e729e7d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListAdapter.kt
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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.contactrequests
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.R
+import cx.ring.contactrequests.BlockListViewHolder.BlockListListeners
+import net.jami.model.Contact
+
+class BlockListAdapter(viewModels: Collection<Contact>, listener: BlockListListeners) :
+    RecyclerView.Adapter<BlockListViewHolder>() {
+    private val mListener: BlockListListeners = listener
+    private val mBlacklisted: ArrayList<Contact> = ArrayList(viewModels)
+    fun replaceAll(viewModels: Collection<Contact>) {
+        mBlacklisted.clear()
+        mBlacklisted.addAll(viewModels)
+        notifyDataSetChanged()
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BlockListViewHolder {
+        val holderView = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_contact_blacklist, parent, false)
+        return BlockListViewHolder(holderView)
+    }
+
+    override fun onBindViewHolder(holder: BlockListViewHolder, position: Int) {
+        val contact = mBlacklisted[position]
+        holder.bind(mListener, contact)
+    }
+
+    override fun getItemCount(): Int {
+        return mBlacklisted.size
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.java b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.java
deleted file mode 100644
index c0bcece5b..000000000
--- a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.contactrequests;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.Collection;
-
-import cx.ring.account.AccountEditionFragment;
-import cx.ring.account.JamiAccountSummaryFragment;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.FragBlocklistBinding;
-
-import net.jami.contactrequests.BlockListPresenter;
-import net.jami.contactrequests.BlockListView;
-import net.jami.model.Contact;
-import cx.ring.mvp.BaseSupportFragment;
-
-public class BlockListFragment extends BaseSupportFragment<BlockListPresenter> implements BlockListView,
-        BlockListViewHolder.BlockListListeners {
-
-    public static final String TAG = BlockListFragment.class.getSimpleName();
-
-    private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) {
-        @Override
-        public void handleOnBackPressed() {
-            mOnBackPressedCallback.setEnabled(false);
-            JamiAccountSummaryFragment fragment = (JamiAccountSummaryFragment) getParentFragment();
-            if (fragment != null) {
-                fragment.popBackStack();
-            }
-        }
-    };
-
-    private BlockListAdapter mAdapter;
-    private FragBlocklistBinding binding;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragBlocklistBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        setHasOptionsMenu(true);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (getArguments() == null || getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY) == null) {
-            return;
-        }
-        String mAccountId = getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY);
-        mOnBackPressedCallback.setEnabled(true);
-        presenter.setAccountId(mAccountId);
-    }
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        requireActivity().getOnBackPressedDispatcher().addCallback(this, mOnBackPressedCallback);
-    }
-
-    @Override
-    public void onUnblockClicked(Contact viewModel) {
-        presenter.unblockClicked(viewModel);
-    }
-
-    @Override
-    public void updateView(final Collection<Contact> list) {
-        binding.blocklist.setVisibility(View.VISIBLE);
-        if (binding.blocklist.getAdapter() != null) {
-            mAdapter.replaceAll(list);
-        } else {
-            mAdapter = new BlockListAdapter(list, BlockListFragment.this);
-            LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
-            binding.blocklist.setLayoutManager(layoutManager);
-            binding.blocklist.setAdapter(mAdapter);
-        }
-    }
-
-    @Override
-    public void hideListView() {
-        binding.blocklist.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void displayEmptyListMessage(final boolean display) {
-        binding.placeholder.setVisibility(display ? View.VISIBLE : View.GONE);
-    }
-
-    public void setAccount(String accountId) {
-        presenter.setAccountId(accountId);
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.kt b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.kt
new file mode 100644
index 000000000..8512752e9
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListFragment.kt
@@ -0,0 +1,114 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.contactrequests
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedCallback
+import androidx.recyclerview.widget.LinearLayoutManager
+import cx.ring.account.AccountEditionFragment
+import cx.ring.account.JamiAccountSummaryFragment
+import cx.ring.contactrequests.BlockListViewHolder.BlockListListeners
+import cx.ring.databinding.FragBlocklistBinding
+import cx.ring.mvp.BaseSupportFragment
+import dagger.hilt.android.AndroidEntryPoint
+import net.jami.contactrequests.BlockListPresenter
+import net.jami.contactrequests.BlockListView
+import net.jami.model.Contact
+
+@AndroidEntryPoint
+class BlockListFragment : BaseSupportFragment<BlockListPresenter, BlockListView>(), BlockListView,
+    BlockListListeners {
+    private val mOnBackPressedCallback: OnBackPressedCallback =
+        object : OnBackPressedCallback(false) {
+            override fun handleOnBackPressed() {
+                this.isEnabled = false
+                val fragment = parentFragment as JamiAccountSummaryFragment?
+                fragment?.popBackStack()
+            }
+        }
+    private var mAdapter: BlockListAdapter? = null
+    private var binding: FragBlocklistBinding? = null
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        binding = FragBlocklistBinding.inflate(inflater, container, false)
+        setHasOptionsMenu(true)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (arguments == null || requireArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY) == null) {
+            return
+        }
+        val mAccountId = requireArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY)
+        mOnBackPressedCallback.isEnabled = true
+        presenter.setAccountId(mAccountId)
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        requireActivity().onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
+    }
+
+    override fun onUnblockClicked(contact: Contact) {
+        presenter.unblockClicked(contact)
+    }
+
+    override fun updateView(list: Collection<Contact>) {
+        binding!!.blocklist.visibility = View.VISIBLE
+        if (binding!!.blocklist.adapter != null) {
+            mAdapter!!.replaceAll(list)
+        } else {
+            mAdapter = BlockListAdapter(list, this@BlockListFragment)
+            val layoutManager = LinearLayoutManager(activity)
+            binding!!.blocklist.layoutManager = layoutManager
+            binding!!.blocklist.adapter = mAdapter
+        }
+    }
+
+    override fun hideListView() {
+        binding!!.blocklist.visibility = View.GONE
+    }
+
+    override fun displayEmptyListMessage(display: Boolean) {
+        binding!!.placeholder.visibility = if (display) View.VISIBLE else View.GONE
+    }
+
+    fun setAccount(accountId: String?) {
+        presenter.setAccountId(accountId)
+    }
+
+    companion object {
+        @JvmStatic
+        val TAG: String = BlockListFragment::class.simpleName!!
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.java b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.java
deleted file mode 100644
index 79b146868..000000000
--- a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.contactrequests;
-
-import androidx.recyclerview.widget.RecyclerView;
-import android.view.View;
-import cx.ring.views.AvatarFactory;
-import cx.ring.databinding.ItemContactBlacklistBinding;
-import net.jami.model.Contact;
-
-public class BlockListViewHolder extends RecyclerView.ViewHolder {
-    private final ItemContactBlacklistBinding binding;
-
-    BlockListViewHolder(View view) {
-        super(view);
-        binding = ItemContactBlacklistBinding.bind(view);
-    }
-
-    void bind(final BlockListListeners clickListener, final Contact contact) {
-        AvatarFactory.loadGlideAvatar(binding.photo, contact);
-        binding.displayName.setText(contact.getRingUsername());
-        binding.unblock.setOnClickListener(view -> clickListener.onUnblockClicked(contact));
-    }
-
-    public interface BlockListListeners {
-        void onUnblockClicked(Contact contact);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.kt b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.kt
new file mode 100644
index 000000000..9c65ce3b6
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/contactrequests/BlockListViewHolder.kt
@@ -0,0 +1,39 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.contactrequests
+
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.databinding.ItemContactBlacklistBinding
+import net.jami.model.Contact
+import cx.ring.views.AvatarFactory
+
+class BlockListViewHolder internal constructor(view: View) : RecyclerView.ViewHolder(view) {
+    private val binding: ItemContactBlacklistBinding = ItemContactBlacklistBinding.bind(view)
+    fun bind(clickListener: BlockListListeners, contact: Contact) {
+        AvatarFactory.loadGlideAvatar(binding.photo, contact)
+        binding.displayName.text = contact.ringUsername
+        binding.unblock.setOnClickListener { clickListener.onUnblockClicked(contact) }
+    }
+
+    interface BlockListListeners {
+        fun onUnblockClicked(contact: Contact)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.java b/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.java
deleted file mode 100644
index e43507f9d..000000000
--- a/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.contactrequests;
-
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.adapters.SmartListAdapter;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.HomeActivity;
-import cx.ring.databinding.FragPendingContactRequestsBinding;
-
-import net.jami.contactrequests.ContactRequestsPresenter;
-import net.jami.contactrequests.ContactRequestsView;
-import net.jami.model.Uri;
-import cx.ring.mvp.BaseSupportFragment;
-import net.jami.smartlist.SmartListViewModel;
-
-import cx.ring.viewholders.SmartListViewHolder;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class ContactRequestsFragment extends BaseSupportFragment<ContactRequestsPresenter> implements ContactRequestsView,
-        SmartListViewHolder.SmartListListeners {
-
-    private  static final String TAG = ContactRequestsFragment.class.getSimpleName();
-    public static final String ACCOUNT_ID = TAG + "accountID";
-
-    private static final int SCROLL_DIRECTION_UP = -1;
-
-    private SmartListAdapter mAdapter;
-    private FragPendingContactRequestsBinding binding;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragPendingContactRequestsBinding.inflate(inflater, container, false);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        setHasOptionsMenu(true);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mAdapter = null;
-        binding = null;
-    }
-
-    public void presentForAccount(@Nullable String accountId) {
-        Bundle arguments = getArguments();
-        if (arguments != null)
-            arguments.putString(ACCOUNT_ID, accountId);
-        if (presenter != null)
-            presenter.updateAccount(accountId);
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        Bundle arguments = getArguments();
-        String accountId = arguments != null ? arguments.getString(ACCOUNT_ID) : null;
-        presenter.updateAccount(accountId);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, @NonNull MenuInflater inflater) {
-        menu.clear();
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(Menu menu) {
-        menu.clear();
-    }
-
-    @Override
-    public void updateView(final List<SmartListViewModel> list, CompositeDisposable disposable) {
-        if (binding == null) {
-            return;
-        }
-
-        if (!list.isEmpty()) {
-            binding.paneRingID.setVisibility(/*viewModel.hasPane() ? View.VISIBLE :*/ View.GONE);
-        }
-
-        binding.placeholder.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
-
-        if (binding.requestsList.getAdapter() != null) {
-            mAdapter.update(list);
-        } else {
-            mAdapter = new SmartListAdapter(list, ContactRequestsFragment.this, disposable);
-            binding.requestsList.setAdapter(mAdapter);
-            LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
-            binding.requestsList.setLayoutManager(mLayoutManager);
-        }
-
-        binding.requestsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
-                super.onScrollStateChanged(recyclerView, newState);
-            }
-
-            @Override
-            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                ((HomeActivity) requireActivity()).setToolbarElevation(recyclerView.canScrollVertically(SCROLL_DIRECTION_UP));
-            }
-        });
-
-        updateBadge();
-    }
-
-    @Override
-    public void updateItem(SmartListViewModel item) {
-        if (mAdapter != null) {
-            mAdapter.update(item);
-        }
-    }
-
-    @Override
-    public void goToConversation(String accountId, Uri contactId) {
-        ((HomeActivity) requireActivity()).startConversation(accountId, contactId);
-    }
-
-    @Override
-    public void onItemClick(SmartListViewModel viewModel) {
-        presenter.contactRequestClicked(viewModel.getAccountId(), viewModel.getUri());
-    }
-
-    @Override
-    public void onItemLongClick(SmartListViewModel smartListViewModel) {
-
-    }
-
-    private void updateBadge() {
-        ((HomeActivity) requireActivity()).setBadge(R.id.navigation_requests, mAdapter.getItemCount());
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt b/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt
new file mode 100644
index 000000000..0d5373103
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt
@@ -0,0 +1,120 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.contactrequests
+
+import android.os.Bundle
+import android.view.*
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.adapters.SmartListAdapter
+import cx.ring.client.HomeActivity
+import cx.ring.databinding.FragPendingContactRequestsBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.viewholders.SmartListViewHolder.SmartListListeners
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.contactrequests.ContactRequestsPresenter
+import net.jami.contactrequests.ContactRequestsView
+import net.jami.model.Uri
+import net.jami.smartlist.SmartListViewModel
+
+@AndroidEntryPoint
+class ContactRequestsFragment :
+    BaseSupportFragment<ContactRequestsPresenter, ContactRequestsView>(), ContactRequestsView,
+    SmartListListeners {
+    private var mAdapter: SmartListAdapter? = null
+    private var binding: FragPendingContactRequestsBinding? = null
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        binding = FragPendingContactRequestsBinding.inflate(inflater, container, false)
+        setHasOptionsMenu(true)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mAdapter = null
+        binding = null
+    }
+
+    fun presentForAccount(accountId: String?) {
+        arguments?.putString(ACCOUNT_ID, accountId)
+        presenter.updateAccount(accountId)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        presenter.updateAccount(arguments?.getString(ACCOUNT_ID))
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        menu.clear()
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        menu.clear()
+    }
+
+    override fun updateView(list: MutableList<SmartListViewModel>, disposable: CompositeDisposable) {
+        if (binding == null) {
+            return
+        }
+        if (list.isNotEmpty()) {
+            binding!!.paneRingID.visibility = View.GONE
+        }
+        binding!!.placeholder.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
+        if (binding!!.requestsList.adapter != null) {
+            mAdapter!!.update(list)
+        } else {
+            mAdapter = SmartListAdapter(list, this@ContactRequestsFragment, disposable)
+            binding!!.requestsList.adapter = mAdapter
+            val mLayoutManager = LinearLayoutManager(activity)
+            binding!!.requestsList.layoutManager = mLayoutManager
+        }
+        binding!!.requestsList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+                (requireActivity() as HomeActivity).setToolbarElevation(
+                    recyclerView.canScrollVertically(
+                        SCROLL_DIRECTION_UP
+                    )
+                )
+            }
+        })
+    }
+
+    override fun updateItem(item: SmartListViewModel) {
+        mAdapter?.update(item)
+    }
+
+    override fun goToConversation(accountId: String, contactId: Uri) {
+        (requireActivity() as HomeActivity).startConversation(accountId, contactId)
+    }
+
+    override fun onItemClick(smartListViewModel: SmartListViewModel) {
+        presenter.contactRequestClicked(smartListViewModel.accountId, smartListViewModel.uri)
+    }
+
+    override fun onItemLongClick(smartListViewModel: SmartListViewModel) {}
+
+    companion object {
+        private val TAG = ContactRequestsFragment::class.java.simpleName
+        val ACCOUNT_ID = TAG + "accountID"
+        private const val SCROLL_DIRECTION_UP = -1
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java
deleted file mode 100755
index bc49eb543..000000000
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionComponent.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.dependencyinjection;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.DaemonService;
-import net.jami.services.HardwareService;
-
-import javax.inject.Singleton;
-
-import cx.ring.account.AccountEditionFragment;
-import cx.ring.account.AccountWizardActivity;
-import cx.ring.account.HomeAccountCreationFragment;
-import cx.ring.account.JamiAccountConnectFragment;
-import cx.ring.account.JamiAccountPasswordFragment;
-import cx.ring.account.JamiAccountSummaryFragment;
-import cx.ring.account.JamiAccountUsernameFragment;
-import cx.ring.account.JamiLinkAccountPasswordFragment;
-import cx.ring.account.ProfileCreationFragment;
-import cx.ring.account.RegisterNameDialog;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.ContactDetailsActivity;
-import cx.ring.client.ConversationSelectionActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.client.LogsActivity;
-import cx.ring.client.RingtoneActivity;
-import cx.ring.contactrequests.BlockListFragment;
-import cx.ring.contactrequests.ContactRequestsFragment;
-import cx.ring.fragments.AccountMigrationFragment;
-import cx.ring.fragments.AdvancedAccountFragment;
-import cx.ring.fragments.CallFragment;
-import cx.ring.fragments.ContactPickerFragment;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.fragments.GeneralAccountFragment;
-import cx.ring.fragments.LinkDeviceFragment;
-import cx.ring.fragments.LocationSharingFragment;
-import cx.ring.fragments.MediaPreferenceFragment;
-import cx.ring.fragments.SIPAccountCreationFragment;
-import cx.ring.fragments.SecurityAccountFragment;
-import cx.ring.fragments.ShareWithFragment;
-import cx.ring.fragments.SmartListFragment;
-import cx.ring.history.DatabaseHelper;
-import cx.ring.service.BootReceiver;
-import cx.ring.service.CallNotificationService;
-import cx.ring.service.DRingService;
-import cx.ring.service.JamiJobService;
-import cx.ring.services.ContactServiceImpl;
-import cx.ring.services.DataTransferService;
-import cx.ring.services.DeviceRuntimeServiceImpl;
-import cx.ring.services.HistoryServiceImpl;
-import cx.ring.service.LocationSharingService;
-import cx.ring.services.NotificationServiceImpl;
-import cx.ring.services.SharedPreferencesServiceImpl;
-import cx.ring.service.SyncService;
-import cx.ring.settings.AccountFragment;
-import cx.ring.settings.SettingsFragment;
-import cx.ring.share.ShareFragment;
-import cx.ring.tv.account.TVAccountExport;
-import cx.ring.tv.account.TVAccountWizard;
-import cx.ring.tv.account.TVHomeAccountCreationFragment;
-import cx.ring.tv.account.TVJamiAccountCreationFragment;
-import cx.ring.tv.account.TVJamiLinkAccountFragment;
-import cx.ring.tv.account.TVProfileCreationFragment;
-import cx.ring.tv.account.TVProfileEditingFragment;
-import cx.ring.tv.account.TVSettingsFragment;
-import cx.ring.tv.account.TVShareFragment;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.tv.call.TVCallFragment;
-import cx.ring.tv.cards.iconcards.IconCardPresenter;
-import cx.ring.tv.contact.TVContactFragment;
-import cx.ring.tv.conversation.TvConversationFragment;
-import cx.ring.tv.main.MainFragment;
-import cx.ring.tv.search.ContactSearchFragment;
-import dagger.Component;
-
-@Singleton
-@Component(modules = {JamiInjectionModule.class, ServiceInjectionModule.class})
-public interface JamiInjectionComponent {
-    void inject(JamiApplication app);
-
-    void inject(HomeActivity activity);
-
-    void inject(DatabaseHelper helper);
-
-    void inject(AccountWizardActivity activity);
-
-    void inject(AccountEditionFragment activity);
-
-    void inject(RingtoneActivity activity);
-
-    void inject(AccountMigrationFragment fragment);
-
-    void inject(SIPAccountCreationFragment fragment);
-
-    void inject(JamiAccountSummaryFragment fragment);
-
-    void inject(CallFragment fragment);
-
-    void inject(SmartListFragment fragment);
-
-    void inject(ConversationSelectionActivity fragment);
-
-    void inject(JamiAccountUsernameFragment fragment);
-
-    void inject(JamiAccountPasswordFragment fragment);
-
-    void inject(MediaPreferenceFragment fragment);
-
-    void inject(SecurityAccountFragment fragment);
-
-    void inject(ShareFragment fragment);
-
-    void inject(SettingsFragment fragment);
-
-    void inject(AccountFragment fragment);
-
-    void inject(ProfileCreationFragment fragment);
-
-    void inject(RegisterNameDialog dialog);
-
-    void inject(ConversationFragment fragment);
-
-    void inject(ContactRequestsFragment fragment);
-
-    void inject(BlockListFragment fragment);
-
-    void inject(DRingService service);
-
-    void inject(DeviceRuntimeServiceImpl service);
-
-    void inject(DaemonService service);
-
-    void inject(CallService service);
-
-    void inject(AccountService service);
-
-    void inject(HardwareService service);
-
-    void inject(SharedPreferencesServiceImpl service);
-
-    void inject(HistoryServiceImpl service);
-
-    void inject(ContactServiceImpl service);
-
-    void inject(NotificationServiceImpl service);
-
-    void inject(ConversationFacade service);
-
-    void inject(CallNotificationService service);
-
-    void inject(DataTransferService service);
-
-    void inject(BootReceiver receiver);
-
-    void inject(AdvancedAccountFragment fragment);
-
-    void inject(GeneralAccountFragment fragment);
-
-    void inject(HomeAccountCreationFragment fragment);
-
-    void inject(JamiLinkAccountPasswordFragment fragment);
-
-    void inject(JamiAccountConnectFragment fragment);
-
-    //    AndroidTV section
-    void inject(TVCallFragment fragment);
-
-    void inject(MainFragment fragment);
-
-    void inject(ContactSearchFragment fragment);
-
-    void inject(cx.ring.tv.main.HomeActivity activity);
-
-    void inject(TVCallActivity activity);
-
-    void inject(TVAccountWizard activity);
-
-    void inject(TVHomeAccountCreationFragment fragment);
-
-    void inject(TVProfileCreationFragment fragment);
-
-    void inject(TVJamiAccountCreationFragment fragment);
-
-    void inject(TVJamiLinkAccountFragment fragment);
-
-    void inject(TVAccountExport fragment);
-
-    void inject(TVProfileEditingFragment activity);
-
-    void inject(TVShareFragment activity);
-
-    void inject(TVContactFragment fragment);
-
-    void inject(TvConversationFragment fragment);
-
-    void inject(TVSettingsFragment tvSettingsFragment);
-
-    void inject(TVSettingsFragment.PrefsFragment prefsFragment);
-
-    void inject(LocationSharingFragment service);
-
-    void inject(JamiJobService service);
-
-    void inject(ShareWithFragment fragment);
-
-    void inject(ContactDetailsActivity fragment);
-
-    void inject(IconCardPresenter presenter);
-
-    void inject(LocationSharingService service);
-
-    void inject(SyncService syncService);
-
-    void inject(LinkDeviceFragment linkDeviceFragment);
-
-    void inject(ContactPickerFragment contactPickerFragment);
-
-    void inject(LogsActivity logsActivity);
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionModule.java
deleted file mode 100755
index 85878a2cf..000000000
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/JamiInjectionModule.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.dependencyinjection;
-
-import android.content.Context;
-
-import cx.ring.application.JamiApplication;
-import dagger.Module;
-import dagger.Provides;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Scheduler;
-
-@Module
-public class JamiInjectionModule {
-
-    private final JamiApplication mJamiApplication;
-
-    public JamiInjectionModule(JamiApplication app) {
-        mJamiApplication = app;
-    }
-
-    @Provides
-    JamiApplication provideJamiApplication() {
-        return mJamiApplication;
-    }
-
-    @Provides
-    Context provideContext() {
-        return mJamiApplication;
-    }
-
-    @Provides
-    Scheduler provideMainSchedulers() {
-        return AndroidSchedulers.mainThread();
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
deleted file mode 100755
index 35214e764..000000000
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.dependencyinjection;
-
-import android.content.Context;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.ContactService;
-import net.jami.services.DaemonService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.HistoryService;
-import net.jami.services.LogService;
-import net.jami.services.NotificationService;
-import net.jami.services.PreferencesService;
-import net.jami.services.VCardService;
-import net.jami.utils.Log;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import cx.ring.application.JamiApplication;
-import cx.ring.services.ContactServiceImpl;
-import cx.ring.services.DeviceRuntimeServiceImpl;
-import cx.ring.services.HardwareServiceImpl;
-import cx.ring.services.HistoryServiceImpl;
-import cx.ring.services.LogServiceImpl;
-import cx.ring.services.NotificationServiceImpl;
-import cx.ring.services.SharedPreferencesServiceImpl;
-import cx.ring.services.VCardServiceImpl;
-import dagger.Module;
-import dagger.Provides;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Scheduler;
-
-@Module
-public class ServiceInjectionModule {
-
-    private final JamiApplication mJamiApplication;
-
-    public ServiceInjectionModule(JamiApplication app) {
-        mJamiApplication = app;
-    }
-
-    @Provides
-    @Singleton
-    PreferencesService provideSettingsService() {
-        SharedPreferencesServiceImpl settingsService = new SharedPreferencesServiceImpl();
-        mJamiApplication.getInjectionComponent().inject(settingsService);
-        return settingsService;
-    }
-
-    @Provides
-    @Singleton
-    HistoryService provideHistoryService() {
-        HistoryServiceImpl historyService = new HistoryServiceImpl();
-        mJamiApplication.getInjectionComponent().inject(historyService);
-        return historyService;
-    }
-
-    @Provides
-    @Singleton
-    LogService provideLogService() {
-        LogService service = new LogServiceImpl();
-        Log.injectLogService(service);
-        return service;
-    }
-
-    @Provides
-    @Singleton
-    NotificationService provideNotificationService() {
-        NotificationServiceImpl service = new NotificationServiceImpl();
-        mJamiApplication.getInjectionComponent().inject(service);
-        service.initHelper();
-        return service;
-    }
-
-    @Provides
-    @Singleton
-    DeviceRuntimeService provideDeviceRuntimeService(LogService logService) {
-        DeviceRuntimeServiceImpl runtimeService = new DeviceRuntimeServiceImpl();
-        mJamiApplication.getInjectionComponent().inject(runtimeService);
-        runtimeService.loadNativeLibrary();
-        return runtimeService;
-    }
-
-    @Provides
-    @Singleton
-    DaemonService provideDaemonService(DeviceRuntimeService deviceRuntimeService) {
-        DaemonService daemonService = new DaemonService(deviceRuntimeService);
-        mJamiApplication.getInjectionComponent().inject(daemonService);
-        return daemonService;
-    }
-
-    @Provides
-    @Singleton
-    CallService provideCallService() {
-        CallService callService = new CallService();
-        mJamiApplication.getInjectionComponent().inject(callService);
-        return callService;
-    }
-
-    @Provides
-    @Singleton
-    AccountService provideAccountService() {
-        AccountService accountService = new AccountService();
-        mJamiApplication.getInjectionComponent().inject(accountService);
-        return accountService;
-    }
-
-    @Provides
-    @Singleton
-    HardwareService provideHardwareService(Context context) {
-        HardwareServiceImpl hardwareService = new HardwareServiceImpl(context);
-        mJamiApplication.getInjectionComponent().inject(hardwareService);
-        return hardwareService;
-    }
-
-    @Provides
-    @Singleton
-    ContactService provideContactService(PreferencesService sharedPreferencesService) {
-        ContactServiceImpl contactService = new ContactServiceImpl();
-        mJamiApplication.getInjectionComponent().inject(contactService);
-        return contactService;
-    }
-
-    @Provides
-    @Singleton
-    ConversationFacade provideConversationFacade(
-            HistoryService historyService,
-            CallService callService,
-            ContactService contactService,
-            AccountService accountService,
-            NotificationService notificationService) {
-        ConversationFacade conversationFacade = new ConversationFacade(historyService, callService, accountService, contactService, notificationService);
-        mJamiApplication.getInjectionComponent().inject(conversationFacade);
-        return conversationFacade;
-    }
-
-    @Provides
-    @Singleton
-    VCardService provideVCardService(Context context) {
-        return new VCardServiceImpl(context);
-    }
-
-    @Provides
-    @Named("DaemonExecutor")
-    @Singleton
-    ScheduledExecutorService provideDaemonExecutorService() {
-        return Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "DRing"));
-    }
-
-    @Provides
-    @Named("UiScheduler")
-    @Singleton
-    Scheduler provideUiScheduler() {
-        return AndroidSchedulers.mainThread();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.kt b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.kt
new file mode 100755
index 000000000..2e8bd5916
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.kt
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.dependencyinjection
+
+import android.content.Context
+import cx.ring.services.*
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Scheduler
+import net.jami.services.ConversationFacade
+import net.jami.services.*
+import net.jami.utils.Log
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import javax.inject.Named
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ServiceInjectionModule {
+    @Provides
+    @Singleton
+    fun provideSettingsService(@ApplicationContext appContext: Context, accountService: AccountService, deviceService: DeviceRuntimeService): PreferencesService {
+        return SharedPreferencesServiceImpl(appContext, accountService, deviceService)
+    }
+
+    @Provides
+    @Singleton
+    fun provideHistoryService(@ApplicationContext appContext: Context): HistoryService {
+        return HistoryServiceImpl(appContext)
+    }
+
+    @Provides
+    @Singleton
+    fun provideLogService(): LogService {
+        val service: LogService = LogServiceImpl()
+        Log.injectLogService(service)
+        return service
+    }
+
+    @Provides
+    @Singleton
+    fun provideNotificationService(@ApplicationContext appContext: Context, accountService: AccountService,
+                                   contactService: ContactService,
+                                   preferencesService: PreferencesService,
+                                   deviceRuntimeService: DeviceRuntimeService): NotificationService {
+        val service = NotificationServiceImpl(appContext, accountService, contactService, preferencesService, deviceRuntimeService)
+        service.initHelper()
+        return service
+    }
+
+    @Provides
+    @Singleton
+    fun provideDeviceRuntimeService(
+        @ApplicationContext appContext: Context, @Named("DaemonExecutor") executor: ScheduledExecutorService, logService: LogService
+    ): DeviceRuntimeService {
+        val runtimeService = DeviceRuntimeServiceImpl(appContext, executor, logService)
+        runtimeService.loadNativeLibrary()
+        return runtimeService
+    }
+
+    @Provides
+    @Singleton
+    fun provideDaemonService(deviceRuntimeService: DeviceRuntimeService,
+                             @Named("DaemonExecutor") executor: ScheduledExecutorService,
+                             callService: CallService,
+                             hardwareService: HardwareService,
+                             accountService: AccountService): DaemonService {
+        return DaemonService(deviceRuntimeService, executor, callService, hardwareService, accountService)
+    }
+
+    @Provides
+    @Singleton
+    fun provideCallService(@Named("DaemonExecutor") executor : ScheduledExecutorService,
+                           contactService: ContactService,
+                           accountService: AccountService): CallService {
+        return CallService(executor, contactService, accountService)
+    }
+
+    @Provides
+    @Singleton
+    fun provideAccountService(@Named("DaemonExecutor") executor : ScheduledExecutorService,
+                              historyService : HistoryService,
+                              deviceRuntimeService : DeviceRuntimeService,
+                              vCardService : VCardService): AccountService {
+        return AccountService(executor, historyService, deviceRuntimeService, vCardService)
+    }
+
+    @Provides
+    @Singleton
+    fun provideHardwareService(@ApplicationContext appContext: Context,
+                               @Named("DaemonExecutor") executor : ScheduledExecutorService,
+                               preferenceService: PreferencesService,
+                               @Named("UiScheduler") uiScheduler: Scheduler): HardwareService {
+        return HardwareServiceImpl(appContext, executor, preferenceService, uiScheduler)
+    }
+
+    @Provides
+    @Singleton
+    fun provideContactService(@ApplicationContext appContext: Context,
+                              preferenceService: PreferencesService,
+                              deviceRuntimeService : DeviceRuntimeService,
+                              accountService: AccountService): ContactService {
+        return ContactServiceImpl(appContext, preferenceService, deviceRuntimeService, accountService)
+    }
+
+    @Provides
+    @Singleton
+    fun provideConversationFacade(
+        historyService: HistoryService,
+        callService: CallService,
+        contactService: ContactService,
+        accountService: AccountService,
+        notificationService: NotificationService,
+        hardwareService: HardwareService,
+        deviceRuntimeService: DeviceRuntimeService,
+        preferencesService: PreferencesService
+    ): ConversationFacade {
+        return ConversationFacade(
+            historyService,
+            callService,
+            accountService,
+            contactService,
+            notificationService,
+            hardwareService,
+            deviceRuntimeService,
+            preferencesService
+        )
+    }
+
+    @Provides
+    @Singleton
+    fun provideVCardService(@ApplicationContext appContext: Context): VCardService {
+        return VCardServiceImpl(appContext)
+    }
+
+    @Provides
+    @Named("DaemonExecutor")
+    @Singleton
+    fun provideDaemonExecutorService(): ScheduledExecutorService {
+        return Executors.newSingleThreadScheduledExecutor { r: Runnable? -> Thread(r, "DRing") }
+    }
+
+    @Provides
+    @Named("UiScheduler")
+    @Singleton
+    fun provideUiScheduler(): Scheduler {
+        return AndroidSchedulers.mainThread()
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java
index 99ec80a0f..b5c369114 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java
@@ -45,6 +45,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 import cx.ring.R;
 import cx.ring.application.JamiApplication;
 import cx.ring.databinding.FragAccountMigrationBinding;
+import dagger.hilt.android.AndroidEntryPoint;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
@@ -53,6 +54,7 @@ import net.jami.model.AccountConfig;
 import net.jami.model.ConfigKey;
 import net.jami.services.AccountService;
 
+@AndroidEntryPoint
 public class AccountMigrationFragment extends Fragment {
     public static final String ACCOUNT_ID = "ACCOUNT_ID";
     static final String TAG = AccountMigrationFragment.class.getSimpleName();
@@ -77,7 +79,6 @@ public class AccountMigrationFragment extends Fragment {
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
         binding = FragAccountMigrationBinding.inflate(inflater, parent, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.java
deleted file mode 100644
index 613d346fe..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.fragments;
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentManager;
-import androidx.preference.EditTextPreference;
-import androidx.preference.ListPreference;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceGroup;
-import androidx.preference.TwoStatePreference;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-
-import java.util.ArrayList;
-
-import cx.ring.R;
-import cx.ring.account.AccountEditionFragment;
-import cx.ring.application.JamiApplication;
-import net.jami.model.AccountConfig;
-import net.jami.model.ConfigKey;
-import cx.ring.mvp.BasePreferenceFragment;
-import cx.ring.views.EditTextIntegerPreference;
-import cx.ring.views.EditTextPreferenceDialog;
-import cx.ring.views.PasswordPreference;
-
-public class AdvancedAccountFragment extends BasePreferenceFragment<AdvancedAccountPresenter> implements AdvancedAccountView, Preference.OnPreferenceChangeListener {
-
-    public static final String TAG = AdvancedAccountFragment.class.getSimpleName();
-
-    private static final String DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG";
-
-    @Override
-    public void onCreatePreferences(Bundle bundle, String s) {
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        super.onCreatePreferences(bundle, s);
-
-        // Load the preferences from an XML resource
-        addPreferencesFromResource(R.xml.account_advanced_prefs);
-
-        Bundle args = getArguments();
-        presenter.init(args == null  ? null : args.getString(AccountEditionFragment.ACCOUNT_ID_KEY));
-    }
-
-    @Override
-    public void onDisplayPreferenceDialog(Preference preference) {
-        FragmentManager fragmentManager = requireFragmentManager();
-        if (fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
-            return;
-        }
-        if (preference instanceof EditTextIntegerPreference) {
-            EditTextPreferenceDialog f = EditTextPreferenceDialog.newInstance(preference.getKey(), EditorInfo.TYPE_CLASS_NUMBER);
-            f.setTargetFragment(this, 0);
-            f.show(fragmentManager, DIALOG_FRAGMENT_TAG);
-        } else if (preference instanceof PasswordPreference) {
-            EditTextPreferenceDialog f = EditTextPreferenceDialog.newInstance(preference.getKey(), EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
-            f.setTargetFragment(this, 0);
-            f.show(fragmentManager, DIALOG_FRAGMENT_TAG);
-        } else {
-            super.onDisplayPreferenceDialog(preference);
-        }
-    }
-
-    @Override
-    public void initView(AccountConfig config, ArrayList<CharSequence> networkInterfaces) {
-        for (ConfigKey confKey : config.getKeys()) {
-            Preference pref = findPreference(confKey.key());
-            if (pref != null) {
-                pref.setOnPreferenceChangeListener(this);
-                if (confKey == ConfigKey.LOCAL_INTERFACE) {
-                    String val = config.get(confKey);
-                    CharSequence[] display = networkInterfaces.toArray(new CharSequence[networkInterfaces.size()]);
-                    ListPreference listPref = (ListPreference) pref;
-                    listPref.setEntries(display);
-                    listPref.setEntryValues(display);
-                    listPref.setSummary(val);
-                    listPref.setValue(val);
-                } else if (!confKey.isTwoState()) {
-                    String val = config.get(confKey);
-                    pref.setSummary(val);
-                    if (pref instanceof EditTextPreference) {
-                        ((EditTextPreference) pref).setText(val);
-                    }
-                } else {
-                    ((TwoStatePreference) pref).setChecked(config.getBool(confKey));
-                }
-            }
-        }
-
-        boolean isJamiAccount = config.get(ConfigKey.ACCOUNT_TYPE).equals(AccountConfig.ACCOUNT_TYPE_RING);
-        Preference bootstrap = findPreference(ConfigKey.ACCOUNT_HOSTNAME.key());
-        bootstrap.setVisible(isJamiAccount);
-        Preference sipLocalPort = findPreference(ConfigKey.LOCAL_PORT.key());
-        sipLocalPort.setVisible(!isJamiAccount);
-        Preference sipLocalInterface = findPreference(ConfigKey.LOCAL_INTERFACE.key());
-        sipLocalInterface.setVisible(!isJamiAccount);
-        Preference registrationExpire = findPreference(ConfigKey.REGISTRATION_EXPIRE.key());
-        registrationExpire.setVisible(!isJamiAccount);
-        Preference publishedSameAsLocal = findPreference(ConfigKey.PUBLISHED_SAMEAS_LOCAL.key());
-        publishedSameAsLocal.setVisible(!isJamiAccount);
-        Preference publishedPort = findPreference(ConfigKey.PUBLISHED_PORT.key());
-        publishedPort.setVisible(!isJamiAccount);
-        Preference publishedAddress = findPreference(ConfigKey.PUBLISHED_ADDRESS.key());
-        publishedAddress.setVisible(!isJamiAccount);
-        Preference dhtproxy = findPreference(ConfigKey.PROXY_ENABLED.key());
-        PreferenceGroup dhtGroup = dhtproxy.getParent();
-        dhtGroup.setVisible(isJamiAccount);
-    }
-
-    @Override
-    public boolean onPreferenceChange(Preference preference, Object newValue) {
-        final ConfigKey key = ConfigKey.fromString(preference.getKey());
-
-        presenter.preferenceChanged(key, newValue);
-        if (preference instanceof TwoStatePreference) {
-            presenter.twoStatePreferenceChanged(key, newValue);
-        } else if (preference instanceof PasswordPreference) {
-            presenter.passwordPreferenceChanged(key, newValue);
-            preference.setSummary(TextUtils.isEmpty(newValue.toString()) ? "" : "******");
-        } else {
-            presenter.preferenceChanged(key, newValue);
-            preference.setSummary(newValue.toString());
-        }
-        return true;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt
new file mode 100644
index 000000000..98c0cbab3
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt
@@ -0,0 +1,143 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.fragments
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.inputmethod.EditorInfo
+import androidx.preference.EditTextPreference
+import androidx.preference.ListPreference
+import androidx.preference.Preference
+import androidx.preference.TwoStatePreference
+import cx.ring.R
+import cx.ring.account.AccountEditionFragment
+import cx.ring.mvp.BasePreferenceFragment
+import cx.ring.views.EditTextIntegerPreference
+import cx.ring.views.EditTextPreferenceDialog
+import cx.ring.views.PasswordPreference
+import dagger.hilt.android.AndroidEntryPoint
+import net.jami.model.AccountConfig
+import net.jami.model.ConfigKey
+
+@AndroidEntryPoint
+class AdvancedAccountFragment : BasePreferenceFragment<AdvancedAccountPresenter>(),
+    AdvancedAccountView, Preference.OnPreferenceChangeListener {
+
+    override fun onCreatePreferences(bundle: Bundle?, rootKey: String?) {
+        super.onCreatePreferences(bundle, rootKey)
+
+        // Load the preferences from an XML resource
+        addPreferencesFromResource(R.xml.account_advanced_prefs)
+        val args = arguments
+        presenter!!.init(args?.getString(AccountEditionFragment.ACCOUNT_ID_KEY))
+    }
+
+    override fun onDisplayPreferenceDialog(preference: Preference) {
+        val fragmentManager = parentFragmentManager
+        if (fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
+            return
+        }
+        if (preference is EditTextIntegerPreference) {
+            val f = EditTextPreferenceDialog.newInstance(
+                preference.getKey(),
+                EditorInfo.TYPE_CLASS_NUMBER
+            )
+            f.setTargetFragment(this, 0)
+            f.show(fragmentManager, DIALOG_FRAGMENT_TAG)
+        } else if (preference is PasswordPreference) {
+            val f = EditTextPreferenceDialog.newInstance(
+                preference.getKey(),
+                EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+            )
+            f.setTargetFragment(this, 0)
+            f.show(fragmentManager, DIALOG_FRAGMENT_TAG)
+        } else {
+            super.onDisplayPreferenceDialog(preference)
+        }
+    }
+
+    override fun initView(config: AccountConfig, networkInterfaces: ArrayList<CharSequence>) {
+        for (confKey in config.keys) {
+            val pref = findPreference<Preference>(confKey.key())
+            if (pref != null) {
+                pref.onPreferenceChangeListener = this
+                if (confKey == ConfigKey.LOCAL_INTERFACE) {
+                    val `val` = config[confKey]
+                    val display = networkInterfaces.toTypedArray()
+                    val listPref = pref as ListPreference
+                    listPref.entries = display
+                    listPref.entryValues = display
+                    listPref.summary = `val`
+                    listPref.value = `val`
+                } else if (!confKey.isTwoState) {
+                    val `val` = config[confKey]
+                    pref.summary = `val`
+                    if (pref is EditTextPreference) {
+                        pref.text = `val`
+                    }
+                } else {
+                    (pref as TwoStatePreference).isChecked = config.getBool(confKey)
+                }
+            }
+        }
+        val isJamiAccount = config[ConfigKey.ACCOUNT_TYPE] == AccountConfig.ACCOUNT_TYPE_RING
+        val bootstrap = findPreference<Preference>(ConfigKey.ACCOUNT_HOSTNAME.key())
+        bootstrap?.isVisible = isJamiAccount
+        val sipLocalPort = findPreference<Preference>(ConfigKey.LOCAL_PORT.key())
+        sipLocalPort?.isVisible = !isJamiAccount
+        val sipLocalInterface = findPreference<Preference>(ConfigKey.LOCAL_INTERFACE.key())
+        sipLocalInterface?.isVisible = !isJamiAccount
+        val registrationExpire = findPreference<Preference>(ConfigKey.REGISTRATION_EXPIRE.key())
+        registrationExpire?.isVisible = !isJamiAccount
+        val publishedSameAsLocal = findPreference<Preference>(ConfigKey.PUBLISHED_SAMEAS_LOCAL.key())
+        publishedSameAsLocal?.isVisible = !isJamiAccount
+        val publishedPort = findPreference<Preference>(ConfigKey.PUBLISHED_PORT.key())
+        publishedPort?.isVisible = !isJamiAccount
+        val publishedAddress = findPreference<Preference>(ConfigKey.PUBLISHED_ADDRESS.key())
+        publishedAddress?.isVisible = !isJamiAccount
+        val dhtproxy = findPreference<Preference>(ConfigKey.PROXY_ENABLED.key())
+        dhtproxy?.parent?.isVisible = isJamiAccount
+    }
+
+    override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
+        val key = ConfigKey.fromString(preference.key)
+        presenter!!.preferenceChanged(key, newValue)
+        when (preference) {
+            is TwoStatePreference -> {
+                presenter!!.twoStatePreferenceChanged(key, newValue)
+            }
+            is PasswordPreference -> {
+                presenter!!.passwordPreferenceChanged(key, newValue)
+                preference.setSummary(if (TextUtils.isEmpty(newValue.toString())) "" else "******")
+            }
+            else -> {
+                presenter!!.preferenceChanged(key, newValue)
+                preference.summary = newValue.toString()
+            }
+        }
+        return true
+    }
+
+    companion object {
+        @JvmField
+        val TAG = AdvancedAccountFragment::class.java.simpleName
+        private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java
index 3041d100c..4d2569e4a 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java
@@ -28,7 +28,7 @@ import java.util.Enumeration;
 
 import javax.inject.Inject;
 
-import net.jami.facades.ConversationFacade;
+import net.jami.services.ConversationFacade;
 import net.jami.model.Account;
 import net.jami.model.ConfigKey;
 import net.jami.mvp.RootPresenter;
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
deleted file mode 100644
index 4ebc05c00..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ /dev/null
@@ -1,1601 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.fragments;
-
-import android.Manifest;
-import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.graphics.Matrix;
-import android.graphics.PixelFormat;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.SurfaceTexture;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.media.projection.MediaProjection;
-import android.media.projection.MediaProjectionManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.util.Rational;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.view.menu.MenuBuilder;
-import androidx.appcompat.view.menu.MenuPopupHelper;
-import androidx.appcompat.widget.PopupMenu;
-import androidx.databinding.DataBindingUtil;
-import androidx.fragment.app.FragmentActivity;
-import androidx.percentlayout.widget.PercentFrameLayout;
-
-import com.rodolfonavalon.shaperipplelibrary.model.Circle;
-
-import net.jami.call.CallPresenter;
-import net.jami.call.CallView;
-import net.jami.daemon.JamiService;
-import net.jami.model.Call;
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Uri;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.NotificationService;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.adapters.ConfParticipantAdapter;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import cx.ring.client.ContactDetailsActivity;
-import cx.ring.client.ConversationActivity;
-import cx.ring.client.ConversationSelectionActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.databinding.FragCallBinding;
-import cx.ring.databinding.ItemParticipantLabelBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.plugins.RecyclerPicker.RecyclerPicker;
-import cx.ring.plugins.RecyclerPicker.RecyclerPickerLayoutManager;
-import cx.ring.service.DRingService;
-import cx.ring.utils.ActionHelper;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import cx.ring.utils.MediaButtonsHelper;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class CallFragment extends BaseSupportFragment<CallPresenter> implements CallView, MediaButtonsHelper.MediaButtonsHelperCallback, RecyclerPickerLayoutManager.ItemSelectedListener {
-
-    public static final String TAG = CallFragment.class.getSimpleName();
-
-    public static final String ACTION_PLACE_CALL = "PLACE_CALL";
-    public static final String ACTION_GET_CALL = "GET_CALL";
-
-    public static final String KEY_ACTION = "action";
-    public static final String KEY_CONF_ID = "confId";
-    public static final String KEY_AUDIO_ONLY = "AUDIO_ONLY";
-
-    private static final int REQUEST_CODE_ADD_PARTICIPANT = 6;
-    private static final int REQUEST_PERMISSION_INCOMING = 1003;
-    private static final int REQUEST_PERMISSION_OUTGOING = 1004;
-    private static final int REQUEST_CODE_SCREEN_SHARE = 7;
-
-    private FragCallBinding binding;
-    private OrientationEventListener mOrientationListener;
-
-    private MenuItem dialPadBtn = null;
-    private MenuItem pluginsMenuBtn = null;
-    private boolean restartVideo = false;
-    private boolean restartPreview = false;
-    private PowerManager.WakeLock mScreenWakeLock = null;
-    private int mCurrentOrientation = 0;
-
-    private int mVideoWidth = -1;
-    private int mVideoHeight = -1;
-    private int mPreviewWidth = 720, mPreviewHeight = 1280;
-    private int mPreviewSurfaceWidth = 0, mPreviewSurfaceHeight = 0;
-
-    private MediaProjectionManager mProjectionManager;
-
-    private boolean mBackstackLost = false;
-
-    private ConfParticipantAdapter confAdapter = null;
-    private boolean mConferenceMode = false;
-    private boolean choosePluginMode = false;
-    public boolean isChoosePluginMode() {
-        return choosePluginMode;
-    }
-    private boolean pluginsModeFirst = true;
-    private List<String> callMediaHandlers;
-    private int previousPluginPosition = -1;
-    private RecyclerPicker rp;
-    private final ValueAnimator animation = new ValueAnimator();
-
-    private PointF previewDrag = null;
-    private final ValueAnimator previewSnapAnimation = new ValueAnimator();
-    private final int[] previewMargins = new int[4];
-    private float previewHiddenState = 0;
-    private enum PreviewPosition {LEFT, RIGHT}
-    private PreviewPosition previewPosition = PreviewPosition.RIGHT;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
-
-    public static CallFragment newInstance(@NonNull String action, @Nullable ConversationPath path, @Nullable String contactId, boolean audioOnly) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_ACTION, action);
-        if (path != null)
-            path.toBundle(bundle);
-        bundle.putString(Intent.EXTRA_PHONE_NUMBER, contactId);
-        bundle.putBoolean(KEY_AUDIO_ONLY, audioOnly);
-        CallFragment countDownFragment = new CallFragment();
-        countDownFragment.setArguments(bundle);
-        return countDownFragment;
-    }
-
-    public static CallFragment newInstance(@NonNull String action, @Nullable String confId) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_ACTION, action);
-        bundle.putString(KEY_CONF_ID, confId);
-        CallFragment countDownFragment = new CallFragment();
-        countDownFragment.setArguments(bundle);
-        return countDownFragment;
-    }
-
-    public static int callStateToHumanState(final Call.CallStatus state) {
-        switch (state) {
-            case SEARCHING:
-                return R.string.call_human_state_searching;
-            case CONNECTING:
-                return R.string.call_human_state_connecting;
-            case RINGING:
-                return R.string.call_human_state_ringing;
-            case CURRENT:
-                return R.string.call_human_state_current;
-            case HUNGUP:
-                return R.string.call_human_state_hungup;
-            case BUSY:
-                return R.string.call_human_state_busy;
-            case FAILURE:
-                return R.string.call_human_state_failure;
-            case HOLD:
-                return R.string.call_human_state_hold;
-            case UNHOLD:
-                return R.string.call_human_state_unhold;
-            case OVER:
-                return R.string.call_human_state_over;
-            case NONE:
-            default:
-                return R.string.call_human_state_none;
-        }
-    }
-
-    @Override
-    protected void initPresenter(CallPresenter presenter) {
-        Bundle args = getArguments();
-        if (args != null) {
-            String action = args.getString(KEY_ACTION);
-            if (action != null) {
-                if (action.equals(ACTION_PLACE_CALL)) {
-                    prepareCall(false);
-                } else if (action.equals(ACTION_GET_CALL) || action.equals(CallActivity.ACTION_CALL_ACCEPT)) {
-                    presenter.initIncomingCall(getArguments().getString(KEY_CONF_ID), action.equals(ACTION_GET_CALL));
-                }
-            }
-        }
-    }
-
-    public void onUserLeave() {
-        presenter.requestPipMode();
-    }
-
-    @Override
-    public void enterPipMode(String callId) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            return;
-        }
-        Context context = requireContext();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            PictureInPictureParams.Builder paramBuilder = new PictureInPictureParams.Builder();
-            if (binding.videoSurface.getVisibility() == View.VISIBLE) {
-                int[] l = new int[2];
-                binding.videoSurface.getLocationInWindow(l);
-                int x = l[0];
-                int y = l[1];
-                int w = binding.videoSurface.getWidth();
-                int h = binding.videoSurface.getHeight();
-                Rect videoBounds = new Rect(x, y, x + w, y + h);
-                paramBuilder.setAspectRatio(new Rational(w, h));
-                paramBuilder.setSourceRectHint(videoBounds);
-            } else {
-                return;
-            }
-            ArrayList<RemoteAction> actions = new ArrayList<>(1);
-            actions.add(new RemoteAction(
-                    Icon.createWithResource(context, R.drawable.baseline_call_end_24),
-                    getString(R.string.action_call_hangup),
-                    getString(R.string.action_call_hangup),
-                    PendingIntent.getService(context, new Random().nextInt(),
-                            new Intent(DRingService.ACTION_CALL_END)
-                                    .setClass(context, JamiService.class)
-                                    .putExtra(NotificationService.KEY_CALL_ID, callId), PendingIntent.FLAG_ONE_SHOT)));
-            paramBuilder.setActions(actions);
-            try {
-                requireActivity().enterPictureInPictureMode(paramBuilder.build());
-            } catch (Exception e) {
-                Log.w(TAG, "Can't enter  PIP mode", e);
-            }
-        } else if (DeviceUtils.isTv(context)) {
-            requireActivity().enterPictureInPictureMode();
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (restartVideo && restartPreview) {
-            displayVideoSurface(true, !presenter.isPipMode());
-            restartVideo = false;
-            restartPreview = false;
-        } else if (restartVideo) {
-            displayVideoSurface(true, false);
-            restartVideo = false;
-        }
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        previewSnapAnimation.cancel();
-        if (binding.videoSurface.getVisibility() == View.VISIBLE) {
-            restartVideo = true;
-        }
-        if (!choosePluginMode) {
-            if (binding.previewContainer.getVisibility() == View.VISIBLE) {
-                restartPreview = true;
-            }
-        }else {
-            if (binding.pluginPreviewContainer.getVisibility() == View.VISIBLE) {
-                restartPreview = true;
-                presenter.stopPlugin();
-            }
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        binding = DataBindingUtil.inflate(inflater, R.layout.frag_call, container, false);
-        binding.setPresenter(this);
-        rp = new RecyclerPicker(binding.recyclerPicker,
-                R.layout.item_picker,
-                LinearLayout.HORIZONTAL, this);
-        rp.setFirstLastElementsWidths(112, 112);
-        return binding.getRoot();
-    }
-
-    private final TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
-        @Override
-        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-            mPreviewSurfaceWidth = width;
-            mPreviewSurfaceHeight = height;
-            presenter.previewVideoSurfaceCreated(binding.previewSurface);
-        }
-
-        @Override
-        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-            mPreviewSurfaceWidth = width;
-            mPreviewSurfaceHeight = height;
-            configurePreview(width, 1);
-        }
-
-        @Override
-        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-            presenter.previewVideoSurfaceDestroyed();
-            return true;
-        }
-
-        @Override
-        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-        }
-    };
-
-    /**
-     * @param hiddenState 0.f if fully shown, 1.f if fully hidden.
-     */
-    private void setPreviewDragHiddenState(float hiddenState) {
-        binding.previewSurface.setAlpha(1.f - (3 * hiddenState / 4));
-        binding.pluginPreviewSurface.setAlpha(1.f - (3 * hiddenState / 4));
-        binding.previewHandle.setAlpha(hiddenState);
-        binding.pluginPreviewHandle.setAlpha(hiddenState);
-    }
-
-    @SuppressLint({"ClickableViewAccessibility", "RtlHardcoded", "WakelockTimeout"})
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        setHasOptionsMenu(true);
-        super.onViewCreated(view, savedInstanceState);
-        mCurrentOrientation = getResources().getConfiguration().orientation;
-        float dpRatio = requireActivity().getResources().getDisplayMetrics().density;
-
-        animation.setDuration(150);
-        animation.addUpdateListener(valueAnimator -> {
-            if (binding == null)
-                return;
-            int upBy = (int) valueAnimator.getAnimatedValue();
-            RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) binding.previewContainer.getLayoutParams();
-            layoutParams.setMargins(0, 0, 0, (int) (upBy * dpRatio));
-            binding.previewContainer.setLayoutParams(layoutParams);
-        });
-
-        FragmentActivity activity = getActivity();
-        if (activity != null) {
-            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-            if (activity instanceof AppCompatActivity) {
-                AppCompatActivity ac_activity = (AppCompatActivity) activity;
-                ActionBar ab = ac_activity.getSupportActionBar();
-                if (ab != null) {
-                    ab.setHomeAsUpIndicator(R.drawable.baseline_chat_24);
-                    ab.setDisplayHomeAsUpEnabled(true);
-                }
-            }
-        }
-
-        mProjectionManager = (MediaProjectionManager) requireContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
-
-        PowerManager powerManager = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
-        if (powerManager != null) {
-            mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
-            mScreenWakeLock.setReferenceCounted(false);
-            if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
-                mScreenWakeLock.acquire();
-            }
-        }
-
-        binding.videoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
-        binding.videoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                presenter.videoSurfaceCreated(holder);
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                presenter.videoSurfaceDestroyed();
-            }
-        });
-
-        binding.pluginPreviewSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
-        binding.pluginPreviewSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                presenter.pluginSurfaceCreated(holder);
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                presenter.pluginSurfaceDestroyed();
-            }
-        });
-
-        view.setOnSystemUiVisibilityChangeListener(visibility -> {
-            boolean ui = (visibility & (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN)) == 0;
-            presenter.uiVisibilityChanged(ui);
-        });
-        boolean ui = (view.getSystemUiVisibility() & (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN)) == 0;
-        presenter.uiVisibilityChanged(ui);
-
-        view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                resetVideoSize(mVideoWidth, mVideoHeight));
-
-        WindowManager windowManager = (WindowManager) requireContext().getSystemService(Context.WINDOW_SERVICE);
-        if (windowManager != null) {
-            mOrientationListener = new OrientationEventListener(getContext()) {
-                @Override
-                public void onOrientationChanged(int orientation) {
-                    int rot = windowManager.getDefaultDisplay().getRotation();
-                    if (mCurrentOrientation != rot) {
-                        mCurrentOrientation = rot;
-                        presenter.configurationChanged(rot);
-                    }
-                }
-            };
-            if (mOrientationListener.canDetectOrientation()) {
-                mOrientationListener.enable();
-            }
-        }
-
-        binding.shapeRipple.setRippleShape(new Circle());
-        binding.callSpeakerBtn.setChecked(presenter.isSpeakerphoneOn());
-        binding.callMicBtn.setChecked(presenter.isMicrophoneMuted());
-        binding.pluginPreviewSurface.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight));
-        binding.previewSurface.setSurfaceTextureListener(listener);
-        binding.previewSurface.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight));
-
-        previewSnapAnimation.setDuration(250);
-        previewSnapAnimation.setFloatValues(0.f, 1.f);
-        previewSnapAnimation.setInterpolator(new DecelerateInterpolator());
-        previewSnapAnimation.addUpdateListener(animation -> {
-            float animatedFraction = animation == null ? 1 : animation.getAnimatedFraction();
-            configurePreview(mPreviewSurfaceWidth, animatedFraction);
-        });
-
-        binding.previewContainer.setOnTouchListener((v, event) -> {
-            int action = event.getActionMasked();
-            RelativeLayout parent = (RelativeLayout) v.getParent();
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) v.getLayoutParams();
-
-            if (action == MotionEvent.ACTION_DOWN) {
-                previewSnapAnimation.cancel();
-                previewDrag = new PointF(event.getX(), event.getY());
-                v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation_dragged));
-                params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
-                params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                params.setMargins((int) v.getX(), (int) v.getY(), parent.getWidth() - ((int) v.getX() + v.getWidth()), parent.getHeight() - ((int) v.getY() + v.getHeight()));
-                v.setLayoutParams(params);
-                return true;
-            } else if (action == MotionEvent.ACTION_MOVE) {
-                if (previewDrag != null) {
-                    int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
-                    int currentYPosition = params.topMargin + (int) (event.getY() - previewDrag.y);
-                    params.setMargins(
-                            currentXPosition,
-                            currentYPosition,
-                            -((currentXPosition + v.getWidth()) - (int) event.getX()),
-                            -((currentYPosition + v.getHeight()) - (int) event.getY()));
-                    v.setLayoutParams(params);
-
-                    float outPosition = binding.previewContainer.getWidth() * 0.85f;
-                    float drapOut = 0.f;
-                    if (currentXPosition < 0) {
-                        drapOut = Math.min(1.f, -currentXPosition / outPosition);
-                    } else if (currentXPosition + v.getWidth() > parent.getWidth()) {
-                        drapOut = Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition);
-                    }
-                    setPreviewDragHiddenState(drapOut);
-                    return true;
-                }
-                return false;
-            } else if (action == MotionEvent.ACTION_UP) {
-                if (previewDrag != null) {
-                    int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
-
-                    previewSnapAnimation.cancel();
-                    previewDrag = null;
-                    v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation));
-                    int ml = 0, mr = 0, mt = 0, mb = 0;
-
-                    FrameLayout.LayoutParams hp = (FrameLayout.LayoutParams) binding.previewHandle.getLayoutParams();
-                    if (params.leftMargin + (v.getWidth() / 2) > parent.getWidth() / 2) {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                        mr = (int) (parent.getWidth() - v.getWidth() - v.getX());
-                        previewPosition = PreviewPosition.RIGHT;
-                        hp.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
-                    } else {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                        ml = (int) v.getX();
-                        previewPosition = PreviewPosition.LEFT;
-                        hp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
-                    }
-                    binding.previewHandle.setLayoutParams(hp);
-
-                    if (params.topMargin + (v.getHeight() / 2) > parent.getHeight() / 2) {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                        mb = (int) (parent.getHeight() - v.getHeight() - v.getY());
-                    } else {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
-                        mt = (int) v.getY();
-                    }
-                    previewMargins[0] = ml;
-                    previewMargins[1] = mt;
-                    previewMargins[2] = mr;
-                    previewMargins[3] = mb;
-                    params.setMargins(ml, mt, mr, mb);
-                    v.setLayoutParams(params);
-
-                    float outPosition = binding.previewContainer.getWidth() * 0.85f;
-                    previewHiddenState = currentXPosition < 0
-                            ? Math.min(1.f, -currentXPosition / outPosition)
-                            : ((currentXPosition + v.getWidth() > parent.getWidth())
-                                ? Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition)
-                                : 0.f);
-                    setPreviewDragHiddenState(previewHiddenState);
-
-                    previewSnapAnimation.start();
-                    return true;
-                }
-                return false;
-            } else {
-                return false;
-            }
-        });
-
-        binding.pluginPreviewContainer.setOnTouchListener((v, event) -> {
-            int action = event.getActionMasked();
-            RelativeLayout parent = (RelativeLayout) v.getParent();
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) v.getLayoutParams();
-
-            if (action == MotionEvent.ACTION_DOWN) {
-                previewSnapAnimation.cancel();
-                previewDrag = new PointF(event.getX(), event.getY());
-                v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation_dragged));
-                params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
-                params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                params.setMargins((int) v.getX(), (int) v.getY(), parent.getWidth() - ((int) v.getX() + v.getWidth()), parent.getHeight() - ((int) v.getY() + v.getHeight()));
-                v.setLayoutParams(params);
-                return true;
-            } else if (action == MotionEvent.ACTION_MOVE) {
-                if (previewDrag != null) {
-                    int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
-                    int currentYPosition = params.topMargin + (int) (event.getY() - previewDrag.y);
-                    params.setMargins(
-                            currentXPosition,
-                            currentYPosition,
-                            -((currentXPosition + v.getWidth()) - (int) event.getX()),
-                            -((currentYPosition + v.getHeight()) - (int) event.getY()));
-                    v.setLayoutParams(params);
-
-                    float outPosition = binding.pluginPreviewContainer.getWidth() * 0.85f;
-                    float drapOut = 0.f;
-                    if (currentXPosition < 0) {
-                        drapOut = Math.min(1.f, -currentXPosition / outPosition);
-                    } else if (currentXPosition + v.getWidth() > parent.getWidth()) {
-                        drapOut = Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition);
-                    }
-                    setPreviewDragHiddenState(drapOut);
-                    return true;
-                }
-                return false;
-            } else if (action == MotionEvent.ACTION_UP) {
-                if (previewDrag != null) {
-                    int currentXPosition = params.leftMargin + (int) (event.getX() - previewDrag.x);
-
-                    previewSnapAnimation.cancel();
-                    previewDrag = null;
-                    v.setElevation(v.getContext().getResources().getDimension(R.dimen.call_preview_elevation));
-                    int ml = 0, mr = 0, mt = 0, mb = 0;
-
-                    FrameLayout.LayoutParams hp = (FrameLayout.LayoutParams) binding.pluginPreviewHandle.getLayoutParams();
-                    if (params.leftMargin + (v.getWidth() / 2) > parent.getWidth() / 2) {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                        mr = (int) (parent.getWidth() - v.getWidth() - v.getX());
-                        previewPosition = PreviewPosition.RIGHT;
-                        hp.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
-                    } else {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
-                        ml = (int) v.getX();
-                        previewPosition = PreviewPosition.LEFT;
-                        hp.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT;
-                    }
-                    binding.pluginPreviewHandle.setLayoutParams(hp);
-
-                    if (params.topMargin + (v.getHeight() / 2) > parent.getHeight() / 2) {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_TOP);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                        mb = (int) (parent.getHeight() - v.getHeight() - v.getY());
-                    } else {
-                        params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-                        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
-                        mt = (int) v.getY();
-                    }
-                    previewMargins[0] = ml;
-                    previewMargins[1] = mt;
-                    previewMargins[2] = mr;
-                    previewMargins[3] = mb;
-                    params.setMargins(ml, mt, mr, mb);
-                    v.setLayoutParams(params);
-
-                    float outPosition = binding.pluginPreviewContainer.getWidth() * 0.85f;
-                    previewHiddenState = currentXPosition < 0
-                            ? Math.min(1.f, -currentXPosition / outPosition)
-                            : ((currentXPosition + v.getWidth() > parent.getWidth())
-                            ? Math.min(1.f, (currentXPosition + v.getWidth() - parent.getWidth()) / outPosition)
-                            : 0.f);
-                    setPreviewDragHiddenState(previewHiddenState);
-
-                    previewSnapAnimation.start();
-                    return true;
-                }
-                return false;
-            } else {
-                return false;
-            }
-        });
-
-        binding.dialpadEditText.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                presenter.sendDtmf(s.subSequence(start, start + count));
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-            }
-        });
-    }
-
-    private void configurePreview(int width, float animatedFraction) {
-        Context context = getContext();
-        if (context == null || binding == null)
-            return;
-        float margin = context.getResources().getDimension(R.dimen.call_preview_margin);
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.previewContainer.getLayoutParams();
-        float r = 1.f - animatedFraction;
-        float hideMargin = 0.f;
-        float targetHiddenState = 0.f;
-        if (previewHiddenState > 0.f) {
-            targetHiddenState = 1.f;
-            float v = width * 0.85f * animatedFraction;
-            hideMargin = previewPosition == PreviewPosition.RIGHT ? v : -v;
-        }
-        setPreviewDragHiddenState(previewHiddenState * r + targetHiddenState * animatedFraction);
-
-        float f = margin * animatedFraction;
-        params.setMargins(
-                (int) (previewMargins[0] * r + f + hideMargin),
-                (int) (previewMargins[1] * r + f),
-                (int) (previewMargins[2] * r + f - hideMargin),
-                (int) (previewMargins[3] * r + f));
-        binding.previewContainer.setLayoutParams(params);
-        binding.pluginPreviewContainer.setLayoutParams(params);
-    }
-
-    /**
-     * Releases current wakelock and acquires a new proximity wakelock if current call is audio only.
-     *
-     * @param isAudioOnly true if it is an audio call
-     */
-    @SuppressLint("WakelockTimeout")
-    @Override
-    public void handleCallWakelock(boolean isAudioOnly) {
-        if (isAudioOnly) {
-            if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
-                mScreenWakeLock.release();
-            }
-            PowerManager powerManager = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
-            if (powerManager != null) {
-                mScreenWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
-                mScreenWakeLock.setReferenceCounted(false);
-
-                if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
-                    mScreenWakeLock.acquire();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        if (mOrientationListener != null) {
-            mOrientationListener.disable();
-            mOrientationListener = null;
-        }
-        mCompositeDisposable.clear();
-        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
-            mScreenWakeLock.release();
-        }
-        binding = null;
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mCompositeDisposable.dispose();
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING)
-            return;
-        for (int i = 0, n = permissions.length; i < n; i++) {
-            boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
-            boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
-            switch (permissions[i]) {
-                case Manifest.permission.CAMERA:
-                    presenter.cameraPermissionChanged(granted);
-                    if (audioGranted) {
-                        initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
-                    }
-                    break;
-                case Manifest.permission.RECORD_AUDIO:
-                    presenter.audioPermissionChanged(granted);
-                    initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        if (requestCode == REQUEST_CODE_ADD_PARTICIPANT) {
-            if (resultCode == Activity.RESULT_OK && data != null) {
-                ConversationPath path = ConversationPath.fromUri(data.getData());
-                if (path != null) {
-                    presenter.addConferenceParticipant(path.getAccountId(), path.getConversationUri());
-                }
-            }
-        } else if (requestCode == REQUEST_CODE_SCREEN_SHARE) {
-            if (resultCode == Activity.RESULT_OK && data != null) {
-                try {
-                    startScreenShare(mProjectionManager.getMediaProjection(resultCode, data));
-                } catch (Exception e) {
-                    Log.w(TAG, "Error starting screen sharing", e);
-                }
-            } else {
-                binding.callScreenshareBtn.setChecked(false);
-            }
-        }
-    }
-
-    @Override
-    public void onCreateOptionsMenu(@NonNull Menu m, @NonNull MenuInflater inf) {
-        super.onCreateOptionsMenu(m, inf);
-        inf.inflate(R.menu.ac_call, m);
-        dialPadBtn = m.findItem(R.id.menuitem_dialpad);
-        pluginsMenuBtn = m.findItem(R.id.menuitem_video_plugins);
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(@NonNull Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        presenter.prepareOptionMenu();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
-        super.onOptionsItemSelected(item);
-        int itemId = item.getItemId();
-        if (itemId == android.R.id.home) {
-            presenter.chatClick();
-        } else if (itemId == R.id.menuitem_dialpad) {
-            presenter.dialpadClick();
-        } else if (itemId == R.id.menuitem_video_plugins) {
-            displayVideoPluginsCarousel();
-        }
-        return true;
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        AppCompatActivity activity = (AppCompatActivity) getActivity();
-        ActionBar actionBar = activity == null ? null : activity.getSupportActionBar();
-        if (actionBar != null) {
-            if (isInPictureInPictureMode) {
-                actionBar.hide();
-            } else {
-                mBackstackLost = true;
-                actionBar.show();
-            }
-        }
-        presenter.pipModeChanged(isInPictureInPictureMode);
-    }
-
-    @Override
-    public void displayContactBubble(final boolean display) {
-        if (binding != null)
-            binding.contactBubbleLayout.getHandler().post(() -> {
-                if (binding != null) binding.contactBubbleLayout.setVisibility(display ? View.VISIBLE : View.GONE);
-            });
-    }
-
-    @Override
-    public void displayVideoSurface(final boolean displayVideoSurface, final boolean displayPreviewContainer) {
-        binding.videoSurface.setVisibility(displayVideoSurface ? View.VISIBLE : View.GONE);
-        if (choosePluginMode) {
-            binding.pluginPreviewSurface.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
-            binding.pluginPreviewContainer.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
-            binding.previewContainer.setVisibility(View.GONE);
-        } else {
-            binding.pluginPreviewSurface.setVisibility(View.GONE);
-            binding.pluginPreviewContainer.setVisibility(View.GONE);
-            binding.previewContainer.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
-        }
-        updateMenu();
-    }
-
-    @Override
-    public void displayPreviewSurface(final boolean display) {
-        if (display) {
-            binding.videoSurface.setZOrderOnTop(false);
-            binding.videoSurface.setZOrderMediaOverlay(false);
-        } else {
-            binding.videoSurface.setZOrderMediaOverlay(true);
-            binding.videoSurface.setZOrderOnTop(true);
-        }
-    }
-
-    @Override
-    public void displayHangupButton(boolean display) {
-        Log.w(TAG, "displayHangupButton " + display);
-        display &= !choosePluginMode;
-        binding.callControlGroup.setVisibility(display ? View.VISIBLE : View.GONE);
-        binding.callHangupBtn.setVisibility(display ? View.VISIBLE : View.GONE);
-        binding.confControlGroup.setVisibility((mConferenceMode && display) ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void displayDialPadKeyboard() {
-        binding.dialpadEditText.requestFocus();
-        InputMethodManager imm = (InputMethodManager) binding.dialpadEditText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
-    }
-
-    @Override
-    public void switchCameraIcon(boolean isFront) {
-        binding.callCameraFlipBtn.setImageResource(isFront ? R.drawable.baseline_camera_front_24 : R.drawable.baseline_camera_rear_24);
-    }
-
-    @Override
-    public void updateAudioState(HardwareService.AudioState state) {
-        binding.callSpeakerBtn.setChecked(state.getOutputType() == HardwareService.AudioOutput.SPEAKERS);
-    }
-
-    @Override
-    public void updateMenu() {
-        requireActivity().invalidateOptionsMenu();
-    }
-
-    @Override
-    public void updateTime(final long duration) {
-        if (binding != null) {
-            if (duration <= 0)
-                binding.callStatusTxt.setText(null);
-            else
-                binding.callStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
-        }
-    }
-
-    @Override
-    @SuppressLint("RestrictedApi")
-    public void updateContactBubble(@NonNull final List<Call> contacts) {
-        Log.w(TAG, "updateContactBubble " + contacts.size());
-
-        String username = contacts.size() > 1 ? "Conference with " + contacts.size() + " people" : contacts.get(0).getContact().getDisplayName();
-        String displayName = contacts.size() > 1 ? null : contacts.get(0).getContact().getDisplayName();
-
-        boolean hasProfileName = displayName != null && !displayName.contentEquals(username);
-
-        AppCompatActivity activity = (AppCompatActivity) getActivity();
-        if (activity != null) {
-            ActionBar ab = activity.getSupportActionBar();
-            if (ab != null) {
-                if (hasProfileName) {
-                    ab.setTitle(displayName);
-                    ab.setSubtitle(username);
-                } else {
-                    ab.setTitle(username);
-                    ab.setSubtitle(null);
-                }
-                ab.setDisplayShowTitleEnabled(true);
-            }
-        }
-
-        if (hasProfileName) {
-            binding.contactBubbleNumTxt.setVisibility(View.VISIBLE);
-            binding.contactBubbleTxt.setText(displayName);
-            binding.contactBubbleNumTxt.setText(username);
-        } else {
-            binding.contactBubbleNumTxt.setVisibility(View.GONE);
-            binding.contactBubbleTxt.setText(username);
-        }
-
-        binding.contactBubble.setImageDrawable(
-                new AvatarDrawable.Builder()
-                        .withContact(contacts.get(0).getContact())
-                        .withCircleCrop(true)
-                        .withPresence(false)
-                        .build(getActivity())
-        );
-
-    }
-
-    @SuppressLint("RestrictedApi")
-    @Override
-    public void updateConfInfo(List<Conference.ParticipantInfo> participantInfo) {
-        Log.w(TAG, "updateConfInfo " + participantInfo);
-
-        mConferenceMode = participantInfo.size() > 1;
-
-        binding.participantLabelContainer.removeAllViews();
-        if (!participantInfo.isEmpty()) {
-            LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext());
-            for (Conference.ParticipantInfo i : participantInfo) {
-                String displayName = i.contact.getDisplayName();
-                if (!TextUtils.isEmpty(displayName)) {
-                    ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater);
-                    PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-                    params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth;
-                    params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight;
-                    params.getPercentLayoutInfo().rightMarginPercent = 1.f - (i.x + i.w) / (float) mVideoWidth;
-                    //params.getPercentLayoutInfo().rightMarginPercent = (i.x + i.w) / (float) mVideoWidth;
-                    label.participantName.setText(displayName);
-                    label.moderator.setVisibility(i.isModerator ? View.VISIBLE : View.GONE);
-                    label.mute.setVisibility(i.audioMuted ? View.VISIBLE : View.GONE);
-                    binding.participantLabelContainer.addView(label.getRoot(), params);
-                }
-            }
-        }
-        binding.participantLabelContainer.setVisibility(participantInfo.isEmpty() ? View.GONE : View.VISIBLE);
-
-        if (participantInfo.isEmpty() || participantInfo.size() < 2) {
-            binding.confControlGroup.setVisibility(View.GONE);
-        } else {
-            binding.confControlGroup.setVisibility(View.VISIBLE);
-            if (confAdapter == null) {
-                confAdapter = new ConfParticipantAdapter((view, info) -> {
-                    if (presenter == null)
-                        return;
-                    boolean maximized = presenter.isMaximized(info);
-                    PopupMenu popup = new PopupMenu(view.getContext(), view);
-                    popup.inflate(R.menu.conference_participant_actions);
-                    popup.setOnMenuItemClickListener(item -> {
-                        if (presenter == null)
-                            return false;
-                        int itemId = item.getItemId();
-                        if (itemId == R.id.conv_contact_details) {
-                            presenter.openParticipantContact(info);
-                        } else if (itemId == R.id.conv_contact_hangup) {
-                            presenter.hangupParticipant(info);
-                        } else if (itemId == R.id.conv_mute) {
-                            //call.muteAudio(!info.audioMuted);
-                            presenter.muteParticipant(info, !info.audioMuted);
-                        } else if (itemId == R.id.conv_contact_maximize) {
-                            presenter.maximizeParticipant(info);
-                        } else {
-                            return false;
-                        }
-                        return true;
-                    });
-                    MenuBuilder menu = (MenuBuilder) popup.getMenu();
-                    MenuItem maxItem = menu.findItem(R.id.conv_contact_maximize);
-                    MenuItem muteItem = menu.findItem(R.id.conv_mute);
-                    if (maximized) {
-                        maxItem.setTitle(R.string.action_call_minimize);
-                        maxItem.setIcon(R.drawable.baseline_close_fullscreen_24);
-                    } else {
-                        maxItem.setTitle(R.string.action_call_maximize);
-                        maxItem.setIcon(R.drawable.baseline_open_in_full_24);
-                    }
-                    if (!info.audioMuted) {
-                        muteItem.setTitle(R.string.action_call_mute);
-                        muteItem.setIcon(R.drawable.baseline_mic_off_24);
-                    } else {
-                        muteItem.setTitle(R.string.action_call_unmute);
-                        muteItem.setIcon(R.drawable.baseline_mic_24);
-                    }
-                    MenuPopupHelper menuHelper = new MenuPopupHelper(view.getContext(), menu, view);
-                    menuHelper.setGravity(Gravity.END);
-                    menuHelper.setForceShowIcon(true);
-                    menuHelper.show();
-                });
-            }
-            confAdapter.updateFromCalls(participantInfo);
-            if (binding.confControlGroup.getAdapter() == null)
-                binding.confControlGroup.setAdapter(confAdapter);
-        }
-    }
-
-    @Override
-    public void updateParticipantRecording(Set<Contact> contacts) {
-        if (contacts.size() == 0) {
-            binding.recordLayout.setVisibility(View.INVISIBLE);
-            binding.recordIndicator.clearAnimation();
-            return;
-        }
-        StringBuilder names = new StringBuilder();
-        Iterator<Contact> contact =  contacts.iterator();
-        for (int i = 0; i < contacts.size(); i++) {
-            names.append(" ").append(contact.next().getDisplayName());
-            if (i != contacts.size() - 1) {
-                names.append(",");
-            }
-        }
-        binding.recordLayout.setVisibility(View.VISIBLE);
-        binding.recordIndicator.setAnimation(getBlinkingAnimation());
-        binding.recordName.setText(getString(R.string.remote_recording, names));
-    }
-
-    @Override
-    public void updateCallStatus(final Call.CallStatus callStatus) {
-        binding.callStatusTxt.setText(callStateToHumanState(callStatus));
-    }
-
-    @Override
-    public void initMenu(boolean isSpeakerOn, boolean displayFlip, boolean canDial,
-                         boolean showPluginBtn, boolean onGoingCall) {
-        if (binding != null) {
-            binding.callCameraFlipBtn.setVisibility(displayFlip ? View.VISIBLE : View.GONE);
-        }
-        if (dialPadBtn != null) {
-            dialPadBtn.setVisible(canDial);
-        }
-
-        if (pluginsMenuBtn != null) {
-            pluginsMenuBtn.setVisible(showPluginBtn);
-        }
-        updateMenu();
-    }
-
-    @Override
-    public void initNormalStateDisplay(final boolean audioOnly, boolean isMuted) {
-        Log.w(TAG, "initNormalStateDisplay");
-        binding.shapeRipple.stopRipple();
-
-        binding.callAcceptBtn.setVisibility(View.GONE);
-        binding.callRefuseBtn.setVisibility(View.GONE);
-        binding.callControlGroup.setVisibility(View.VISIBLE);
-        binding.callHangupBtn.setVisibility(View.VISIBLE);
-
-        binding.contactBubbleLayout.setVisibility(audioOnly ? View.VISIBLE : View.GONE);
-        binding.callMicBtn.setChecked(isMuted);
-
-        requireActivity().invalidateOptionsMenu();
-        CallActivity callActivity = (CallActivity) getActivity();
-        if (callActivity != null) {
-            callActivity.showSystemUI();
-        }
-    }
-
-    @Override
-    public void initIncomingCallDisplay() {
-        Log.w(TAG, "initIncomingCallDisplay");
-
-        binding.callAcceptBtn.setVisibility(View.VISIBLE);
-        binding.callRefuseBtn.setVisibility(View.VISIBLE);
-        binding.callControlGroup.setVisibility(View.GONE);
-        binding.callHangupBtn.setVisibility(View.GONE);
-
-        binding.contactBubbleLayout.setVisibility(View.VISIBLE);
-        requireActivity().invalidateOptionsMenu();
-    }
-
-    @Override
-    public void initOutGoingCallDisplay() {
-        Log.w(TAG, "initOutGoingCallDisplay");
-
-        binding.callAcceptBtn.setVisibility(View.GONE);
-        binding.callRefuseBtn.setVisibility(View.VISIBLE);
-        binding.callControlGroup.setVisibility(View.GONE);
-        binding.callHangupBtn.setVisibility(View.GONE);
-
-        binding.contactBubbleLayout.setVisibility(View.VISIBLE);
-        requireActivity().invalidateOptionsMenu();
-    }
-
-    @Override
-    public void resetPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
-        if (previewWidth == -1 && previewHeight == -1)
-            return;
-        mPreviewWidth = previewWidth;
-        mPreviewHeight = previewHeight;
-        boolean flip = (rot % 180) != 0;
-        binding.previewSurface.setAspectRatio(flip ? mPreviewHeight : mPreviewWidth, flip ? mPreviewWidth : mPreviewHeight);
-    }
-
-    @Override
-    public void resetPluginPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
-        if (previewWidth == -1 && previewHeight == -1)
-            return;
-        mPreviewWidth = previewWidth;
-        mPreviewHeight = previewHeight;
-        boolean flip = (rot % 180) != 0;
-        binding.pluginPreviewSurface.setAspectRatio(flip ? mPreviewHeight : mPreviewWidth, flip ? mPreviewWidth : mPreviewHeight);
-    }
-
-    @Override
-    public void resetVideoSize(int videoWidth, int videoHeight) {
-        ViewGroup rootView = (ViewGroup) getView();
-        if (rootView == null)
-            return;
-        double videoRatio = videoWidth / (double) videoHeight;
-        double screenRatio = rootView.getWidth() / (double) rootView.getHeight();
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.videoSurface.getLayoutParams();
-        int oldW = params.width;
-        int oldH = params.height;
-        if (videoRatio >= screenRatio) {
-            params.width = RelativeLayout.LayoutParams.MATCH_PARENT;
-            params.height = (int) (videoHeight * (double) rootView.getWidth() / (double) videoWidth);
-        } else {
-            params.height = RelativeLayout.LayoutParams.MATCH_PARENT;
-            params.width = (int) (videoWidth * (double) rootView.getHeight() / (double) videoHeight);
-        }
-
-        if (oldW != params.width || oldH != params.height) {
-            binding.videoSurface.setLayoutParams(params);
-        }
-        mVideoWidth = videoWidth;
-        mVideoHeight = videoHeight;
-    }
-
-    private void configureTransform(int viewWidth, int viewHeight) {
-        Activity activity = getActivity();
-        if (null == binding || null == activity) {
-            return;
-        }
-        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
-        boolean rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
-        // Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot=" + rot + " mPreviewWidth=" + mPreviewWidth + " mPreviewHeight=" + mPreviewHeight);
-        Matrix matrix = new Matrix();
-        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
-        float centerX = viewRect.centerX();
-        float centerY = viewRect.centerY();
-        if (rot) {
-            RectF bufferRect = new RectF(0, 0, mPreviewHeight, mPreviewWidth);
-            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
-            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
-            float scale = Math.max(
-                    (float) viewHeight / mPreviewHeight,
-                    (float) viewWidth / mPreviewWidth);
-            matrix.postScale(scale, scale, centerX, centerY);
-            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
-        } else if (Surface.ROTATION_180 == rotation) {
-            matrix.postRotate(180, centerX, centerY);
-        }
-        if (!choosePluginMode) {
-//            binding.pluginPreviewSurface.setTransform(matrix);
-//        }
-//        else {
-            binding.previewSurface.setTransform(matrix);
-        }
-    }
-
-    @Override
-    public void goToConversation(String accountId, Uri conversationId) {
-        Context context = requireContext();
-        if (DeviceUtils.isTablet(context)) {
-            startActivity(new Intent(DRingService.ACTION_CONV_ACCEPT, ConversationPath.toUri(accountId, conversationId), context, HomeActivity.class));
-        } else {
-            startActivityForResult(new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, conversationId), context, ConversationActivity.class)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT), HomeActivity.REQUEST_CODE_CONVERSATION);
-        }
-    }
-
-    @Override
-    public void goToAddContact(Contact contact) {
-        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact),
-                ConversationFragment.REQ_ADD_CONTACT);
-    }
-
-    @Override
-    public void goToContact(String accountId, Contact contact) {
-        startActivity(new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, contact.getUri()))
-                .setClass(requireContext(), ContactDetailsActivity.class));
-    }
-
-    /**
-     * Checks if permissions are accepted for camera and microphone. Takes into account whether call is incoming and outgoing, and requests permissions if not available.
-     * Initializes the call if permissions are accepted.
-     *
-     * @param isIncoming true if call is incoming, false for outgoing
-     * @see #initializeCall(boolean) initializeCall
-     */
-    @Override
-    public void prepareCall(boolean isIncoming) {
-        boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
-        boolean audioOnly;
-        int permissionType;
-
-        if (isIncoming) {
-            audioOnly = presenter.isAudioOnly();
-            permissionType = REQUEST_PERMISSION_INCOMING;
-        } else {
-            Bundle args = getArguments();
-            audioOnly = args != null && args.getBoolean(KEY_AUDIO_ONLY);
-            permissionType = REQUEST_PERMISSION_OUTGOING;
-        }
-        if (!audioOnly) {
-            boolean videoGranted = mDeviceRuntimeService.hasVideoPermission();
-
-            if ((!audioGranted || !videoGranted) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                ArrayList<String> perms = new ArrayList<>();
-                if (!videoGranted) {
-                    perms.add(Manifest.permission.CAMERA);
-                }
-                if (!audioGranted) {
-                    perms.add(Manifest.permission.RECORD_AUDIO);
-                }
-                requestPermissions(perms.toArray(new String[perms.size()]), permissionType);
-            } else if (audioGranted && videoGranted) {
-                initializeCall(isIncoming);
-            }
-        } else {
-            if (!audioGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, permissionType);
-            } else if (audioGranted) {
-                initializeCall(isIncoming);
-            }
-        }
-    }
-
-    /**
-     * Starts a call. Takes into account whether call is incoming or outgoing.
-     *
-     * @param isIncoming true if call is incoming, false for outgoing
-     */
-    private void initializeCall(boolean isIncoming) {
-        if (isIncoming) {
-            presenter.acceptCall();
-        } else {
-            Bundle args;
-            args = getArguments();
-            if (args != null) {
-                ConversationPath conversation = ConversationPath.fromBundle(args);
-                presenter.initOutGoing(conversation.getAccountId(),
-                        conversation.getConversationUri(),
-                        args.getString(Intent.EXTRA_PHONE_NUMBER),
-                        args.getBoolean(KEY_AUDIO_ONLY));
-            }
-        }
-    }
-
-    @Override
-    public void finish() {
-        Activity activity = getActivity();
-        if (activity != null) {
-            activity.finishAndRemoveTask();
-            if (mBackstackLost) {
-                startActivity(Intent.makeMainActivity(new ComponentName(activity, HomeActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            }
-        }
-    }
-
-    public void speakerClicked() {
-        presenter.speakerClick(binding.callSpeakerBtn.isChecked());
-    }
-
-    private void startScreenShare(MediaProjection mediaProjection) {
-        if (presenter.startScreenShare(mediaProjection)) {
-            if(choosePluginMode) {
-                binding.pluginPreviewSurface.setVisibility(View.GONE);
-            } else {
-                binding.previewSurface.setVisibility(View.GONE);
-            }
-        } else {
-            Toast.makeText(requireContext(), "Can't start screen sharing", Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    private void stopShareScreen() {
-        binding.previewSurface.setVisibility(View.VISIBLE);
-        presenter.stopScreenShare();
-    }
-
-    public void shareScreenClicked(boolean checked) {
-        if (!checked) {
-            stopShareScreen();
-        } else {
-            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_SHARE);
-        }
-    }
-
-    public void micClicked() {
-        presenter.muteMicrophoneToggled(binding.callMicBtn.isChecked());
-        binding.callMicBtn.setImageResource(binding.callMicBtn.isChecked()? R.drawable.baseline_mic_off_24 : R.drawable.baseline_mic_24);
-    }
-
-    public void hangUpClicked() {
-        presenter.hangupCall();
-    }
-
-    public void refuseClicked() {
-        presenter.refuseCall();
-    }
-
-    public void acceptClicked() {
-        prepareCall(true);
-    }
-
-    public void cameraFlip() {
-        presenter.switchVideoInputClick();
-    }
-
-    public void addParticipant() {
-        presenter.startAddParticipant();
-    }
-
-    @Override
-    public void startAddParticipant(String conferenceId) {
-        startActivityForResult(
-                new Intent(Intent.ACTION_PICK)
-                        .setClass(requireActivity(), ConversationSelectionActivity.class)
-                        .putExtra(KEY_CONF_ID, conferenceId),
-                CallFragment.REQUEST_CODE_ADD_PARTICIPANT);
-    }
-
-    @Override
-    public void toggleCallMediaHandler(String id, String callId, boolean toggle) {
-        JamiService.toggleCallMediaHandler(id, callId, toggle);
-    }
-
-    public Map<String, String> getCallMediaHandlerDetails(String id) {
-        return JamiService.getCallMediaHandlerDetails(id).toNative();
-    }
-
-    @Override
-    public void positiveMediaButtonClicked() {
-        presenter.positiveButtonClicked();
-    }
-
-    @Override
-    public void negativeMediaButtonClicked() {
-        presenter.negativeButtonClicked();
-    }
-
-    @Override
-    public void toggleMediaButtonClicked() {
-        presenter.toggleButtonClicked();
-    }
-
-    public boolean displayPluginsButton() {
-        return JamiService.getPluginsEnabled() && JamiService.getCallMediaHandlers().size() > 0;
-    }
-
-    @Override
-    public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        // Reset the padding of the RecyclerPicker on each
-        rp.setFirstLastElementsWidths(112, 112);
-        binding.recyclerPicker.setVisibility(View.GONE);
-        if (choosePluginMode) {
-            displayHangupButton(false);
-            binding.recyclerPicker.setVisibility(View.VISIBLE);
-            movePreview(true);
-            if (previousPluginPosition != -1) {
-                rp.scrollToPosition(previousPluginPosition);
-            }
-        } else {
-            movePreview(false);
-        }
-    }
-
-    public void toggleVideoPluginsCarousel(boolean toggle) {
-        if (choosePluginMode) {
-            if (toggle) {
-                binding.recyclerPicker.setVisibility(View.VISIBLE);
-                movePreview(true);
-            } else {
-                binding.recyclerPicker.setVisibility(View.INVISIBLE);
-                movePreview(false);
-            }
-        }
-    }
-
-    public void movePreview(boolean up) {
-        // Move the preview container (cardview) by a certain margin
-        if(up) {
-            animation.setIntValues(12, 128);
-        } else {
-            animation.setIntValues(128, 12);
-        }
-        animation.start();
-    }
-
-    /**
-     * Function that is called to show/hide the plugins recycler viewer and update UI
-     */
-    @SuppressLint("UseCompatLoadingForDrawables")
-    public void displayVideoPluginsCarousel() {
-        choosePluginMode = !choosePluginMode;
-
-        Context context = requireActivity();
-
-        // Create callMediaHandlers and videoPluginsItems in a lazy manner
-        if (pluginsModeFirst) {
-            // Init
-            callMediaHandlers = JamiService.getCallMediaHandlers();
-            List<Drawable> videoPluginsItems = new ArrayList<>(callMediaHandlers.size() + 1);
-
-            videoPluginsItems.add(context.getDrawable(R.drawable.baseline_cancel_24));
-            // Search for plugin call media handlers icons
-            // If a call media handler doesn't have an icon use a standard android icon
-            for (String callMediaHandler : callMediaHandlers) {
-                Map<String, String> details = getCallMediaHandlerDetails(callMediaHandler);
-                String drawablePath = details.get("iconPath");
-                if (drawablePath != null && drawablePath.endsWith("svg"))
-                    drawablePath = drawablePath.replace(".svg", ".png");
-                Drawable handlerIcon = Drawable.createFromPath(drawablePath);
-                if (handlerIcon == null) {
-                    handlerIcon = context.getDrawable(R.drawable.ic_jami);
-                }
-                videoPluginsItems.add(handlerIcon);
-            }
-
-            rp.updateData(videoPluginsItems);
-
-            pluginsModeFirst = false;
-        }
-
-        if (choosePluginMode) {
-            // hide hang up button and other call buttons
-            displayHangupButton(false);
-            // Display the plugins recyclerpicker
-            binding.recyclerPicker.setVisibility(View.VISIBLE);
-            movePreview(true);
-
-            // Start loading the first or previous plugin if one was active
-            if(callMediaHandlers.size() > 0) {
-                // If no previous plugin was active, take the first, else previous
-                int position;
-                if (previousPluginPosition < 1) {
-                    rp.scrollToPosition(1);
-                    position = 1;
-                    previousPluginPosition = 1;
-                } else {
-                    position = previousPluginPosition;
-                }
-                String callMediaId = callMediaHandlers.get(position-1);
-                presenter.startPlugin(callMediaId);
-            }
-
-        } else {
-            if (previousPluginPosition > 0) {
-                String callMediaId = callMediaHandlers.
-                        get(previousPluginPosition-1);
-
-                presenter.toggleCallMediaHandler(callMediaId, false);
-                rp.scrollToPosition(previousPluginPosition);
-            }
-            presenter.stopPlugin();
-            binding.recyclerPicker.setVisibility(View.GONE);
-            movePreview(false);
-            displayHangupButton(true);
-        }
-
-        //change preview image
-        displayVideoSurface(true,true);
-    }
-
-    /**
-     * Called whenever a plugin drawable in the recycler picker is clicked or scrolled to
-     */
-    @Override
-    public void onItemSelected(int position) {
-        Log.i(TAG, "selected position: " + position);
-        /* If there was a different plugin before, unload it
-         * If previousPluginPosition = -1 or 0, there was no plugin
-         */
-        if (previousPluginPosition > 0) {
-            String callMediaId = callMediaHandlers.get(previousPluginPosition-1);
-            presenter.toggleCallMediaHandler(callMediaId, false);
-        }
-
-        if (position > 0) {
-            previousPluginPosition = position;
-            String callMediaId = callMediaHandlers.get(position-1);
-            presenter.toggleCallMediaHandler(callMediaId, true);
-        }
-    }
-
-
-    /**
-     * Called whenever a plugin drawable in the recycler picker is clicked
-     */
-    @Override
-    public void onItemClicked(int position) {
-        Log.i(TAG, "selected position: " + position);
-        if (position == 0) {
-            /* If there was a different plugin before, unload it
-             * If previousPluginPosition = -1 or 0, there was no plugin
-             */
-            if (previousPluginPosition > 0) {
-                String callMediaId = callMediaHandlers.get(previousPluginPosition-1);
-                presenter.toggleCallMediaHandler(callMediaId, false);
-                rp.scrollToPosition(previousPluginPosition);
-            }
-
-            CallActivity callActivity = (CallActivity) getActivity();
-            if (callActivity != null) {
-                callActivity.showSystemUI();
-            }
-
-            toggleVideoPluginsCarousel(false);
-            displayVideoPluginsCarousel();
-        }
-    }
-
-    private Animation getBlinkingAnimation() {
-        Animation animation = new AlphaAnimation(1, 0);
-        animation.setDuration(400);
-        animation.setInterpolator(new LinearInterpolator());
-        animation.setRepeatCount(Animation.INFINITE);
-        animation.setRepeatMode(Animation.REVERSE);
-        return animation;
-    }
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
new file mode 100644
index 000000000..0ffe99af7
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
@@ -0,0 +1,1480 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *          Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.fragments
+
+import android.Manifest
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.PendingIntent
+import android.app.PictureInPictureParams
+import android.app.RemoteAction
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.media.projection.MediaProjection
+import android.media.projection.MediaProjectionManager
+import android.os.Build
+import android.os.Bundle
+import android.os.PowerManager
+import android.text.Editable
+import android.text.TextUtils
+import android.text.TextWatcher
+import android.util.Log
+import android.util.Rational
+import android.view.*
+import android.view.TextureView.SurfaceTextureListener
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.LinearInterpolator
+import android.view.inputmethod.InputMethodManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.view.menu.MenuBuilder
+import androidx.appcompat.view.menu.MenuPopupHelper
+import androidx.appcompat.widget.PopupMenu
+import androidx.databinding.DataBindingUtil
+import androidx.percentlayout.widget.PercentFrameLayout
+import com.rodolfonavalon.shaperipplelibrary.model.Circle
+import cx.ring.R
+import cx.ring.adapters.ConfParticipantAdapter
+import cx.ring.adapters.ConfParticipantAdapter.ConfParticipantSelected
+import cx.ring.client.*
+import cx.ring.databinding.FragCallBinding
+import cx.ring.databinding.ItemParticipantLabelBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.plugins.RecyclerPicker.RecyclerPicker
+import cx.ring.plugins.RecyclerPicker.RecyclerPickerLayoutManager.ItemSelectedListener
+import cx.ring.service.DRingService
+import cx.ring.utils.ActionHelper
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.DeviceUtils.isTablet
+import cx.ring.utils.DeviceUtils.isTv
+import cx.ring.utils.MediaButtonsHelper.MediaButtonsHelperCallback
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.call.CallPresenter
+import net.jami.call.CallView
+import net.jami.daemon.JamiService
+import net.jami.model.Call
+import net.jami.model.Call.CallStatus
+import net.jami.model.Conference.ParticipantInfo
+import net.jami.model.Contact
+import net.jami.model.Uri
+import net.jami.services.DeviceRuntimeService
+import net.jami.services.HardwareService
+import net.jami.services.HardwareService.AudioState
+import net.jami.services.NotificationService
+import java.util.*
+import javax.inject.Inject
+import kotlin.math.min
+
+@AndroidEntryPoint
+class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
+    MediaButtonsHelperCallback, ItemSelectedListener {
+    private var binding: FragCallBinding? = null
+    private var mOrientationListener: OrientationEventListener? = null
+    private var dialPadBtn: MenuItem? = null
+    private var pluginsMenuBtn: MenuItem? = null
+    private var restartVideo = false
+    private var restartPreview = false
+    private var mScreenWakeLock: PowerManager.WakeLock? = null
+    private var mCurrentOrientation = 0
+    private var mVideoWidth = -1
+    private var mVideoHeight = -1
+    private var mPreviewWidth = 720
+    private var mPreviewHeight = 1280
+    private var mPreviewSurfaceWidth = 0
+    private var mPreviewSurfaceHeight = 0
+    private lateinit var mProjectionManager: MediaProjectionManager
+    private var mBackstackLost = false
+    private var confAdapter: ConfParticipantAdapter? = null
+    private var mConferenceMode = false
+    var isChoosePluginMode = false
+        private set
+    private var pluginsModeFirst = true
+    private var callMediaHandlers: List<String>? = null
+    private var previousPluginPosition = -1
+    private var rp: RecyclerPicker? = null
+    private val animation = ValueAnimator().apply { duration = 150 }
+    private var previewDrag: PointF? = null
+    private val previewSnapAnimation = ValueAnimator().apply {
+        duration = 250
+        setFloatValues(0f, 1f)
+        interpolator = DecelerateInterpolator()
+        addUpdateListener { a -> configurePreview(mPreviewSurfaceWidth, a.animatedFraction) }
+    }
+    private val previewMargins = IntArray(4)
+    private var previewHiddenState = 0f
+
+    private enum class PreviewPosition { LEFT, RIGHT }
+    private var previewPosition = PreviewPosition.RIGHT
+
+    @Inject
+    lateinit var mDeviceRuntimeService: DeviceRuntimeService
+
+    private val mCompositeDisposable = CompositeDisposable()
+    override fun initPresenter(presenter: CallPresenter) {
+        val args = requireArguments()
+        args.getString(KEY_ACTION)?.let { action ->
+            if (action == ACTION_PLACE_CALL)
+                prepareCall(false)
+            else if (action == ACTION_GET_CALL || action == CallActivity.ACTION_CALL_ACCEPT)
+                presenter.initIncomingCall(args.getString(KEY_CONF_ID)!!, action == ACTION_GET_CALL)
+        }
+    }
+
+    override fun onUserLeave() {
+        presenter.requestPipMode()
+    }
+
+    override fun enterPipMode(callId: String) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            return
+        }
+        val context = requireContext()
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val paramBuilder = PictureInPictureParams.Builder()
+            if (binding!!.videoSurface.visibility == View.VISIBLE) {
+                val l = IntArray(2)
+                binding!!.videoSurface.getLocationInWindow(l)
+                val x = l[0]
+                val y = l[1]
+                val w = binding!!.videoSurface.width
+                val h = binding!!.videoSurface.height
+                val videoBounds = Rect(x, y, x + w, y + h)
+                paramBuilder.setAspectRatio(Rational(w, h))
+                paramBuilder.setSourceRectHint(videoBounds)
+            } else {
+                return
+            }
+            val actions = ArrayList<RemoteAction>(1)
+            actions.add(RemoteAction(Icon.createWithResource(context, R.drawable.baseline_call_end_24),
+                    getString(R.string.action_call_hangup),
+                    getString(R.string.action_call_hangup),
+                    PendingIntent.getService(context, Random().nextInt(),
+                        Intent(DRingService.ACTION_CALL_END)
+                            .setClass(context, JamiService::class.java)
+                            .putExtra(NotificationService.KEY_CALL_ID, callId), PendingIntent.FLAG_ONE_SHOT)))
+            paramBuilder.setActions(actions)
+            try {
+                requireActivity().enterPictureInPictureMode(paramBuilder.build())
+            } catch (e: Exception) {
+                Log.w(TAG, "Can't enter  PIP mode", e)
+            }
+        } else if (isTv(context)) {
+            requireActivity().enterPictureInPictureMode()
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (restartVideo && restartPreview) {
+            displayVideoSurface(true, !presenter.isPipMode)
+            restartVideo = false
+            restartPreview = false
+        } else if (restartVideo) {
+            displayVideoSurface(displayVideoSurface = true, displayPreviewContainer = false)
+            restartVideo = false
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        previewSnapAnimation.cancel()
+        binding?.let { binding ->
+            if (binding.videoSurface.visibility == View.VISIBLE) {
+                restartVideo = true
+            }
+            if (!isChoosePluginMode) {
+                if (binding.previewContainer.visibility == View.VISIBLE) {
+                    restartPreview = true
+                }
+            } else {
+                if (binding.pluginPreviewContainer.visibility == View.VISIBLE) {
+                    restartPreview = true
+                    presenter.stopPlugin()
+                }
+            }
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        return (DataBindingUtil.inflate(inflater, R.layout.frag_call, container, false) as FragCallBinding).also { b ->
+            b.presenter = this
+            binding = b
+            rp = RecyclerPicker(b.recyclerPicker, R.layout.item_picker, LinearLayout.HORIZONTAL, this)
+                .apply { setFirstLastElementsWidths(112, 112) }
+        }.root
+    }
+
+    private val listener: SurfaceTextureListener = object : SurfaceTextureListener {
+        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+            mPreviewSurfaceWidth = width
+            mPreviewSurfaceHeight = height
+            presenter.previewVideoSurfaceCreated(binding!!.previewSurface)
+        }
+
+        override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+            mPreviewSurfaceWidth = width
+            mPreviewSurfaceHeight = height
+            configurePreview(width, 1f)
+        }
+
+        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+            presenter.previewVideoSurfaceDestroyed()
+            return true
+        }
+
+        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
+    }
+
+    /**
+     * @param hiddenState 0.f if fully shown, 1.f if fully hidden.
+     */
+    private fun setPreviewDragHiddenState(hiddenState: Float) {
+        binding?.let { binding ->
+            binding.previewSurface.alpha = 1f - 3 * hiddenState / 4
+            binding.pluginPreviewSurface.alpha = 1f - 3 * hiddenState / 4
+            binding.previewHandle.alpha = hiddenState
+            binding.pluginPreviewHandle.alpha = hiddenState
+        }
+    }
+
+    private val previewTouchListener = object: View.OnTouchListener {
+        @SuppressLint("ClickableViewAccessibility")
+        override fun onTouch(v: View, event: MotionEvent): Boolean {
+            val action = event.actionMasked
+            val parent = v.parent as RelativeLayout
+            val params = v.layoutParams as RelativeLayout.LayoutParams
+            when (action) {
+                MotionEvent.ACTION_DOWN -> {
+                    previewSnapAnimation.cancel()
+                    previewDrag = PointF(event.x, event.y)
+                    v.elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation_dragged)
+                    params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                    params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                    params.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+                    params.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                    params.setMargins(
+                        v.x.toInt(),
+                        v.y.toInt(),
+                        parent.width - (v.x.toInt() + v.width),
+                        parent.height - (v.y.toInt() + v.height)
+                    )
+                    v.layoutParams = params
+                    return true
+                }
+                MotionEvent.ACTION_MOVE -> {
+                    if (previewDrag != null) {
+                        val currentXPosition = params.leftMargin + (event.x - previewDrag!!.x).toInt()
+                        val currentYPosition = params.topMargin + (event.y - previewDrag!!.y).toInt()
+                        params.setMargins(
+                            currentXPosition,
+                            currentYPosition,
+                            -(currentXPosition + v.width - event.x.toInt()),
+                            -(currentYPosition + v.height - event.y.toInt())
+                        )
+                        v.layoutParams = params
+                        val outPosition = binding!!.previewContainer.width * 0.85f
+                        var drapOut = 0f
+                        if (currentXPosition < 0) {
+                            drapOut = min(1f, -currentXPosition / outPosition)
+                        } else if (currentXPosition + v.width > parent.width) {
+                            drapOut = min(1f, (currentXPosition + v.width - parent.width) / outPosition)
+                        }
+                        setPreviewDragHiddenState(drapOut)
+                        return true
+                    }
+                    return false
+                }
+                MotionEvent.ACTION_UP -> {
+                    if (previewDrag != null) {
+                        val currentXPosition = params.leftMargin + (event.x - previewDrag!!.x).toInt()
+                        previewSnapAnimation.cancel()
+                        previewDrag = null
+                        v.elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation)
+                        var ml = 0
+                        var mr = 0
+                        var mt = 0
+                        var mb = 0
+                        val hp = binding!!.previewHandle.layoutParams as FrameLayout.LayoutParams
+                        if (params.leftMargin + v.width / 2 > parent.width / 2) {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                            mr = (parent.width - v.width - v.x).toInt()
+                            previewPosition = PreviewPosition.RIGHT
+                            hp.gravity = Gravity.CENTER_VERTICAL or Gravity.LEFT
+                        } else {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                            ml = v.x.toInt()
+                            previewPosition = PreviewPosition.LEFT
+                            hp.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT
+                        }
+                        binding!!.previewHandle.layoutParams = hp
+                        if (params.topMargin + v.height / 2 > parent.height / 2) {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_TOP)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                            mb = (parent.height - v.height - v.y).toInt()
+                        } else {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+                            mt = v.y.toInt()
+                        }
+                        previewMargins[0] = ml
+                        previewMargins[1] = mt
+                        previewMargins[2] = mr
+                        previewMargins[3] = mb
+                        params.setMargins(ml, mt, mr, mb)
+                        v.layoutParams = params
+                        val outPosition = binding!!.previewContainer.width * 0.85f
+                        previewHiddenState = when {
+                            currentXPosition < 0 ->
+                                min(1f, -currentXPosition / outPosition)
+                            currentXPosition + v.width > parent.width ->
+                                min(1f, (currentXPosition + v.width - parent.width) / outPosition)
+                            else -> 0f
+                        }
+                        setPreviewDragHiddenState(previewHiddenState)
+                        previewSnapAnimation.start()
+                        return true
+                    }
+                    return false
+                }
+                else -> return false
+            }
+        }
+    }
+
+    @SuppressLint("ClickableViewAccessibility", "RtlHardcoded", "WakelockTimeout")
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        setHasOptionsMenu(true)
+        super.onViewCreated(view, savedInstanceState)
+        mCurrentOrientation = resources.configuration.orientation
+        val dpRatio = requireActivity().resources.displayMetrics.density
+        animation.addUpdateListener { valueAnimator ->
+            binding?.let { binding ->
+                val upBy = valueAnimator.animatedValue as Int
+                val layoutParams = binding.previewContainer.layoutParams as RelativeLayout.LayoutParams
+                layoutParams.setMargins(0, 0, 0, (upBy * dpRatio).toInt())
+                binding.previewContainer.layoutParams = layoutParams
+            }
+        }
+        val activity = activity
+        if (activity != null) {
+            activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+            if (activity is AppCompatActivity) {
+                val ab = activity.supportActionBar
+                if (ab != null) {
+                    ab.setHomeAsUpIndicator(R.drawable.baseline_chat_24)
+                    ab.setDisplayHomeAsUpEnabled(true)
+                }
+            }
+        }
+        mProjectionManager = requireContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
+        val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
+        mScreenWakeLock = powerManager.newWakeLock(
+            PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
+            "ring:callLock"
+        ).apply {
+            setReferenceCounted(false)
+            if (!isHeld)
+                acquire()
+        }
+        binding?.let { binding ->
+            binding.videoSurface.holder.setFormat(PixelFormat.RGBA_8888)
+            binding.videoSurface.holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(holder: SurfaceHolder) {
+                    presenter.videoSurfaceCreated(holder)
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
+
+                override fun surfaceDestroyed(holder: SurfaceHolder) {
+                    presenter.videoSurfaceDestroyed()
+                }
+            })
+            binding.pluginPreviewSurface.holder.setFormat(PixelFormat.RGBA_8888)
+            binding.pluginPreviewSurface.holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(holder: SurfaceHolder) {
+                    presenter.pluginSurfaceCreated(holder)
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
+
+                override fun surfaceDestroyed(holder: SurfaceHolder) {
+                    presenter.pluginSurfaceDestroyed()
+                }
+            })
+            view.setOnSystemUiVisibilityChangeListener { visibility: Int ->
+                val ui = visibility and (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
+                presenter.uiVisibilityChanged(ui)
+            }
+            val ui = view.systemUiVisibility and (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
+            presenter.uiVisibilityChanged(ui)
+            view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> resetVideoSize(mVideoWidth, mVideoHeight) }
+            val windowManager = view.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+            mOrientationListener = object : OrientationEventListener(context) {
+                override fun onOrientationChanged(orientation: Int) {
+                    val rot = windowManager.defaultDisplay.rotation
+                    if (mCurrentOrientation != rot) {
+                        mCurrentOrientation = rot
+                        presenter.configurationChanged(rot)
+                    }
+                }
+            }.apply { if (canDetectOrientation()) enable() }
+            binding.shapeRipple.rippleShape = Circle()
+            binding.callSpeakerBtn.isChecked = presenter.isSpeakerphoneOn
+            binding.callMicBtn.isChecked = presenter.isMicrophoneMuted
+            binding.pluginPreviewSurface.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+                configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight)
+            }
+            binding.previewSurface.surfaceTextureListener = listener
+            binding.previewSurface.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+                configureTransform(mPreviewSurfaceWidth, mPreviewSurfaceHeight)
+            }
+            binding.previewContainer.setOnTouchListener(previewTouchListener)
+            binding.pluginPreviewContainer.setOnTouchListener { v: View, event: MotionEvent ->
+                val action = event.actionMasked
+                val parent = v.parent as RelativeLayout
+                val params = v.layoutParams as RelativeLayout.LayoutParams
+                if (action == MotionEvent.ACTION_DOWN) {
+                    previewSnapAnimation.cancel()
+                    previewDrag = PointF(event.x, event.y)
+                    v.elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation_dragged)
+                    params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                    params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                    params.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+                    params.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                    params.setMargins(
+                        v.x.toInt(),
+                        v.y.toInt(),
+                        parent.width - (v.x.toInt() + v.width),
+                        parent.height - (v.y
+                            .toInt() + v.height)
+                    )
+                    v.layoutParams = params
+                    return@setOnTouchListener true
+                } else if (action == MotionEvent.ACTION_MOVE) {
+                    if (previewDrag != null) {
+                        val currentXPosition = params.leftMargin + (event.x - previewDrag!!.x).toInt()
+                        val currentYPosition = params.topMargin + (event.y - previewDrag!!.y).toInt()
+                        params.setMargins(
+                            currentXPosition,
+                            currentYPosition,
+                            -(currentXPosition + v.width - event.x.toInt()),
+                            -(currentYPosition + v.height - event.y.toInt())
+                        )
+                        v.layoutParams = params
+                        val outPosition = binding.pluginPreviewContainer.width * 0.85f
+                        var drapOut = 0f
+                        if (currentXPosition < 0) {
+                            drapOut = min(1f, -currentXPosition / outPosition)
+                        } else if (currentXPosition + v.width > parent.width) {
+                            drapOut = min(1f, (currentXPosition + v.width - parent.width) / outPosition)
+                        }
+                        setPreviewDragHiddenState(drapOut)
+                        return@setOnTouchListener true
+                    }
+                    return@setOnTouchListener false
+                } else if (action == MotionEvent.ACTION_UP) {
+                    if (previewDrag != null) {
+                        val currentXPosition = params.leftMargin + (event.x - previewDrag!!.x).toInt()
+                        previewSnapAnimation.cancel()
+                        previewDrag = null
+                        v.elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation)
+                        var ml = 0; var mr = 0; var mt = 0; var mb = 0
+                        val hp = binding.pluginPreviewHandle.layoutParams as FrameLayout.LayoutParams
+                        if (params.leftMargin + v.width / 2 > parent.width / 2) {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                            mr = (parent.width - v.width - v.x).toInt()
+                            previewPosition = PreviewPosition.RIGHT
+                            hp.gravity = Gravity.CENTER_VERTICAL or Gravity.LEFT
+                        } else {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_LEFT)
+                            ml = v.x.toInt()
+                            previewPosition = PreviewPosition.LEFT
+                            hp.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT
+                        }
+                        binding.pluginPreviewHandle.layoutParams = hp
+                        if (params.topMargin + v.height / 2 > parent.height / 2) {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_TOP)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                            mb = (parent.height - v.height - v.y).toInt()
+                        } else {
+                            params.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+                            params.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+                            mt = v.y.toInt()
+                        }
+                        previewMargins[0] = ml
+                        previewMargins[1] = mt
+                        previewMargins[2] = mr
+                        previewMargins[3] = mb
+                        params.setMargins(ml, mt, mr, mb)
+                        v.layoutParams = params
+                        val outPosition = binding.pluginPreviewContainer.width * 0.85f
+                        previewHiddenState = when {
+                            currentXPosition < 0 -> min(1f, -currentXPosition / outPosition)
+                            currentXPosition + v.width > parent.width -> min(1f, (currentXPosition + v.width - parent.width) / outPosition)
+                            else -> 0f
+                        }
+                        setPreviewDragHiddenState(previewHiddenState)
+                        previewSnapAnimation.start()
+                        return@setOnTouchListener true
+                    }
+                    return@setOnTouchListener false
+                } else {
+                    return@setOnTouchListener false
+                }
+            }
+            binding.dialpadEditText.addTextChangedListener(object : TextWatcher {
+                override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+                    presenter.sendDtmf(s.subSequence(start, start + count))
+                }
+
+                override fun afterTextChanged(s: Editable) {}
+            })
+        }
+
+    }
+
+    private fun configurePreview(width: Int, animatedFraction: Float) {
+        val context = context
+        if (context == null || binding == null) return
+        val margin = context.resources.getDimension(R.dimen.call_preview_margin)
+        val params = binding!!.previewContainer.layoutParams as RelativeLayout.LayoutParams
+        val r = 1f - animatedFraction
+        var hideMargin = 0f
+        var targetHiddenState = 0f
+        if (previewHiddenState > 0f) {
+            targetHiddenState = 1f
+            val v = width * 0.85f * animatedFraction
+            hideMargin = if (previewPosition == PreviewPosition.RIGHT) v else -v
+        }
+        setPreviewDragHiddenState(previewHiddenState * r + targetHiddenState * animatedFraction)
+        val f = margin * animatedFraction
+        params.setMargins(
+            (previewMargins[0] * r + f + hideMargin).toInt(),
+            (previewMargins[1] * r + f).toInt(),
+            (previewMargins[2] * r + f - hideMargin).toInt(),
+            (previewMargins[3] * r + f).toInt()
+        )
+        binding!!.previewContainer.layoutParams = params
+        binding!!.pluginPreviewContainer.layoutParams = params
+    }
+
+    /**
+     * Releases current wakelock and acquires a new proximity wakelock if current call is audio only.
+     *
+     * @param isAudioOnly true if it is an audio call
+     */
+    @SuppressLint("WakelockTimeout")
+    override fun handleCallWakelock(isAudioOnly: Boolean) {
+        if (isAudioOnly) {
+            mScreenWakeLock?.apply {
+                if (isHeld) release()
+            }
+            val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
+            mScreenWakeLock = powerManager.newWakeLock(
+                PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE,
+                "ring:callLock"
+            ).apply {
+                setReferenceCounted(false)
+                if (!isHeld)
+                    acquire()
+            }
+        }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        if (mOrientationListener != null) {
+            mOrientationListener!!.disable()
+            mOrientationListener = null
+        }
+        mCompositeDisposable.clear()
+        if (mScreenWakeLock != null && mScreenWakeLock!!.isHeld) {
+            mScreenWakeLock!!.release()
+        }
+        binding = null
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mCompositeDisposable.dispose()
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING) return
+        var i = 0
+        val n = permissions.size
+        while (i < n) {
+            val audioGranted = mDeviceRuntimeService.hasAudioPermission()
+            val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+            when (permissions[i]) {
+                Manifest.permission.CAMERA -> {
+                    presenter.cameraPermissionChanged(granted)
+                    if (audioGranted) {
+                        initializeCall(requestCode == REQUEST_PERMISSION_INCOMING)
+                    }
+                }
+                Manifest.permission.RECORD_AUDIO -> {
+                    presenter.audioPermissionChanged(granted)
+                    initializeCall(requestCode == REQUEST_PERMISSION_INCOMING)
+                }
+            }
+            i++
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == REQUEST_CODE_ADD_PARTICIPANT) {
+            if (resultCode == Activity.RESULT_OK && data != null) {
+                val path = ConversationPath.fromUri(data.data)
+                if (path != null) {
+                    presenter.addConferenceParticipant(path.accountId, path.conversationUri)
+                }
+            }
+        } else if (requestCode == REQUEST_CODE_SCREEN_SHARE) {
+            if (resultCode == Activity.RESULT_OK && data != null) {
+                try {
+                    startScreenShare(mProjectionManager.getMediaProjection(resultCode, data))
+                } catch (e: Exception) {
+                    Log.w(TAG, "Error starting screen sharing", e)
+                }
+            } else {
+                binding!!.callScreenshareBtn.isChecked = false
+            }
+        }
+    }
+
+    override fun onCreateOptionsMenu(m: Menu, inf: MenuInflater) {
+        super.onCreateOptionsMenu(m, inf)
+        inf.inflate(R.menu.ac_call, m)
+        dialPadBtn = m.findItem(R.id.menuitem_dialpad)
+        pluginsMenuBtn = m.findItem(R.id.menuitem_video_plugins)
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        super.onPrepareOptionsMenu(menu)
+        presenter.prepareOptionMenu()
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        super.onOptionsItemSelected(item)
+        val itemId = item.itemId
+        if (itemId == android.R.id.home) {
+            presenter.chatClick()
+        } else if (itemId == R.id.menuitem_dialpad) {
+            presenter.dialpadClick()
+        } else if (itemId == R.id.menuitem_video_plugins) {
+            displayVideoPluginsCarousel()
+        }
+        return true
+    }
+
+    override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+        val activity = activity as AppCompatActivity?
+        val actionBar = activity?.supportActionBar
+        if (actionBar != null) {
+            if (isInPictureInPictureMode) {
+                actionBar.hide()
+            } else {
+                mBackstackLost = true
+                actionBar.show()
+            }
+        }
+        presenter.pipModeChanged(isInPictureInPictureMode)
+    }
+
+    override fun displayContactBubble(display: Boolean) {
+        if (binding != null) binding!!.contactBubbleLayout.handler.post {
+            if (binding != null) binding!!.contactBubbleLayout.visibility =
+                if (display) View.VISIBLE else View.GONE
+        }
+    }
+
+    override fun displayVideoSurface(
+        displayVideoSurface: Boolean,
+        displayPreviewContainer: Boolean
+    ) {
+        binding!!.videoSurface.visibility =
+            if (displayVideoSurface) View.VISIBLE else View.GONE
+        if (isChoosePluginMode) {
+            binding!!.pluginPreviewSurface.visibility =
+                if (displayPreviewContainer) View.VISIBLE else View.GONE
+            binding!!.pluginPreviewContainer.visibility =
+                if (displayPreviewContainer) View.VISIBLE else View.GONE
+            binding!!.previewContainer.visibility = View.GONE
+        } else {
+            binding!!.pluginPreviewSurface.visibility = View.GONE
+            binding!!.pluginPreviewContainer.visibility = View.GONE
+            binding!!.previewContainer.visibility =
+                if (displayPreviewContainer) View.VISIBLE else View.GONE
+        }
+        updateMenu()
+    }
+
+    override fun displayPreviewSurface(display: Boolean) {
+        if (display) {
+            binding!!.videoSurface.setZOrderOnTop(false)
+            binding!!.videoSurface.setZOrderMediaOverlay(false)
+        } else {
+            binding!!.videoSurface.setZOrderMediaOverlay(true)
+            binding!!.videoSurface.setZOrderOnTop(true)
+        }
+    }
+
+    override fun displayHangupButton(display: Boolean) {
+        var display = display
+        Log.w(TAG, "displayHangupButton $display")
+        display = display and !isChoosePluginMode
+        binding!!.callControlGroup.visibility = if (display) View.VISIBLE else View.GONE
+        binding!!.callHangupBtn.visibility =
+            if (display) View.VISIBLE else View.GONE
+        binding!!.confControlGroup.visibility =
+            if (mConferenceMode && display) View.VISIBLE else View.GONE
+    }
+
+    override fun displayDialPadKeyboard() {
+        binding!!.dialpadEditText.requestFocus()
+        val imm =
+            binding!!.dialpadEditText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
+    }
+
+    override fun switchCameraIcon(isFront: Boolean) {
+        binding!!.callCameraFlipBtn.setImageResource(if (isFront) R.drawable.baseline_camera_front_24 else R.drawable.baseline_camera_rear_24)
+    }
+
+    override fun updateAudioState(state: AudioState) {
+        binding!!.callSpeakerBtn.isChecked =
+            state.outputType == HardwareService.AudioOutput.SPEAKERS
+    }
+
+    override fun updateMenu() {
+        requireActivity().invalidateOptionsMenu()
+    }
+
+    override fun updateTime(duration: Long) {
+        binding?.let { binding ->
+            if (duration <= 0) binding.callStatusTxt.text =
+                null else binding.callStatusTxt.text = String.format(
+                Locale.getDefault(),
+                "%d:%02d:%02d",
+                duration / 3600,
+                duration % 3600 / 60,
+                duration % 60
+            )
+        }
+    }
+
+    @SuppressLint("RestrictedApi")
+    override fun updateContactBubble(contacts: List<Call>) {
+        Log.w(TAG, "updateContactBubble " + contacts.size)
+        val username = if (contacts.size > 1)
+            "Conference with " + contacts.size + " people"
+        else contacts[0].contact!!.displayName
+        val displayName = if (contacts.size > 1) null else contacts[0].contact!!.displayName
+        val hasProfileName = displayName != null && !displayName.contentEquals(username)
+        val activity = activity as AppCompatActivity?
+        if (activity != null) {
+            val ab = activity.supportActionBar
+            if (ab != null) {
+                if (hasProfileName) {
+                    ab.title = displayName
+                    ab.subtitle = username
+                } else {
+                    ab.title = username
+                    ab.subtitle = null
+                }
+                ab.setDisplayShowTitleEnabled(true)
+            }
+        }
+        if (hasProfileName) {
+            binding!!.contactBubbleNumTxt.visibility = View.VISIBLE
+            binding!!.contactBubbleTxt.text = displayName
+            binding!!.contactBubbleNumTxt.text = username
+        } else {
+            binding!!.contactBubbleNumTxt.visibility = View.GONE
+            binding!!.contactBubbleTxt.text = username
+        }
+        binding!!.contactBubble.setImageDrawable(
+            AvatarDrawable.Builder()
+                .withContact(contacts[0].contact)
+                .withCircleCrop(true)
+                .withPresence(false)
+                .build(requireActivity())
+        )
+    }
+
+    @SuppressLint("RestrictedApi")
+    override fun updateConfInfo(participantInfo: List<ParticipantInfo>) {
+        Log.w(TAG, "updateConfInfo $participantInfo")
+        mConferenceMode = participantInfo.size > 1
+        binding!!.participantLabelContainer.removeAllViews()
+        if (participantInfo.isNotEmpty()) {
+            val inflater = LayoutInflater.from(binding!!.participantLabelContainer.context)
+            for (i in participantInfo) {
+                val displayName = i.contact.displayName
+                if (!TextUtils.isEmpty(displayName)) {
+                    val label = ItemParticipantLabelBinding.inflate(inflater)
+                    val params = PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+                    params.percentLayoutInfo.leftMarginPercent = i.x / mVideoWidth.toFloat()
+                    params.percentLayoutInfo.topMarginPercent = i.y / mVideoHeight.toFloat()
+                    params.percentLayoutInfo.rightMarginPercent =
+                        1f - (i.x + i.w) / mVideoWidth.toFloat()
+                    //params.getPercentLayoutInfo().rightMarginPercent = (i.x + i.w) / (float) mVideoWidth;
+                    label.participantName.text = displayName
+                    label.moderator.visibility = if (i.isModerator) View.VISIBLE else View.GONE
+                    label.mute.visibility = if (i.audioMuted) View.VISIBLE else View.GONE
+                    binding!!.participantLabelContainer.addView(label.root, params)
+                }
+            }
+        }
+        binding!!.participantLabelContainer.visibility =
+            if (participantInfo.isEmpty()) View.GONE else View.VISIBLE
+        if (participantInfo.isEmpty() || participantInfo.size < 2) {
+            binding!!.confControlGroup.visibility = View.GONE
+        } else {
+            binding!!.confControlGroup.visibility = View.VISIBLE
+            if (confAdapter == null) {
+                confAdapter =
+                    ConfParticipantAdapter(object : ConfParticipantSelected {
+                        override fun onParticipantSelected(view: View, contact: ParticipantInfo) {
+                            val maximized = presenter.isMaximized(contact)
+                            val popup = PopupMenu(view.context, view)
+                            popup.inflate(R.menu.conference_participant_actions)
+                            popup.setOnMenuItemClickListener { item ->
+                                when (item.itemId) {
+                                    R.id.conv_contact_details -> presenter.openParticipantContact(contact)
+                                    R.id.conv_contact_hangup -> presenter.hangupParticipant(contact)
+                                    R.id.conv_mute -> presenter.muteParticipant(contact, !contact.audioMuted)
+                                    R.id.conv_contact_maximize -> presenter.maximizeParticipant(contact)
+                                    else -> return@setOnMenuItemClickListener false
+                                }
+                                true
+                            }
+                            val menu = popup.menu as MenuBuilder
+                            val maxItem = menu.findItem(R.id.conv_contact_maximize)
+                            val muteItem = menu.findItem(R.id.conv_mute)
+                            if (maximized) {
+                                maxItem.setTitle(R.string.action_call_minimize)
+                                maxItem.setIcon(R.drawable.baseline_close_fullscreen_24)
+                            } else {
+                                maxItem.setTitle(R.string.action_call_maximize)
+                                maxItem.setIcon(R.drawable.baseline_open_in_full_24)
+                            }
+                            if (!contact.audioMuted) {
+                                muteItem.setTitle(R.string.action_call_mute)
+                                muteItem.setIcon(R.drawable.baseline_mic_off_24)
+                            } else {
+                                muteItem.setTitle(R.string.action_call_unmute)
+                                muteItem.setIcon(R.drawable.baseline_mic_24)
+                            }
+                            val menuHelper = MenuPopupHelper(view.context, menu, view)
+                            menuHelper.gravity = Gravity.END
+                            menuHelper.setForceShowIcon(true)
+                            menuHelper.show()
+                        }
+                    })
+            }
+            confAdapter!!.updateFromCalls(participantInfo)
+            if (binding!!.confControlGroup.adapter == null) binding!!.confControlGroup.adapter =
+                confAdapter
+        }
+    }
+
+    override fun updateParticipantRecording(contacts: Set<Contact>) {
+        binding?.let { binding ->
+            if (contacts.isEmpty()) {
+                binding.recordLayout.visibility = View.INVISIBLE
+                binding.recordIndicator.clearAnimation()
+                return
+            }
+            val names = StringBuilder()
+            val contact = contacts.iterator()
+            for (i in contacts.indices) {
+                names.append(" ").append(contact.next().displayName)
+                if (i != contacts.size - 1) {
+                    names.append(",")
+                }
+            }
+            binding.recordLayout.visibility = View.VISIBLE
+            binding.recordIndicator.animation = blinkingAnimation
+            binding.recordName.text = getString(R.string.remote_recording, names)
+        }
+    }
+
+    override fun updateCallStatus(callStatus: CallStatus) {
+        binding!!.callStatusTxt.setText(callStateToHumanState(callStatus))
+    }
+
+    override fun initMenu(
+        isSpeakerOn: Boolean, displayFlip: Boolean, canDial: Boolean,
+        showPluginBtn: Boolean, onGoingCall: Boolean
+    ) {
+        if (binding != null) {
+            binding!!.callCameraFlipBtn.visibility = if (displayFlip) View.VISIBLE else View.GONE
+        }
+        if (dialPadBtn != null) {
+            dialPadBtn!!.isVisible = canDial
+        }
+        if (pluginsMenuBtn != null) {
+            pluginsMenuBtn!!.isVisible = showPluginBtn
+        }
+        updateMenu()
+    }
+
+    override fun initNormalStateDisplay(audioOnly: Boolean, isMuted: Boolean) {
+        Log.w(TAG, "initNormalStateDisplay")
+        binding?.apply {
+            shapeRipple.stopRipple()
+            callAcceptBtn.visibility = View.GONE
+            callRefuseBtn.visibility = View.GONE
+            callControlGroup.visibility = View.VISIBLE
+            callHangupBtn.visibility = View.VISIBLE
+            contactBubbleLayout.visibility = if (audioOnly) View.VISIBLE else View.GONE
+            callMicBtn.isChecked = isMuted
+        }
+        requireActivity().invalidateOptionsMenu()
+        val callActivity = activity as CallActivity?
+        callActivity?.showSystemUI()
+    }
+
+    override fun initIncomingCallDisplay() {
+        Log.w(TAG, "initIncomingCallDisplay")
+        binding?.apply {
+            callAcceptBtn.visibility = View.VISIBLE
+            callRefuseBtn.visibility = View.VISIBLE
+            callControlGroup.visibility = View.GONE
+            callHangupBtn.visibility = View.GONE
+            contactBubbleLayout.visibility = View.VISIBLE
+        }
+        requireActivity().invalidateOptionsMenu()
+    }
+
+    override fun initOutGoingCallDisplay() {
+        Log.w(TAG, "initOutGoingCallDisplay")
+        binding?.apply {
+            callAcceptBtn.visibility = View.GONE
+            callRefuseBtn.visibility = View.VISIBLE
+            callControlGroup.visibility = View.GONE
+            callHangupBtn.visibility = View.GONE
+            contactBubbleLayout.visibility = View.VISIBLE
+        }
+        requireActivity().invalidateOptionsMenu()
+    }
+
+    override fun resetPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) {
+        if (previewWidth == -1 && previewHeight == -1) return
+        mPreviewWidth = previewWidth
+        mPreviewHeight = previewHeight
+        val flip = rot % 180 != 0
+        binding?.previewSurface?.setAspectRatio(
+            if (flip) mPreviewHeight else mPreviewWidth,
+            if (flip) mPreviewWidth else mPreviewHeight
+        )
+    }
+
+    override fun resetPluginPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) {
+        if (previewWidth == -1 && previewHeight == -1) return
+        mPreviewWidth = previewWidth
+        mPreviewHeight = previewHeight
+        val flip = rot % 180 != 0
+        binding?.pluginPreviewSurface?.setAspectRatio(
+            if (flip) mPreviewHeight else mPreviewWidth,
+            if (flip) mPreviewWidth else mPreviewHeight
+        )
+    }
+
+    override fun resetVideoSize(videoWidth: Int, videoHeight: Int) {
+        val rootView = view as ViewGroup? ?: return
+        val videoRatio = videoWidth / videoHeight.toDouble()
+        val screenRatio = rootView.width / rootView.height.toDouble()
+        val params = binding!!.videoSurface.layoutParams as RelativeLayout.LayoutParams
+        val oldW = params.width
+        val oldH = params.height
+        if (videoRatio >= screenRatio) {
+            params.width = RelativeLayout.LayoutParams.MATCH_PARENT
+            params.height =
+                (videoHeight * rootView.width.toDouble() / videoWidth.toDouble()).toInt()
+        } else {
+            params.height = RelativeLayout.LayoutParams.MATCH_PARENT
+            params.width =
+                (videoWidth * rootView.height.toDouble() / videoHeight.toDouble()).toInt()
+        }
+        if (oldW != params.width || oldH != params.height) {
+            binding!!.videoSurface.layoutParams = params
+        }
+        mVideoWidth = videoWidth
+        mVideoHeight = videoHeight
+    }
+
+    private fun configureTransform(viewWidth: Int, viewHeight: Int) {
+        val activity: Activity? = activity
+        if (null == binding || null == activity) {
+            return
+        }
+        val rotation = activity.windowManager.defaultDisplay.rotation
+        val rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation
+        // Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot=" + rot + " mPreviewWidth=" + mPreviewWidth + " mPreviewHeight=" + mPreviewHeight);
+        val matrix = Matrix()
+        val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
+        val centerX = viewRect.centerX()
+        val centerY = viewRect.centerY()
+        if (rot) {
+            val bufferRect = RectF(0f, 0f, mPreviewHeight.toFloat(), mPreviewWidth.toFloat())
+            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
+            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
+            val scale = Math.max(
+                viewHeight.toFloat() / mPreviewHeight,
+                viewWidth.toFloat() / mPreviewWidth
+            )
+            matrix.postScale(scale, scale, centerX, centerY)
+            matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
+        } else if (Surface.ROTATION_180 == rotation) {
+            matrix.postRotate(180f, centerX, centerY)
+        }
+        if (!isChoosePluginMode) {
+//            binding.pluginPreviewSurface.setTransform(matrix);
+//        }
+//        else {
+            binding!!.previewSurface.setTransform(matrix)
+        }
+    }
+
+    override fun goToConversation(accountId: String, conversationId: Uri) {
+        val context = requireContext()
+        if (isTablet(context)) {
+            startActivity(
+                Intent(DRingService.ACTION_CONV_ACCEPT, ConversationPath.toUri(accountId, conversationId), context, HomeActivity::class.java)
+            )
+        } else {
+            startActivityForResult(
+                Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, conversationId), context, ConversationActivity::class.java)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT),
+                HomeActivity.REQUEST_CODE_CONVERSATION
+            )
+        }
+    }
+
+    override fun goToAddContact(contact: Contact) {
+        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), ConversationFragment.REQ_ADD_CONTACT)
+    }
+
+    override fun goToContact(accountId: String, contact: Contact) {
+        startActivity(
+            Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, contact.uri))
+                .setClass(requireContext(), ContactDetailsActivity::class.java)
+        )
+    }
+
+    /**
+     * Checks if permissions are accepted for camera and microphone. Takes into account whether call is incoming and outgoing, and requests permissions if not available.
+     * Initializes the call if permissions are accepted.
+     *
+     * @param isIncoming true if call is incoming, false for outgoing
+     * @see .initializeCall
+     */
+    override fun prepareCall(isIncoming: Boolean) {
+        val audioGranted = mDeviceRuntimeService.hasAudioPermission()
+        val audioOnly: Boolean
+        val permissionType: Int
+        if (isIncoming) {
+            audioOnly = presenter.isAudioOnly
+            permissionType = REQUEST_PERMISSION_INCOMING
+        } else {
+            val args = arguments
+            audioOnly = args != null && args.getBoolean(KEY_AUDIO_ONLY)
+            permissionType = REQUEST_PERMISSION_OUTGOING
+        }
+        if (!audioOnly) {
+            val videoGranted = mDeviceRuntimeService.hasVideoPermission()
+            if ((!audioGranted || !videoGranted) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                val perms = ArrayList<String>()
+                if (!videoGranted) {
+                    perms.add(Manifest.permission.CAMERA)
+                }
+                if (!audioGranted) {
+                    perms.add(Manifest.permission.RECORD_AUDIO)
+                }
+                requestPermissions(perms.toTypedArray(), permissionType)
+            } else if (audioGranted && videoGranted) {
+                initializeCall(isIncoming)
+            }
+        } else {
+            if (!audioGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), permissionType)
+            } else if (audioGranted) {
+                initializeCall(isIncoming)
+            }
+        }
+    }
+
+    /**
+     * Starts a call. Takes into account whether call is incoming or outgoing.
+     *
+     * @param isIncoming true if call is incoming, false for outgoing
+     */
+    private fun initializeCall(isIncoming: Boolean) {
+        if (isIncoming) {
+            presenter.acceptCall()
+        } else {
+            arguments?.let { args ->
+                val conversation = ConversationPath.fromBundle(args)!!
+                presenter.initOutGoing(
+                    conversation.accountId,
+                    conversation.conversationUri,
+                    args.getString(Intent.EXTRA_PHONE_NUMBER),
+                    args.getBoolean(KEY_AUDIO_ONLY)
+                )
+            }
+        }
+    }
+
+    override fun finish() {
+        activity?.let { activity ->
+            activity.finishAndRemoveTask()
+            if (mBackstackLost) {
+                startActivity(
+                    Intent.makeMainActivity(ComponentName(activity, HomeActivity::class.java)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                )
+            }
+        }
+    }
+
+    fun speakerClicked() {
+        presenter.speakerClick(binding!!.callSpeakerBtn.isChecked)
+    }
+
+    private fun startScreenShare(mediaProjection: MediaProjection) {
+        if (presenter.startScreenShare(mediaProjection)) {
+            if (isChoosePluginMode) {
+                binding!!.pluginPreviewSurface.visibility = View.GONE
+            } else {
+                binding!!.previewSurface.visibility = View.GONE
+            }
+        } else {
+            Toast.makeText(requireContext(), "Can't start screen sharing", Toast.LENGTH_SHORT)
+                .show()
+        }
+    }
+
+    private fun stopShareScreen() {
+        binding?.previewSurface?.visibility = View.VISIBLE
+        presenter.stopScreenShare()
+    }
+
+    fun shareScreenClicked(checked: Boolean) {
+        if (!checked) {
+            stopShareScreen()
+        } else {
+            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_SHARE)
+        }
+    }
+
+    fun micClicked() {
+        binding?.let { binding->
+            presenter.muteMicrophoneToggled(binding.callMicBtn.isChecked)
+            binding.callMicBtn.setImageResource(if (binding.callMicBtn.isChecked) R.drawable.baseline_mic_off_24 else R.drawable.baseline_mic_24)
+        }
+    }
+
+    fun hangUpClicked() {
+        presenter.hangupCall()
+    }
+
+    fun refuseClicked() {
+        presenter.refuseCall()
+    }
+
+    fun acceptClicked() {
+        prepareCall(true)
+    }
+
+    fun cameraFlip() {
+        presenter.switchVideoInputClick()
+    }
+
+    fun addParticipant() {
+        presenter.startAddParticipant()
+    }
+
+    override fun startAddParticipant(conferenceId: String) {
+        startActivityForResult(Intent(Intent.ACTION_PICK)
+                .setClass(requireActivity(), ConversationSelectionActivity::class.java)
+                .putExtra(KEY_CONF_ID, conferenceId),
+            REQUEST_CODE_ADD_PARTICIPANT)
+    }
+
+    override fun toggleCallMediaHandler(id: String, callId: String, toggle: Boolean) {
+        JamiService.toggleCallMediaHandler(id, callId, toggle)
+    }
+
+    fun getCallMediaHandlerDetails(id: String): Map<String, String> {
+        return JamiService.getCallMediaHandlerDetails(id).toNative()
+    }
+
+    override fun positiveMediaButtonClicked() {
+        presenter.positiveButtonClicked()
+    }
+
+    override fun negativeMediaButtonClicked() {
+        presenter.negativeButtonClicked()
+    }
+
+    override fun toggleMediaButtonClicked() {
+        presenter.toggleButtonClicked()
+    }
+
+    override fun displayPluginsButton(): Boolean {
+        return JamiService.getPluginsEnabled() && JamiService.getCallMediaHandlers().size > 0
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        // Reset the padding of the RecyclerPicker on each
+        rp!!.setFirstLastElementsWidths(112, 112)
+        binding!!.recyclerPicker.visibility = View.GONE
+        if (isChoosePluginMode) {
+            displayHangupButton(false)
+            binding!!.recyclerPicker.visibility = View.VISIBLE
+            movePreview(true)
+            if (previousPluginPosition != -1) {
+                rp!!.scrollToPosition(previousPluginPosition)
+            }
+        } else {
+            movePreview(false)
+        }
+    }
+
+    fun toggleVideoPluginsCarousel(toggle: Boolean) {
+        if (isChoosePluginMode) {
+            if (toggle) {
+                binding!!.recyclerPicker.visibility = View.VISIBLE
+                movePreview(true)
+            } else {
+                binding!!.recyclerPicker.visibility = View.INVISIBLE
+                movePreview(false)
+            }
+        }
+    }
+
+    fun movePreview(up: Boolean) {
+        // Move the preview container (cardview) by a certain margin
+        if (up) {
+            animation.setIntValues(12, 128)
+        } else {
+            animation.setIntValues(128, 12)
+        }
+        animation.start()
+    }
+
+    /**
+     * Function that is called to show/hide the plugins recycler viewer and update UI
+     */
+    @SuppressLint("UseCompatLoadingForDrawables")
+    fun displayVideoPluginsCarousel() {
+        isChoosePluginMode = !isChoosePluginMode
+        val context: Context = requireActivity()
+
+        // Create callMediaHandlers and videoPluginsItems in a lazy manner
+        if (pluginsModeFirst) {
+            // Init
+            val callMediaHandlers = JamiService.getCallMediaHandlers()
+            val videoPluginsItems: MutableList<Drawable?> = ArrayList(callMediaHandlers.size + 1)
+            videoPluginsItems.add(context.getDrawable(R.drawable.baseline_cancel_24))
+            // Search for plugin call media handlers icons
+            // If a call media handler doesn't have an icon use a standard android icon
+            for (callMediaHandler in callMediaHandlers) {
+                val details = getCallMediaHandlerDetails(callMediaHandler)
+                var drawablePath = details["iconPath"]
+                if (drawablePath != null && drawablePath.endsWith("svg")) drawablePath =
+                    drawablePath.replace(".svg", ".png")
+                var handlerIcon = Drawable.createFromPath(drawablePath)
+                if (handlerIcon == null) {
+                    handlerIcon = context.getDrawable(R.drawable.ic_jami)
+                }
+                videoPluginsItems.add(handlerIcon)
+            }
+            rp!!.updateData(videoPluginsItems)
+            pluginsModeFirst = false
+        }
+        if (isChoosePluginMode) {
+            // hide hang up button and other call buttons
+            displayHangupButton(false)
+            // Display the plugins recyclerpicker
+            binding!!.recyclerPicker.visibility = View.VISIBLE
+            movePreview(true)
+
+            // Start loading the first or previous plugin if one was active
+            if (callMediaHandlers!!.isNotEmpty()) {
+                // If no previous plugin was active, take the first, else previous
+                val position: Int
+                if (previousPluginPosition < 1) {
+                    rp!!.scrollToPosition(1)
+                    position = 1
+                    previousPluginPosition = 1
+                } else {
+                    position = previousPluginPosition
+                }
+                val callMediaId = callMediaHandlers!![position - 1]
+                presenter.startPlugin(callMediaId)
+            }
+        } else {
+            if (previousPluginPosition > 0) {
+                val callMediaId = callMediaHandlers!![previousPluginPosition - 1]
+                presenter.toggleCallMediaHandler(callMediaId, false)
+                rp!!.scrollToPosition(previousPluginPosition)
+            }
+            presenter.stopPlugin()
+            binding!!.recyclerPicker.visibility = View.GONE
+            movePreview(false)
+            displayHangupButton(true)
+        }
+
+        //change preview image
+        displayVideoSurface(true, true)
+    }
+
+    /**
+     * Called whenever a plugin drawable in the recycler picker is clicked or scrolled to
+     */
+    override fun onItemSelected(position: Int) {
+        Log.i(TAG, "selected position: $position")
+        /* If there was a different plugin before, unload it
+         * If previousPluginPosition = -1 or 0, there was no plugin
+         */if (previousPluginPosition > 0) {
+            val callMediaId = callMediaHandlers!![previousPluginPosition - 1]
+            presenter.toggleCallMediaHandler(callMediaId, false)
+        }
+        if (position > 0) {
+            previousPluginPosition = position
+            val callMediaId = callMediaHandlers!![position - 1]
+            presenter.toggleCallMediaHandler(callMediaId, true)
+        }
+    }
+
+    /**
+     * Called whenever a plugin drawable in the recycler picker is clicked
+     */
+    override fun onItemClicked(position: Int) {
+        Log.i(TAG, "selected position: $position")
+        if (position == 0) {
+            /* If there was a different plugin before, unload it
+             * If previousPluginPosition = -1 or 0, there was no plugin
+             */
+            if (previousPluginPosition > 0) {
+                val callMediaId = callMediaHandlers!![previousPluginPosition - 1]
+                presenter.toggleCallMediaHandler(callMediaId, false)
+                rp!!.scrollToPosition(previousPluginPosition)
+            }
+            val callActivity = activity as CallActivity?
+            callActivity?.showSystemUI()
+            toggleVideoPluginsCarousel(false)
+            displayVideoPluginsCarousel()
+        }
+    }
+
+    private val blinkingAnimation: Animation
+        get() {
+            return AlphaAnimation(1f, 0f).apply {
+                duration = 400
+                interpolator = LinearInterpolator()
+                repeatCount = Animation.INFINITE
+                repeatMode = Animation.REVERSE
+            }
+        }
+
+    companion object {
+        val TAG = CallFragment::class.simpleName!!
+        const val ACTION_PLACE_CALL = "PLACE_CALL"
+        const val ACTION_GET_CALL = "GET_CALL"
+        const val KEY_ACTION = "action"
+        const val KEY_CONF_ID = "confId"
+        const val KEY_AUDIO_ONLY = "AUDIO_ONLY"
+        private const val REQUEST_CODE_ADD_PARTICIPANT = 6
+        private const val REQUEST_PERMISSION_INCOMING = 1003
+        private const val REQUEST_PERMISSION_OUTGOING = 1004
+        private const val REQUEST_CODE_SCREEN_SHARE = 7
+
+        fun newInstance(action: String, path: ConversationPath?, contactId: String?, audioOnly: Boolean): CallFragment {
+            val bundle = Bundle()
+            bundle.putString(KEY_ACTION, action)
+            path?.toBundle(bundle)
+            bundle.putString(Intent.EXTRA_PHONE_NUMBER, contactId)
+            bundle.putBoolean(KEY_AUDIO_ONLY, audioOnly)
+            val countDownFragment = CallFragment()
+            countDownFragment.arguments = bundle
+            return countDownFragment
+        }
+
+        fun newInstance(action: String, confId: String?): CallFragment {
+            val bundle = Bundle()
+            bundle.putString(KEY_ACTION, action)
+            bundle.putString(KEY_CONF_ID, confId)
+            val countDownFragment = CallFragment()
+            countDownFragment.arguments = bundle
+            return countDownFragment
+        }
+
+        fun callStateToHumanState(state: CallStatus?): Int {
+            return when (state) {
+                CallStatus.SEARCHING -> R.string.call_human_state_searching
+                CallStatus.CONNECTING -> R.string.call_human_state_connecting
+                CallStatus.RINGING -> R.string.call_human_state_ringing
+                CallStatus.CURRENT -> R.string.call_human_state_current
+                CallStatus.HUNGUP -> R.string.call_human_state_hungup
+                CallStatus.BUSY -> R.string.call_human_state_busy
+                CallStatus.FAILURE -> R.string.call_human_state_failure
+                CallStatus.HOLD -> R.string.call_human_state_hold
+                CallStatus.UNHOLD -> R.string.call_human_state_unhold
+                CallStatus.OVER -> R.string.call_human_state_over
+                CallStatus.NONE -> R.string.call_human_state_none
+                else -> R.string.call_human_state_none
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java
index 0a3fa87fb..d60da326e 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java
@@ -23,17 +23,18 @@ import javax.inject.Inject;
 
 import cx.ring.R;
 import cx.ring.adapters.SmartListAdapter;
-import cx.ring.application.JamiApplication;
 import cx.ring.client.HomeActivity;
 import cx.ring.databinding.FragContactPickerBinding;
-import net.jami.facades.ConversationFacade;
+import net.jami.services.ConversationFacade;
 import net.jami.model.Contact;
 import net.jami.smartlist.SmartListViewModel;
 import cx.ring.viewholders.SmartListViewHolder;
 import cx.ring.views.AvatarDrawable;
+import dagger.hilt.android.AndroidEntryPoint;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
+@AndroidEntryPoint
 public class ContactPickerFragment extends BottomSheetDialogFragment {
 
     public static final String TAG = ContactPickerFragment.class.getSimpleName();
@@ -59,8 +60,8 @@ public class ContactPickerFragment extends BottomSheetDialogFragment {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setRetainInstance(true);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
+        //setRetainInstance(true);
+        //((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
deleted file mode 100644
index ec1c548c8..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java
+++ /dev/null
@@ -1,1283 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.fragments;
-
-import android.Manifest;
-import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ActivityOptions;
-import android.content.ActivityNotFoundException;
-import android.content.ClipData;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.IBinder;
-import android.provider.MediaStore;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-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.view.inputmethod.EditorInfo;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.view.menu.MenuBuilder;
-import androidx.appcompat.view.menu.MenuPopupHelper;
-import androidx.appcompat.widget.PopupMenu;
-import androidx.appcompat.widget.Toolbar;
-import androidx.core.view.ViewCompat;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.conversation.ConversationPresenter;
-import net.jami.conversation.ConversationView;
-import net.jami.daemon.JamiService;
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.DataTransfer;
-import net.jami.model.Error;
-import net.jami.model.Interaction;
-import net.jami.model.Phone;
-import net.jami.model.Uri;
-import net.jami.services.NotificationService;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import cx.ring.R;
-import cx.ring.adapters.ConversationAdapter;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import cx.ring.client.ContactDetailsActivity;
-import cx.ring.client.ConversationActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.databinding.FragConversationBinding;
-import cx.ring.interfaces.Colorable;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.service.DRingService;
-import cx.ring.service.LocationSharingService;
-import cx.ring.services.NotificationServiceImpl;
-import cx.ring.services.SharedPreferencesServiceImpl;
-import cx.ring.utils.ActionHelper;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import cx.ring.utils.MediaButtonsHelper;
-import cx.ring.views.AvatarDrawable;
-import cx.ring.views.AvatarFactory;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-import static android.app.Activity.RESULT_OK;
-
-public class ConversationFragment extends BaseSupportFragment<ConversationPresenter> implements
-        MediaButtonsHelper.MediaButtonsHelperCallback,
-        ConversationView, SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final String TAG = ConversationFragment.class.getSimpleName();
-
-    public static final int REQ_ADD_CONTACT = 42;
-
-    public static final String KEY_PREFERENCE_PENDING_MESSAGE = "pendingMessage";
-    public static final String KEY_PREFERENCE_CONVERSATION_COLOR = "color";
-    public static final String KEY_PREFERENCE_CONVERSATION_LAST_READ = "lastRead";
-    public static final String KEY_PREFERENCE_CONVERSATION_SYMBOL = "symbol";
-    public static final String EXTRA_SHOW_MAP = "showMap";
-
-    private static final int REQUEST_CODE_FILE_PICKER = 1000;
-    private static final int REQUEST_PERMISSION_CAMERA = 1001;
-    private static final int REQUEST_CODE_TAKE_PICTURE = 1002;
-    private static final int REQUEST_CODE_SAVE_FILE = 1003;
-    private static final int REQUEST_CODE_CAPTURE_AUDIO = 1004;
-    private static final int REQUEST_CODE_CAPTURE_VIDEO = 1005;
-
-    private ServiceConnection locationServiceConnection = null;
-
-    private FragConversationBinding binding;
-    private MenuItem mAudioCallBtn = null;
-    private MenuItem mVideoCallBtn = null;
-
-    private View currentBottomView = null;
-    private ConversationAdapter mAdapter = null;
-    private int marginPx;
-    private int marginPxTotal;
-    private final ValueAnimator animation = new ValueAnimator();
-
-    private SharedPreferences mPreferences;
-
-    private File mCurrentPhoto = null;
-    private String mCurrentFileAbsolutePath = null;
-    private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
-    private int mSelectedPosition;
-
-    private boolean mIsBubble;
-
-    private AvatarDrawable mConversationAvatar;
-    private final Map<String, AvatarDrawable> mParticipantAvatars = new HashMap<>();
-    private final Map<String, AvatarDrawable> mSmallParticipantAvatars = new HashMap<>();
-    private int mapWidth, mapHeight;
-    private String mLastRead;
-
-    private boolean loading = true;
-
-    public AvatarDrawable getConversationAvatar(String uri) {
-        return mParticipantAvatars.get(uri);
-    }
-    public AvatarDrawable getSmallConversationAvatar(String uri) {
-        synchronized (mSmallParticipantAvatars) {
-            return mSmallParticipantAvatars.get(uri);
-        }
-    }
-
-    private static int getIndex(Spinner spinner, Uri myString) {
-        for (int i = 0, n = spinner.getCount(); i < n; i++)
-            if (((Phone) spinner.getItemAtPosition(i)).getNumber().equals(myString)) {
-                return i;
-            }
-        return 0;
-    }
-
-    @Override
-    public void refreshView(final List<Interaction> conversation) {
-        if (conversation == null) {
-            return;
-        }
-        if (binding != null)
-            binding.pbLoading.setVisibility(View.GONE);
-        if (mAdapter != null) {
-            mAdapter.updateDataset(conversation);
-            loading = false;
-        }
-        requireActivity().invalidateOptionsMenu();
-    }
-
-    @Override
-    public void scrollToEnd() {
-        if (mAdapter.getItemCount() > 0) {
-            binding.histList.scrollToPosition(mAdapter.getItemCount() - 1);
-        }
-    }
-
-    private static void setBottomPadding(@NonNull View view, int padding) {
-        view.setPadding(
-                view.getPaddingLeft(),
-                view.getPaddingTop(),
-                view.getPaddingRight(),
-                padding);
-    }
-
-    private void updateListPadding() {
-        if (currentBottomView != null && currentBottomView.getHeight() != 0) {
-            setBottomPadding(binding.histList, currentBottomView.getHeight() + marginPxTotal);
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
-            params.bottomMargin = currentBottomView.getHeight() + marginPxTotal;
-            binding.mapCard.setLayoutParams(params);
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        Resources res = getResources();
-        marginPx = res.getDimensionPixelSize(R.dimen.conversation_message_input_margin);
-        mapWidth = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_width);
-        mapHeight = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_height);
-        marginPxTotal = marginPx;
-
-        binding = FragConversationBinding.inflate(inflater, container, false);
-        binding.setPresenter(this);
-
-        animation.setDuration(150);
-        animation.addUpdateListener(valueAnimator -> setBottomPadding(binding.histList, (Integer)valueAnimator.getAnimatedValue()));
-
-        ViewCompat.setOnApplyWindowInsetsListener(binding.histList, (v, insets) -> {
-            marginPxTotal = marginPx + insets.getSystemWindowInsetBottom();
-            updateListPadding();
-            insets.consumeSystemWindowInsets();
-            return insets;
-        });
-        View layout = binding.conversationLayout;
-
-        // remove action bar height for tablet layout
-        if (DeviceUtils.isTablet(layout.getContext())) {
-            layout.setPadding(layout.getPaddingLeft(), 0, layout.getPaddingRight(), layout.getPaddingBottom());
-        }
-
-        int paddingTop = layout.getPaddingTop();
-        ViewCompat.setOnApplyWindowInsetsListener(layout, (v, insets) -> {
-            v.setPadding(
-                    v.getPaddingLeft(),
-                    paddingTop + insets.getSystemWindowInsetTop(),
-                    v.getPaddingRight(),
-                    v.getPaddingBottom());
-            insets.consumeSystemWindowInsets();
-            return insets;
-        });
-
-        binding.ongoingcallPane.setVisibility(View.GONE);
-        binding.msgInputTxt.setMediaListener(contentInfo -> startFileSend(AndroidFileUtils
-                .getCacheFile(requireContext(), contentInfo.getContentUri())
-                .flatMapCompletable(this::sendFile)
-                .doFinally(contentInfo::releasePermission)));
-        binding.msgInputTxt.setOnEditorActionListener((v, actionId, event) -> actionSendMsgText(actionId));
-        binding.msgInputTxt.setOnFocusChangeListener((view, hasFocus) -> {
-            if (hasFocus)  {
-                Fragment fragment = getChildFragmentManager().findFragmentById(R.id.mapLayout);
-                if (fragment != null) {
-                    ((LocationSharingFragment) fragment).hideControls();
-                }
-            }
-        });
-        binding.msgInputTxt.addTextChangedListener(new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
-
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-            }
-
-            @Override
-            public void afterTextChanged(Editable s) {
-                String message = s.toString();
-                boolean hasMessage = !TextUtils.isEmpty(message);
-                presenter.onComposingChanged(hasMessage);
-                if (hasMessage) {
-                    binding.msgSend.setVisibility(View.VISIBLE);
-                    binding.emojiSend.setVisibility(View.GONE);
-                } else {
-                    binding.msgSend.setVisibility(View.GONE);
-                    binding.emojiSend.setVisibility(View.VISIBLE);
-                }
-                if (mPreferences != null) {
-                    if (hasMessage)
-                        mPreferences.edit().putString(KEY_PREFERENCE_PENDING_MESSAGE, message).apply();
-                    else
-                        mPreferences.edit().remove(KEY_PREFERENCE_PENDING_MESSAGE).apply();
-                }
-            }
-        });
-
-        setHasOptionsMenu(true);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        if (mPreferences != null) {
-            String pendingMessage = mPreferences.getString(KEY_PREFERENCE_PENDING_MESSAGE, null);
-            if (!TextUtils.isEmpty(pendingMessage)) {
-                binding.msgInputTxt.setText(pendingMessage);
-                binding.msgSend.setVisibility(View.VISIBLE);
-                binding.emojiSend.setVisibility(View.GONE);
-            }
-        }
-
-        binding.msgInputTxt.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-            if (oldBottom == 0 && oldTop == 0) {
-                updateListPadding();
-            } else {
-                if (animation.isStarted())
-                    animation.cancel();
-                animation.setIntValues(binding.histList.getPaddingBottom(), (currentBottomView == null ? 0 : currentBottomView.getHeight()) + marginPxTotal);
-                animation.start();
-            }
-        });
-
-        binding.histList.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            // The minimum amount of items to have below current scroll position
-            // before loading more.
-            static private final int visibleThreshold = 3;
-
-            @Override
-            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
-            }
-
-            @Override
-            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
-                if (!loading && layoutManager.findFirstVisibleItemPosition() < visibleThreshold) {
-                    loading = true;
-                    presenter.loadMore();
-                }
-            }
-        });
-
-        DefaultItemAnimator animator = (DefaultItemAnimator) binding.histList.getItemAnimator();
-        if (animator != null)
-            animator.setSupportsChangeAnimations(false);
-        binding.histList.setAdapter(mAdapter);
-    }
-
-    @Override
-    public void setConversationColor(int color) {
-        Colorable activity = (Colorable) getActivity();
-        if (activity != null)
-            activity.setColor(color);
-        mAdapter.setPrimaryColor(color);
-    }
-
-    @Override
-    public void setConversationSymbol(CharSequence symbol) {
-        binding.emojiSend.setText(symbol);
-    }
-
-    @Override
-    public void onDestroyView() {
-        if (mPreferences != null)
-            mPreferences.unregisterOnSharedPreferenceChangeListener(this);
-        animation.removeAllUpdateListeners();
-        binding.histList.setAdapter(null);
-        mCompositeDisposable.clear();
-        if (locationServiceConnection != null) {
-            try {
-                requireContext().unbindService(locationServiceConnection);
-            } catch (Exception e) {
-                Log.w(TAG, "Error unbinding service: " + e.getMessage());
-            }
-        }
-        mAdapter = null;
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public boolean onContextItemSelected(@NonNull MenuItem item) {
-        if (mAdapter.onContextItemSelected(item))
-            return true;
-        return super.onContextItemSelected(item);
-    }
-
-    public void updateAdapterItem() {
-        if (mSelectedPosition != -1) {
-            mAdapter.notifyItemChanged(mSelectedPosition);
-            mSelectedPosition = -1;
-        }
-    }
-
-    public void sendMessageText() {
-        String message = binding.msgInputTxt.getText().toString();
-        clearMsgEdit();
-        presenter.sendTextMessage(message);
-    }
-
-    public void sendEmoji() {
-        presenter.sendTextMessage(binding.emojiSend.getText().toString());
-    }
-
-    @SuppressLint("RestrictedApi")
-    public void expandMenu(View v) {
-        Context context = requireContext();
-        PopupMenu popup = new PopupMenu(context, v);
-        popup.inflate(R.menu.conversation_share_actions);
-        popup.setOnMenuItemClickListener(item -> {
-            int itemId = item.getItemId();
-            if (itemId == R.id.conv_send_audio) {
-                sendAudioMessage();
-            } else if (itemId == R.id.conv_send_video) {
-                sendVideoMessage();
-            } else if (itemId == R.id.conv_send_file) {
-                presenter.selectFile();
-            } else if (itemId == R.id.conv_share_location) {
-                shareLocation();
-            } else if (itemId == R.id.chat_plugins) {
-                presenter.showPluginListHandlers();
-            }
-            return false;
-        });
-        popup.getMenu().findItem(R.id.chat_plugins).setVisible(JamiService.getPluginsEnabled() && !JamiService.getChatHandlers().isEmpty());
-        MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), v);
-        menuHelper.setForceShowIcon(true);
-        menuHelper.show();
-    }
-
-    public void showPluginListHandlers(String accountId, String contactId) {
-        Log.w(TAG, "show Plugin Chat Handlers List");
-
-        FragmentManager fragmentManager = getChildFragmentManager();
-        PluginHandlersListFragment fragment = PluginHandlersListFragment.newInstance(accountId, contactId);
-        fragmentManager.beginTransaction()
-                .add(R.id.pluginListHandlers, fragment, PluginHandlersListFragment.TAG)
-                .commit();
-
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
-        if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
-            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
-            binding.mapCard.setLayoutParams(params);
-        }
-        binding.mapCard.setVisibility(View.VISIBLE);
-    }
-
-    public void hidePluginListHandlers() {
-        if (binding.mapCard.getVisibility() != View.GONE) {
-            binding.mapCard.setVisibility(View.GONE);
-
-            FragmentManager fragmentManager = getChildFragmentManager();
-            Fragment fragment = fragmentManager.findFragmentById(R.id.pluginListHandlers);
-
-            if (fragment != null) {
-                fragmentManager.beginTransaction()
-                        .remove(fragment)
-                        .commit();
-            }
-        }
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
-        if (params.width != mapWidth) {
-            params.width = mapWidth;
-            params.height = mapHeight;
-            binding.mapCard.setLayoutParams(params);
-        }
-    }
-
-    public void shareLocation() {
-        presenter.shareLocation();
-    }
-
-    public void closeLocationSharing(boolean isSharing) {
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
-        if (params.width != mapWidth) {
-            params.width = mapWidth;
-            params.height = mapHeight;
-            binding.mapCard.setLayoutParams(params);
-        }
-        if (!isSharing)
-            hideMap();
-    }
-
-    public void openLocationSharing() {
-        binding.conversationLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.mapCard.getLayoutParams();
-        if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
-            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-            params.height = ViewGroup.LayoutParams.MATCH_PARENT;
-            binding.mapCard.setLayoutParams(params);
-        }
-    }
-
-    @Override
-    public void startShareLocation(String accountId, String conversationId) {
-        showMap(accountId, conversationId, true);
-    }
-
-    /**
-     * Used to update with the past adapter position when a long click was registered
-     */
-    public void updatePosition(int position) {
-        mSelectedPosition = position;
-    }
-
-    @Override
-    public void showMap(String accountId, String contactId, boolean open)  {
-        if (binding.mapCard.getVisibility() == View.GONE) {
-            Log.w(TAG, "showMap " + accountId + " " + contactId);
-
-            FragmentManager fragmentManager = getChildFragmentManager();
-            LocationSharingFragment fragment = LocationSharingFragment.newInstance(accountId, contactId, open);
-            fragmentManager.beginTransaction()
-                    .add(R.id.mapLayout, fragment, "map")
-                    .commit();
-            binding.mapCard.setVisibility(View.VISIBLE);
-        }
-        if (open) {
-            Fragment fragment = getChildFragmentManager().findFragmentById(R.id.mapLayout);
-            if (fragment != null) {
-                ((LocationSharingFragment) fragment).showControls();
-            }
-        }
-    }
-
-    @Override
-    public void hideMap() {
-        if (binding.mapCard.getVisibility() != View.GONE) {
-            binding.mapCard.setVisibility(View.GONE);
-
-            FragmentManager fragmentManager = getChildFragmentManager();
-            Fragment fragment = fragmentManager.findFragmentById(R.id.mapLayout);
-
-            if (fragment != null) {
-                fragmentManager.beginTransaction()
-                        .remove(fragment)
-                        .commit();
-            }
-        }
-    }
-
-    public void sendAudioMessage() {
-        if (!presenter.getDeviceRuntimeService().hasAudioPermission()) {
-            requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE_CAPTURE_AUDIO);
-        } else {
-            try {
-                Context ctx = requireContext();
-                Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
-                mCurrentPhoto = AndroidFileUtils.createAudioFile(ctx);
-                startActivityForResult(intent, REQUEST_CODE_CAPTURE_AUDIO);
-            } catch (Exception ex) {
-                Log.e(TAG, "sendAudioMessage: error", ex);
-                Toast.makeText(getActivity(), "Can't find audio recorder app", Toast.LENGTH_SHORT).show();
-            }
-        }
-    }
-
-    public void sendVideoMessage() {
-        if (!presenter.getDeviceRuntimeService().hasVideoPermission()) {
-            requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAPTURE_VIDEO);
-        } else {
-            try {
-                Context context = requireContext();
-                Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-                intent.putExtra("android.intent.extras.CAMERA_FACING", android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
-                intent.putExtra("android.intent.extras.LENS_FACING_FRONT", 1);
-                intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
-                    mCurrentPhoto = AndroidFileUtils.createVideoFile(context);
-                intent.putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, mCurrentPhoto));
-                startActivityForResult(intent, REQUEST_CODE_CAPTURE_VIDEO);
-            } catch (Exception ex) {
-                Log.e(TAG, "sendVideoMessage: error", ex);
-                Toast.makeText(getActivity(), "Can't find video recorder app", Toast.LENGTH_SHORT).show();
-            }
-        }
-    }
-
-    public void takePicture() {
-        if (!presenter.getDeviceRuntimeService().hasVideoPermission()) {
-            requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_TAKE_PICTURE);
-            return;
-        }
-        Context c = getContext();
-        if (c == null)
-            return;
-        try {
-            File photoFile = AndroidFileUtils.createImageFile(c);
-            Log.i(TAG, "takePicture: trying to save to " + photoFile);
-            android.net.Uri photoURI = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, photoFile);
-            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
-                    .putExtra("android.intent.extras.CAMERA_FACING", 1)
-                    .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
-                    .putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
-            mCurrentPhoto = photoFile;
-            startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PICTURE);
-        } catch (Exception e) {
-            Toast.makeText(c, "Error taking picture: " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void askWriteExternalStoragePermission() {
-        requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, JamiApplication.PERMISSIONS_REQUEST);
-    }
-
-    @Override
-    public void openFilePicker() {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType("*/*");
-
-        startActivityForResult(intent, REQUEST_CODE_FILE_PICKER);
-    }
-
-    private Completable sendFile(File file) {
-        return Completable.fromAction(() -> presenter.sendFile(file));
-    }
-
-    private void startFileSend(Completable op) {
-        setLoading(true);
-        op.observeOn(AndroidSchedulers.mainThread())
-                .doFinally(() -> setLoading(false))
-                .subscribe(() -> {}, e -> {
-                    Log.e(TAG, "startFileSend: not able to create cache file", e);
-                    displayErrorToast(Error.INVALID_FILE);
-                });
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent resultData) {
-        Log.w(TAG, "onActivityResult: " + requestCode + " " + resultCode + " " + resultData);
-        android.net.Uri uri = resultData == null ? null : resultData.getData();
-        if (requestCode == REQUEST_CODE_FILE_PICKER) {
-            if (resultCode == RESULT_OK && uri != null) {
-                startFileSend(AndroidFileUtils.getCacheFile(requireContext(), uri)
-                        .observeOn(AndroidSchedulers.mainThread())
-                        .flatMapCompletable(this::sendFile));
-            }
-        } else if (requestCode == REQUEST_CODE_TAKE_PICTURE
-                || requestCode == REQUEST_CODE_CAPTURE_AUDIO
-                || requestCode == REQUEST_CODE_CAPTURE_VIDEO)
-        {
-            if (resultCode != RESULT_OK) {
-                mCurrentPhoto = null;
-                return;
-            }
-            Log.w(TAG, "onActivityResult: mCurrentPhoto " + mCurrentPhoto.getAbsolutePath() + " " + mCurrentPhoto.exists() + " " + mCurrentPhoto.length());
-            Single<File> file = null;
-            if (mCurrentPhoto == null || !mCurrentPhoto.exists() || mCurrentPhoto.length() == 0) {
-                if (uri != null) {
-                    file = AndroidFileUtils.getCacheFile(requireContext(), uri);
-                }
-            } else {
-                file = Single.just(mCurrentPhoto);
-            }
-            mCurrentPhoto = null;
-            if (file == null) {
-                Toast.makeText(getActivity(), "Can't find picture", Toast.LENGTH_SHORT).show();
-                return;
-            }
-            startFileSend(file.flatMapCompletable(this::sendFile));
-        }
-        // File download trough SAF
-        else if (requestCode == ConversationFragment.REQUEST_CODE_SAVE_FILE) {
-            if (resultCode == RESULT_OK &&  uri != null) {
-                writeToFile(uri);
-            }
-        }
-    }
-
-    private void writeToFile(android.net.Uri data) {
-        File input = new File(mCurrentFileAbsolutePath);
-        if (requireContext().getContentResolver() != null)
-            mCompositeDisposable.add(AndroidFileUtils.copyFileToUri(requireContext().getContentResolver(), input, data).
-                    observeOn(AndroidSchedulers.mainThread()).
-                    subscribe(() -> Toast.makeText(getContext(), R.string.file_saved_successfully, Toast.LENGTH_SHORT).show(),
-                            error -> Toast.makeText(getContext(), R.string.generic_error, Toast.LENGTH_SHORT).show()));
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        for (int i = 0, n = permissions.length; i < n; i++) {
-            boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
-            switch (permissions[i]) {
-                case Manifest.permission.CAMERA:
-                    presenter.cameraPermissionChanged(granted);
-                    if (granted) {
-                        if (requestCode == REQUEST_CODE_CAPTURE_VIDEO) {
-                            sendVideoMessage();
-                        } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
-                            takePicture();
-                        }
-                    }
-                    return;
-                case Manifest.permission.RECORD_AUDIO:
-                    if (granted) {
-                        if (requestCode == REQUEST_CODE_CAPTURE_AUDIO) {
-                            sendAudioMessage();
-                        }
-                    }
-                    return;
-                default:
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void addElement(Interaction element) {
-        if (mLastRead != null && mLastRead.equals(element.getMessageId()))
-            element.read();
-        if (mAdapter.add(element))
-            scrollToEnd();
-        loading = false;
-    }
-
-    @Override
-    public void updateElement(Interaction element) {
-        mAdapter.update(element);
-    }
-
-    @Override
-    public void removeElement(Interaction element) {
-        mAdapter.remove(element);
-    }
-
-    @Override
-    public void setComposingStatus(Account.ComposingStatus composingStatus) {
-        mAdapter.setComposingStatus(composingStatus);
-        if (composingStatus == Account.ComposingStatus.Active)
-            scrollToEnd();
-    }
-
-    @Override
-    public void setLastDisplayed(Interaction interaction) {
-        mAdapter.setLastDisplayed(interaction);
-    }
-
-    @Override
-    public void acceptFile(String accountId, Uri conversationUri, DataTransfer transfer) {
-        File cacheDir = requireContext().getCacheDir();
-        long spaceLeft = AndroidFileUtils.getSpaceLeft(cacheDir.toString());
-        if (spaceLeft == -1L || transfer.getTotalSize() > spaceLeft) {
-            presenter.noSpaceLeft();
-            return;
-        }
-        requireActivity().startService(new Intent(DRingService.ACTION_FILE_ACCEPT, ConversationPath.toUri(accountId, conversationUri), requireContext(), DRingService.class)
-                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId())
-                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId()));
-    }
-
-    @Override
-    public void refuseFile(String accountId, Uri conversationUri, DataTransfer transfer) {
-        requireActivity().startService(new Intent(DRingService.ACTION_FILE_CANCEL, ConversationPath.toUri(accountId, conversationUri), requireContext(), DRingService.class)
-                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId())
-                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId()));
-    }
-
-    @Override
-    public void shareFile(File path, String displayName) {
-        Context c = getContext();
-        if (c == null)
-            return;
-        android.net.Uri fileUri = null;
-        try {
-            fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName);
-        } catch (IllegalArgumentException e) {
-            Log.e("File Selector", "The selected file can't be shared: " + path.getName());
-        }
-        if (fileUri != null) {
-            Intent sendIntent = new Intent();
-            sendIntent.setAction(Intent.ACTION_SEND);
-            sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            String type = c.getContentResolver().getType(fileUri.buildUpon().appendPath(displayName).build());
-            sendIntent.setDataAndType(fileUri, type);
-            sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
-            startActivity(Intent.createChooser(sendIntent, null));
-        }
-    }
-
-    @Override
-    public void openFile(File path, String displayName) {
-        Context c = getContext();
-        if (c == null)
-            return;
-        android.net.Uri fileUri = null;
-        try {
-            fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "The selected file can't be shared: " + path.getName());
-        }
-        if (fileUri != null) {
-            Intent sendIntent = new Intent();
-            sendIntent.setAction(Intent.ACTION_VIEW);
-            sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            String type = c.getContentResolver().getType(fileUri.buildUpon().appendPath(displayName).build());
-            sendIntent.setDataAndType(fileUri, type);
-            sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
-            //startActivity(Intent.createChooser(sendIntent, null));
-            try {
-                startActivity(sendIntent);
-            } catch (ActivityNotFoundException e) {
-                Snackbar.make(getView(), R.string.conversation_open_file_error, Snackbar.LENGTH_LONG).show();
-                Log.e("File Loader", "File of unknown type, could not open: " + path.getName());
-            }
-        }
-    }
-
-    boolean actionSendMsgText(int actionId) {
-        switch (actionId) {
-            case EditorInfo.IME_ACTION_SEND:
-                sendMessageText();
-                return true;
-        }
-        return false;
-    }
-
-    public void onClick() {
-        presenter.clickOnGoingPane();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        presenter.resume(mIsBubble);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        presenter.pause();
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        //presenter.pause();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        //presenter.resume(mIsBubble);
-    }
-
-    @Override
-    public void onDestroy() {
-        mCompositeDisposable.dispose();
-        super.onDestroy();
-    }
-
-    @Override
-    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
-        if (!isVisible()) {
-            return;
-        }
-        inflater.inflate(R.menu.conversation_actions, menu);
-        mAudioCallBtn = menu.findItem(R.id.conv_action_audiocall);
-        mVideoCallBtn = menu.findItem(R.id.conv_action_videocall);
-    }
-
-    public void openContact() {
-        presenter.openContact();
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        int itemId = item.getItemId();
-        if (itemId == android.R.id.home) {
-            startActivity(new Intent(getActivity(), HomeActivity.class));
-            return true;
-        } else if (itemId == R.id.conv_action_audiocall) {
-            presenter.goToCall(true);
-            return true;
-        } else if (itemId == R.id.conv_action_videocall) {
-            presenter.goToCall(false);
-            return true;
-        } else if (itemId == R.id.conv_contact_details) {
-            presenter.openContact();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    protected void initPresenter(ConversationPresenter presenter) {
-        ConversationPath path = ConversationPath.fromBundle(getArguments());
-        mIsBubble = getArguments().getBoolean(NotificationServiceImpl.EXTRA_BUBBLE);
-        Log.w(TAG, "initPresenter " + path);
-        if (path == null)
-            return;
-
-        Uri uri = path.getConversationUri();
-        mAdapter = new ConversationAdapter(this, presenter);
-        presenter.init(uri, path.getAccountId());
-        try {
-            mPreferences = SharedPreferencesServiceImpl.getConversationPreferences(requireContext(), path.getAccountId(), uri);
-            mPreferences.registerOnSharedPreferenceChangeListener(this);
-            presenter.setConversationColor(mPreferences.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, getResources().getColor(R.color.color_primary_light)));
-            presenter.setConversationSymbol(mPreferences.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, getResources().getText(R.string.conversation_default_emoji).toString()));
-            mLastRead = mPreferences.getString(KEY_PREFERENCE_CONVERSATION_LAST_READ, null);
-        } catch (Exception e) {
-            Log.e(TAG, "Can't load conversation preferences");
-        }
-
-        if (locationServiceConnection == null) {
-            locationServiceConnection = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    Log.w(TAG, "onServiceConnected");
-                    LocationSharingService.LocalBinder binder = (LocationSharingService.LocalBinder) service;
-                    LocationSharingService locationService = binder.getService();
-                    ConversationPath path = new ConversationPath(presenter.getPath());
-                    if (locationService.isSharing(path)) {
-                        showMap(path.getAccountId(), uri.getUri(), false);
-                    }
-                    try {
-                        requireContext().unbindService(locationServiceConnection);
-                    } catch (Exception e) {
-                        Log.w(TAG, "Error unbinding service", e);
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    Log.w(TAG, "onServiceDisconnected");
-                    locationServiceConnection = null;
-                }
-            };
-
-            Log.w(TAG, "bindService");
-            requireContext().bindService(new Intent(requireContext(), LocationSharingService.class), locationServiceConnection, 0);
-        }
-    }
-
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        switch (key) {
-            case KEY_PREFERENCE_CONVERSATION_COLOR:
-                presenter.setConversationColor(prefs.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, getResources().getColor(R.color.color_primary_light)));
-                break;
-            case KEY_PREFERENCE_CONVERSATION_SYMBOL:
-                presenter.setConversationSymbol(prefs.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, getResources().getText(R.string.conversation_default_emoji).toString()));
-                break;
-        }
-    }
-
-    @Override
-    public void updateContact(Contact contact) {
-        String contactKey = contact.getPrimaryNumber();
-        AvatarDrawable a = mSmallParticipantAvatars.get(contactKey);
-        if (a != null) {
-            a.update(contact);
-            mParticipantAvatars.get(contactKey).update(contact);
-            mAdapter.setPhoto();
-        } else {
-            mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), contact, true)
-                    .subscribeOn(Schedulers.computation())
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(avatar -> {
-                        mParticipantAvatars.put(contactKey, (AvatarDrawable) avatar);
-                        mSmallParticipantAvatars.put(contactKey, new AvatarDrawable.Builder()
-                                .withContact(contact)
-                                .withCircleCrop(true)
-                                .withPresence(false)
-                                .build(requireContext()));
-                        mAdapter.setPhoto();
-                    }));
-        }
-    }
-
-    @Override
-    public void displayContact(Conversation conversation) {
-        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(d -> {
-                    mConversationAvatar = (AvatarDrawable) d;
-                    mParticipantAvatars.put(conversation.getUri().getRawRingId(), new AvatarDrawable((AvatarDrawable) d));
-                    setupActionbar(conversation);
-                }));
-    }
-
-    @Override
-    public void displayOnGoingCallPane(final boolean display) {
-        binding.ongoingcallPane.setVisibility(display ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void displayNumberSpinner(final Conversation conversation, final Uri number) {
-        binding.numberSelector.setVisibility(View.VISIBLE);
-        //binding.numberSelector.setAdapter(new NumberAdapter(getActivity(), conversation.getContact(), false));
-        binding.numberSelector.setSelection(getIndex(binding.numberSelector, number));
-    }
-
-    @Override
-    public void hideNumberSpinner() {
-        binding.numberSelector.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void clearMsgEdit() {
-        binding.msgInputTxt.setText("");
-    }
-
-    @Override
-    public void goToHome() {
-
-        if (getActivity() instanceof ConversationActivity) {
-            getActivity().finish();
-        }
-    }
-
-    @Override
-    public void goToAddContact(Contact contact) {
-        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), REQ_ADD_CONTACT);
-    }
-
-    @Override
-    public void goToCallActivity(String conferenceId) {
-        startActivity(new Intent(Intent.ACTION_VIEW)
-                .setClass(requireActivity().getApplicationContext(), CallActivity.class)
-                .putExtra(NotificationService.KEY_CALL_ID, conferenceId));
-    }
-
-    @Override
-    public void goToContactActivity(String accountId, Uri uri) {
-        Toolbar toolbar = requireActivity().findViewById(R.id.main_toolbar);
-        ImageView logo = toolbar.findViewById(R.id.contact_image);
-        startActivity(new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, uri))
-                        .setClass(requireActivity().getApplicationContext(), ContactDetailsActivity.class),
-                ActivityOptions.makeSceneTransitionAnimation(getActivity(), logo, "conversationIcon").toBundle());
-    }
-
-    @Override
-    public void goToCallActivityWithResult(String accountId, Uri conversationUri, Uri contactUri, boolean audioOnly) {
-        Intent intent = new Intent(Intent.ACTION_CALL)
-                .setClass(requireContext(), CallActivity.class)
-                .putExtras(ConversationPath.toBundle(accountId, conversationUri))
-                .putExtra(Intent.EXTRA_PHONE_NUMBER, contactUri.getUri())
-                .putExtra(CallFragment.KEY_AUDIO_ONLY, audioOnly);
-        startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
-    }
-
-    private void setupActionbar(Conversation conversation) {
-        if (!isVisible()) {
-            return;
-        }
-        Activity activity = requireActivity();
-        String displayName = conversation.getTitle();
-        String identity = conversation.getUriTitle();
-
-        Toolbar toolbar = activity.findViewById(R.id.main_toolbar);
-        TextView title = toolbar.findViewById(R.id.contact_title);
-        TextView subtitle = toolbar.findViewById(R.id.contact_subtitle);
-        ImageView logo = toolbar.findViewById(R.id.contact_image);
-
-        logo.setImageDrawable(mConversationAvatar);
-        logo.setVisibility(View.VISIBLE);
-        title.setText(displayName);
-        title.setTextSize(15);
-        title.setTypeface(null, Typeface.NORMAL);
-
-        if (identity != null && !identity.equals(displayName)) {
-            subtitle.setText(identity);
-            subtitle.setVisibility(View.VISIBLE);
-            /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
-            params.addRule(RelativeLayout.ALIGN_TOP, R.id.contact_image);
-            title.setLayoutParams(params);*/
-        } else {
-            subtitle.setText("");
-            subtitle.setVisibility(View.GONE);
-
-            /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
-            params.removeRule(RelativeLayout.ALIGN_TOP);
-            params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
-            title.setLayoutParams(params);*/
-        }
-    }
-
-    public void blockContactRequest() {
-        presenter.onBlockIncomingContactRequest();
-    }
-
-    public void refuseContactRequest() {
-        presenter.onRefuseIncomingContactRequest();
-    }
-
-    public void acceptContactRequest() {
-        presenter.onAcceptIncomingContactRequest();
-    }
-
-    public void addContact() {
-        presenter.onAddContact();
-    }
-
-    @Override
-    public void onPrepareOptionsMenu(@NonNull Menu menu) {
-        super.onPrepareOptionsMenu(menu);
-        boolean visible = binding.cvMessageInput.getVisibility() == View.VISIBLE;
-        if (mAudioCallBtn != null)
-            mAudioCallBtn.setVisible(visible);
-        if (mVideoCallBtn != null)
-            mVideoCallBtn.setVisible(visible);
-    }
-
-    @Override
-    public void switchToUnknownView(String contactDisplayName) {
-        binding.cvMessageInput.setVisibility(View.GONE);
-        binding.unknownContactPrompt.setVisibility(View.VISIBLE);
-        binding.trustRequestPrompt.setVisibility(View.GONE);
-        binding.tvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted), contactDisplayName));
-        binding.trustRequestMessageLayout.setVisibility(View.VISIBLE);
-        currentBottomView = binding.unknownContactPrompt;
-        requireActivity().invalidateOptionsMenu();
-        updateListPadding();
-    }
-
-    @Override
-    public void switchToIncomingTrustRequestView(String contactDisplayName) {
-        binding.cvMessageInput.setVisibility(View.GONE);
-        binding.unknownContactPrompt.setVisibility(View.GONE);
-        binding.trustRequestPrompt.setVisibility(View.VISIBLE);
-        binding.tvTrustRequestMessage.setText(String.format(getString(R.string.message_contact_not_trusted_yet), contactDisplayName));
-        binding.trustRequestMessageLayout.setVisibility(View.VISIBLE);
-        currentBottomView = binding.trustRequestPrompt;
-        requireActivity().invalidateOptionsMenu();
-        updateListPadding();
-    }
-
-    @Override
-    public void switchToConversationView() {
-        binding.cvMessageInput.setVisibility(View.VISIBLE);
-        binding.unknownContactPrompt.setVisibility(View.GONE);
-        binding.trustRequestPrompt.setVisibility(View.GONE);
-        binding.trustRequestMessageLayout.setVisibility(View.GONE);
-        currentBottomView = binding.cvMessageInput;
-        requireActivity().invalidateOptionsMenu();
-        updateListPadding();
-    }
-
-    @Override
-    public void positiveMediaButtonClicked() {
-        presenter.clickOnGoingPane();
-    }
-
-    @Override
-    public void negativeMediaButtonClicked() {
-        presenter.clickOnGoingPane();
-    }
-
-    @Override
-    public void toggleMediaButtonClicked() {
-        presenter.clickOnGoingPane();
-    }
-
-    private void setLoading(boolean isLoading) {
-        if (binding == null)
-            return;
-        if (isLoading) {
-            binding.btnTakePicture.setVisibility(View.GONE);
-            binding.pbDataTransfer.setVisibility(View.VISIBLE);
-        } else {
-            binding.btnTakePicture.setVisibility(View.VISIBLE);
-            binding.pbDataTransfer.setVisibility(View.GONE);
-        }
-    }
-
-    public void handleShareIntent(Intent intent) {
-        Log.w(TAG, "handleShareIntent " + intent);
-
-        String action = intent.getAction();
-        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
-            String type = intent.getType();
-            if (type == null) {
-                Log.w(TAG, "Can't share with no type");
-                return;
-            }
-            if (type.startsWith("text/plain")) {
-                binding.msgInputTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
-            } else {
-                android.net.Uri uri = intent.getData();
-                ClipData clip = intent.getClipData();
-                if (uri == null && clip != null && clip.getItemCount() > 0)
-                    uri = clip.getItemAt(0).getUri();
-                if (uri == null)
-                    return;
-                startFileSend(AndroidFileUtils.getCacheFile(requireContext(), uri).flatMapCompletable(this::sendFile));
-            }
-        } else if (Intent.ACTION_VIEW.equals(action)) {
-            ConversationPath path = ConversationPath.fromIntent(intent);
-            if (path != null && intent.getBooleanExtra(EXTRA_SHOW_MAP, false)) {
-                shareLocation();
-            }
-        }
-    }
-
-    /**
-     * Creates an intent using Android Storage Access Framework
-     * This intent is then received by applications that can handle it like
-     * Downloads or Google drive
-     * @param file DataTransfer of the file that is going to be stored
-     * @param currentFileAbsolutePath absolute path of the file we want to save
-     */
-    public void startSaveFile(DataTransfer file, String currentFileAbsolutePath){
-        //Get the current file absolute path and store it
-        mCurrentFileAbsolutePath = currentFileAbsolutePath;
-
-        try {
-            //Use Android Storage File Access to download the file
-            Intent downloadFileIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-            downloadFileIntent.setType(AndroidFileUtils.getMimeTypeFromExtension(file.getExtension()));
-            downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
-            downloadFileIntent.putExtra(Intent.EXTRA_TITLE,file.getDisplayName());
-
-            startActivityForResult(downloadFileIntent, ConversationFragment.REQUEST_CODE_SAVE_FILE);
-        } catch (Exception e) {
-            Log.i(TAG, "No app detected for saving files.");
-            File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
-            if (!directory.exists()) {
-                directory.mkdirs();
-            }
-            writeToFile(android.net.Uri.fromFile(new File(directory, file.getDisplayName())));
-        }
-    }
-
-    @Override
-    public void displayNetworkErrorPanel() {
-        if (binding != null) {
-            binding.errorMsgPane.setVisibility(View.VISIBLE);
-            binding.errorMsgPane.setOnClickListener(null);
-            binding.errorMsgPane.setText(R.string.error_no_network);
-        }
-    }
-
-    @Override
-    public void displayAccountOfflineErrorPanel() {
-        if (binding != null) {
-            binding.errorMsgPane.setVisibility(View.VISIBLE);
-            binding.errorMsgPane.setOnClickListener(null);
-            binding.errorMsgPane.setText(R.string.error_account_offline);
-            for ( int idx = 0 ; idx < binding.btnContainer.getChildCount() ; idx++) {
-                binding.btnContainer.getChildAt(idx).setEnabled(false);
-            }
-        }
-    }
-
-    @Override
-    public void setReadIndicatorStatus(boolean show) {
-        if (mAdapter != null) {
-            mAdapter.setReadIndicatorStatus(show);
-        }
-    }
-
-    @Override
-    public void updateLastRead(String last) {
-        Log.w(TAG, "Updated last read " + mLastRead);
-        mLastRead = last;
-        if (mPreferences != null)
-            mPreferences.edit().putString(KEY_PREFERENCE_CONVERSATION_LAST_READ, last).apply();
-    }
-
-    @Override
-    public void hideErrorPanel() {
-        if (binding != null) {
-            binding.errorMsgPane.setVisibility(View.GONE);
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt
new file mode 100644
index 000000000..8da6e2e92
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt
@@ -0,0 +1,1190 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.fragments
+
+import android.Manifest
+import android.animation.LayoutTransition
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.ActivityOptions
+import android.content.*
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.content.pm.PackageManager
+import android.graphics.Typeface
+import android.hardware.Camera
+import android.net.Uri
+import android.os.Bundle
+import android.os.Environment
+import android.os.IBinder
+import android.provider.MediaStore
+import android.text.Editable
+import android.text.TextUtils
+import android.text.TextWatcher
+import android.util.Log
+import android.view.*
+import android.view.inputmethod.EditorInfo
+import android.widget.*
+import androidx.appcompat.view.menu.MenuBuilder
+import androidx.appcompat.view.menu.MenuPopupHelper
+import androidx.appcompat.widget.PopupMenu
+import androidx.appcompat.widget.Toolbar
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.inputmethod.InputContentInfoCompat
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.adapters.ConversationAdapter
+import cx.ring.application.JamiApplication
+import cx.ring.client.CallActivity
+import cx.ring.client.ContactDetailsActivity
+import cx.ring.client.ConversationActivity
+import cx.ring.client.HomeActivity
+import cx.ring.databinding.FragConversationBinding
+import cx.ring.interfaces.Colorable
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.service.DRingService
+import cx.ring.service.LocationSharingService
+import cx.ring.service.LocationSharingService.LocalBinder
+import cx.ring.services.NotificationServiceImpl
+import cx.ring.services.SharedPreferencesServiceImpl.Companion.getConversationPreferences
+import cx.ring.utils.ActionHelper
+import cx.ring.utils.AndroidFileUtils.copyFileToUri
+import cx.ring.utils.AndroidFileUtils.createAudioFile
+import cx.ring.utils.AndroidFileUtils.createImageFile
+import cx.ring.utils.AndroidFileUtils.createVideoFile
+import cx.ring.utils.AndroidFileUtils.getCacheFile
+import cx.ring.utils.AndroidFileUtils.getMimeTypeFromExtension
+import cx.ring.utils.AndroidFileUtils.getSpaceLeft
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ContentUriHandler.getUriForFile
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.DeviceUtils.isTablet
+import cx.ring.utils.MediaButtonsHelper.MediaButtonsHelperCallback
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.conversation.ConversationPresenter
+import net.jami.conversation.ConversationView
+import net.jami.daemon.JamiService
+import net.jami.model.*
+import net.jami.model.Account.ComposingStatus
+import net.jami.services.NotificationService
+import java.io.File
+import java.util.*
+
+@AndroidEntryPoint
+class ConversationFragment : BaseSupportFragment<ConversationPresenter, ConversationView>(),
+    MediaButtonsHelperCallback, ConversationView, OnSharedPreferenceChangeListener {
+    private var locationServiceConnection: ServiceConnection? = null
+    private var binding: FragConversationBinding? = null
+    private var mAudioCallBtn: MenuItem? = null
+    private var mVideoCallBtn: MenuItem? = null
+    private var currentBottomView: View? = null
+    private var mAdapter: ConversationAdapter? = null
+    private var marginPx = 0
+    private var marginPxTotal = 0
+    private val animation = ValueAnimator()
+    private var mPreferences: SharedPreferences? = null
+    private var mCurrentPhoto: File? = null
+    private var mCurrentFileAbsolutePath: String? = null
+    private val mCompositeDisposable = CompositeDisposable()
+    private var mSelectedPosition = 0
+    private var mIsBubble = false
+    private var mConversationAvatar: AvatarDrawable? = null
+    private val mParticipantAvatars: MutableMap<String, AvatarDrawable> = HashMap()
+    private val mSmallParticipantAvatars: MutableMap<String, AvatarDrawable> = HashMap()
+    private var mapWidth = 0
+    private var mapHeight = 0
+    private var mLastRead: String? = null
+    private var loading = true
+
+    fun getConversationAvatar(uri: String): AvatarDrawable? {
+        return mParticipantAvatars[uri]
+    }
+
+    fun getSmallConversationAvatar(uri: String): AvatarDrawable? {
+        synchronized(mSmallParticipantAvatars) { return mSmallParticipantAvatars[uri] }
+    }
+
+    override fun refreshView(conversation: List<Interaction>) {
+        if (binding != null) binding!!.pbLoading.visibility = View.GONE
+        mAdapter?.let { adapter ->
+            adapter.updateDataset(conversation)
+            loading = false
+        }
+        requireActivity().invalidateOptionsMenu()
+    }
+
+    override fun scrollToEnd() {
+        if (mAdapter!!.itemCount > 0) {
+            binding!!.histList.scrollToPosition(mAdapter!!.itemCount - 1)
+        }
+    }
+
+    private fun updateListPadding() {
+        if (currentBottomView != null && currentBottomView!!.height != 0) {
+            val bottomViewHeight = if (currentBottomView != null) currentBottomView!!.height else 0
+            setBottomPadding(binding!!.histList, bottomViewHeight + marginPxTotal)
+            val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
+            params.bottomMargin = bottomViewHeight + marginPxTotal
+            binding!!.mapCard.layoutParams = params
+        }
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        val res = resources
+        marginPx = res.getDimensionPixelSize(R.dimen.conversation_message_input_margin)
+        mapWidth = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_width)
+        mapHeight = res.getDimensionPixelSize(R.dimen.location_sharing_minmap_height)
+        marginPxTotal = marginPx
+        return FragConversationBinding.inflate(inflater, container, false).let { binding ->
+            this@ConversationFragment.binding = binding
+            binding.presenter = this@ConversationFragment
+            animation.duration = 150
+            animation.addUpdateListener { valueAnimator: ValueAnimator -> setBottomPadding(binding.histList, valueAnimator.animatedValue as Int) }
+
+            ViewCompat.setOnApplyWindowInsetsListener(binding.histList) { _, insets: WindowInsetsCompat ->
+                marginPxTotal = marginPx + insets.systemWindowInsetBottom
+                updateListPadding()
+                insets.consumeSystemWindowInsets()
+                insets
+            }
+            val layout: View = binding.conversationLayout
+
+            // remove action bar height for tablet layout
+            if (isTablet(layout.context)) {
+                layout.setPadding(layout.paddingLeft, 0, layout.paddingRight, layout.paddingBottom)
+            }
+            val paddingTop = layout.paddingTop
+            ViewCompat.setOnApplyWindowInsetsListener(layout) { v: View, insets: WindowInsetsCompat ->
+                v.setPadding(v.paddingLeft, paddingTop + insets.systemWindowInsetTop, v.paddingRight, v.paddingBottom)
+                insets.consumeSystemWindowInsets()
+                insets
+            }
+            binding.ongoingcallPane.visibility = View.GONE
+            binding.msgInputTxt.setMediaListener { contentInfo: InputContentInfoCompat ->
+                startFileSend(
+                    getCacheFile(requireContext(), contentInfo.contentUri)
+                        .flatMapCompletable { file: File -> sendFile(file) }
+                        .doFinally { contentInfo.releasePermission() })
+            }
+            binding.msgInputTxt.setOnEditorActionListener { _, actionId: Int, _ -> actionSendMsgText(actionId) }
+            binding.msgInputTxt.onFocusChangeListener = View.OnFocusChangeListener { view: View, hasFocus: Boolean ->
+                if (hasFocus) {
+                    val fragment = childFragmentManager.findFragmentById(R.id.mapLayout)
+                    if (fragment != null) {
+                        (fragment as LocationSharingFragment).hideControls()
+                    }
+                }
+            }
+            binding.msgInputTxt.addTextChangedListener(object : TextWatcher {
+                override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+                override fun afterTextChanged(s: Editable) {
+                    val message = s.toString()
+                    val hasMessage = !TextUtils.isEmpty(message)
+                    presenter.onComposingChanged(hasMessage)
+                    if (hasMessage) {
+                        binding.msgSend.visibility = View.VISIBLE
+                        binding.emojiSend.visibility = View.GONE
+                    } else {
+                        binding.msgSend.visibility = View.GONE
+                        binding.emojiSend.visibility = View.VISIBLE
+                    }
+                    mPreferences?.let { preferences ->
+                        if (hasMessage)
+                            preferences.edit().putString(KEY_PREFERENCE_PENDING_MESSAGE, message).apply()
+                        else
+                            preferences.edit().remove(KEY_PREFERENCE_PENDING_MESSAGE).apply()
+                    }
+                }
+            })
+            setHasOptionsMenu(true)
+            binding.root
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        binding?.let { binding ->
+            mPreferences?.let { preferences ->
+                val pendingMessage = preferences.getString(KEY_PREFERENCE_PENDING_MESSAGE, null)
+                if (pendingMessage != null && pendingMessage.isNotEmpty()) {
+                    binding.msgInputTxt.setText(pendingMessage)
+                    binding.msgSend.visibility = View.VISIBLE
+                    binding.emojiSend.visibility = View.GONE
+                }
+            }
+            binding.msgInputTxt.addOnLayoutChangeListener { _, _, _, _, _, oldLeft, oldTop, oldRight, oldBottom ->
+                if (oldBottom == 0 && oldTop == 0) {
+                    updateListPadding()
+                } else {
+                    if (animation.isStarted) animation.cancel()
+                    animation.setIntValues(
+                        binding.histList.paddingBottom,
+                        (if (currentBottomView == null) 0 else currentBottomView!!.height) + marginPxTotal
+                    )
+                    animation.start()
+                }
+            }
+            binding.histList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+                // The minimum amount of items to have below current scroll position
+                // before loading more.
+                val visibleThreshold = 3
+                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {}
+                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+                    val layoutManager = recyclerView.layoutManager as LinearLayoutManager?
+                    if (!loading && layoutManager!!.findFirstVisibleItemPosition() < visibleThreshold) {
+                        loading = true
+                        presenter.loadMore()
+                    }
+                }
+            })
+            val animator = binding.histList.itemAnimator as DefaultItemAnimator?
+            animator?.supportsChangeAnimations = false
+            binding.histList.adapter = mAdapter
+        }
+    }
+
+    override fun setConversationColor(color: Int) {
+        val activity = activity as Colorable?
+        activity?.setColor(color)
+        mAdapter?.setPrimaryColor(color)
+    }
+
+    override fun setConversationSymbol(symbol: CharSequence) {
+        binding?.emojiSend?.text = symbol
+    }
+
+    override fun onDestroyView() {
+        mPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+        animation.removeAllUpdateListeners()
+        binding?.histList?.adapter = null
+        mCompositeDisposable.clear()
+        locationServiceConnection?.let {
+            try {
+                requireContext().unbindService(it)
+            } catch (e: Exception) {
+                Log.w(TAG, "Error unbinding service: " + e.message)
+            }
+        }
+        mAdapter = null
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onContextItemSelected(item: MenuItem): Boolean {
+        return if (mAdapter!!.onContextItemSelected(item)) true
+        else super.onContextItemSelected(item)
+    }
+
+    fun updateAdapterItem() {
+        if (mSelectedPosition != -1) {
+            mAdapter!!.notifyItemChanged(mSelectedPosition)
+            mSelectedPosition = -1
+        }
+    }
+
+    fun sendMessageText() {
+        val message = binding!!.msgInputTxt.text.toString()
+        clearMsgEdit()
+        presenter.sendTextMessage(message)
+    }
+
+    fun sendEmoji() {
+        presenter.sendTextMessage(binding!!.emojiSend.text.toString())
+    }
+
+    @SuppressLint("RestrictedApi")
+    fun expandMenu(v: View) {
+        val context = requireContext()
+        val popup = PopupMenu(context, v)
+        popup.inflate(R.menu.conversation_share_actions)
+        popup.setOnMenuItemClickListener { item: MenuItem ->
+            when (item.itemId) {
+                R.id.conv_send_audio -> sendAudioMessage()
+                R.id.conv_send_video -> sendVideoMessage()
+                R.id.conv_send_file -> presenter.selectFile()
+                R.id.conv_share_location -> shareLocation()
+                R.id.chat_plugins -> presenter.showPluginListHandlers()
+            }
+            false
+        }
+        popup.menu.findItem(R.id.chat_plugins).isVisible = JamiService.getPluginsEnabled() && !JamiService.getChatHandlers().isEmpty()
+        val menuHelper = MenuPopupHelper(context, (popup.menu as MenuBuilder), v)
+        menuHelper.setForceShowIcon(true)
+        menuHelper.show()
+    }
+
+    override fun showPluginListHandlers(accountId: String, contactId: String) {
+        Log.w(TAG, "show Plugin Chat Handlers List")
+        val fragment = PluginHandlersListFragment.newInstance(accountId, contactId)
+        childFragmentManager.beginTransaction()
+            .add(R.id.pluginListHandlers, fragment, PluginHandlersListFragment.TAG)
+            .commit()
+        binding?.let { binding ->
+            val params = binding.mapCard.layoutParams as RelativeLayout.LayoutParams
+            if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
+                params.width = ViewGroup.LayoutParams.MATCH_PARENT
+                params.height = ViewGroup.LayoutParams.MATCH_PARENT
+                binding.mapCard.layoutParams = params
+            }
+            binding.mapCard.visibility = View.VISIBLE
+        }
+    }
+
+    fun hidePluginListHandlers() {
+        if (binding!!.mapCard.visibility != View.GONE) {
+            binding!!.mapCard.visibility = View.GONE
+            val fragmentManager = childFragmentManager
+            val fragment = fragmentManager.findFragmentById(R.id.pluginListHandlers)
+            if (fragment != null) {
+                fragmentManager.beginTransaction()
+                    .remove(fragment)
+                    .commit()
+            }
+        }
+        val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
+        if (params.width != mapWidth) {
+            params.width = mapWidth
+            params.height = mapHeight
+            binding!!.mapCard.layoutParams = params
+        }
+    }
+
+    private fun shareLocation() {
+        presenter.shareLocation()
+    }
+
+    fun closeLocationSharing(isSharing: Boolean) {
+        val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
+        if (params.width != mapWidth) {
+            params.width = mapWidth
+            params.height = mapHeight
+            binding!!.mapCard.layoutParams = params
+        }
+        if (!isSharing) hideMap()
+    }
+
+    fun openLocationSharing() {
+        binding!!.conversationLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
+        val params = binding!!.mapCard.layoutParams as RelativeLayout.LayoutParams
+        if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
+            params.width = ViewGroup.LayoutParams.MATCH_PARENT
+            params.height = ViewGroup.LayoutParams.MATCH_PARENT
+            binding!!.mapCard.layoutParams = params
+        }
+    }
+
+    override fun startShareLocation(accountId: String, conversationId: String) {
+        showMap(accountId, conversationId, true)
+    }
+
+    /**
+     * Used to update with the past adapter position when a long click was registered
+     */
+    fun updatePosition(position: Int) {
+        mSelectedPosition = position
+    }
+
+    override fun showMap(accountId: String, contactId: String, open: Boolean) {
+        if (binding!!.mapCard.visibility == View.GONE) {
+            Log.w(TAG, "showMap $accountId $contactId")
+            val fragmentManager = childFragmentManager
+            val fragment = LocationSharingFragment.newInstance(accountId, contactId, open)
+            fragmentManager.beginTransaction()
+                .add(R.id.mapLayout, fragment, "map")
+                .commit()
+            binding!!.mapCard.visibility = View.VISIBLE
+        }
+        if (open) {
+            val fragment = childFragmentManager.findFragmentById(R.id.mapLayout)
+            if (fragment != null) {
+                (fragment as LocationSharingFragment).showControls()
+            }
+        }
+    }
+
+    override fun hideMap() {
+        if (binding!!.mapCard.visibility != View.GONE) {
+            binding!!.mapCard.visibility = View.GONE
+            val fragmentManager = childFragmentManager
+            val fragment = fragmentManager.findFragmentById(R.id.mapLayout)
+            if (fragment != null) {
+                fragmentManager.beginTransaction()
+                    .remove(fragment)
+                    .commit()
+            }
+        }
+    }
+
+    private fun sendAudioMessage() {
+        if (!presenter.deviceRuntimeService.hasAudioPermission()) {
+            requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), REQUEST_CODE_CAPTURE_AUDIO)
+        } else {
+            try {
+                val ctx = requireContext()
+                val intent = Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+                mCurrentPhoto = createAudioFile(ctx)
+                startActivityForResult(intent, REQUEST_CODE_CAPTURE_AUDIO)
+            } catch (ex: Exception) {
+                Log.e(TAG, "sendAudioMessage: error", ex)
+                Toast.makeText(activity, "Can't find audio recorder app", Toast.LENGTH_SHORT).show()
+            }
+        }
+    }
+
+    private fun sendVideoMessage() {
+        if (!presenter.deviceRuntimeService.hasVideoPermission()) {
+            requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_CAPTURE_VIDEO)
+        } else {
+            try {
+                val context = requireContext()
+                val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE).apply {
+                    putExtra("android.intent.extras.CAMERA_FACING", Camera.CameraInfo.CAMERA_FACING_FRONT)
+                    putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
+                    putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
+                    putExtra(MediaStore.EXTRA_OUTPUT, getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, createVideoFile(context).apply {
+                        mCurrentPhoto = this
+                    }))
+                }
+                startActivityForResult(intent, REQUEST_CODE_CAPTURE_VIDEO)
+            } catch (ex: Exception) {
+                Log.e(TAG, "sendVideoMessage: error", ex)
+                Toast.makeText(activity, "Can't find video recorder app", Toast.LENGTH_SHORT).show()
+            }
+        }
+    }
+
+    fun takePicture() {
+        if (!presenter.deviceRuntimeService.hasVideoPermission()) {
+            requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE_TAKE_PICTURE)
+            return
+        }
+        val c = context ?: return
+        try {
+            val photoFile = createImageFile(c)
+            Log.i(TAG, "takePicture: trying to save to $photoFile")
+            val photoURI = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, photoFile)
+            val takePictureIntent =
+                Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
+                    .putExtra("android.intent.extras.CAMERA_FACING", 1)
+                    .putExtra("android.intent.extras.LENS_FACING_FRONT", 1)
+                    .putExtra("android.intent.extra.USE_FRONT_CAMERA", true)
+            mCurrentPhoto = photoFile
+            startActivityForResult(takePictureIntent, REQUEST_CODE_TAKE_PICTURE)
+        } catch (e: Exception) {
+            Toast.makeText(c, "Error taking picture: " + e.localizedMessage, Toast.LENGTH_SHORT)
+                .show()
+        }
+    }
+
+    override fun askWriteExternalStoragePermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+            JamiApplication.PERMISSIONS_REQUEST
+        )
+    }
+
+    override fun openFilePicker() {
+        val intent = Intent(Intent.ACTION_GET_CONTENT)
+        intent.addCategory(Intent.CATEGORY_OPENABLE)
+        intent.type = "*/*"
+        startActivityForResult(intent, REQUEST_CODE_FILE_PICKER)
+    }
+
+    private fun sendFile(file: File): Completable {
+        return Completable.fromAction { presenter.sendFile(file) }
+    }
+
+    private fun startFileSend(op: Completable) {
+        setLoading(true)
+        op.observeOn(AndroidSchedulers.mainThread())
+            .doFinally { setLoading(false) }
+            .subscribe({}) { e: Throwable? ->
+                Log.e(TAG, "startFileSend: not able to create cache file", e)
+                displayErrorToast(Error.INVALID_FILE)
+            }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+        Log.w(TAG, "onActivityResult: $requestCode $resultCode $resultData")
+        val uri = resultData?.data
+        if (requestCode == REQUEST_CODE_FILE_PICKER) {
+            if (resultCode == Activity.RESULT_OK && uri != null) {
+                startFileSend(
+                    getCacheFile(requireContext(), uri)
+                        .observeOn(AndroidSchedulers.mainThread())
+                        .flatMapCompletable { file: File -> sendFile(file) })
+            }
+        } else if (requestCode == REQUEST_CODE_TAKE_PICTURE || requestCode == REQUEST_CODE_CAPTURE_AUDIO || requestCode == REQUEST_CODE_CAPTURE_VIDEO) {
+            if (resultCode != Activity.RESULT_OK) {
+                mCurrentPhoto = null
+                return
+            }
+            Log.w(TAG, "onActivityResult: mCurrentPhoto " + mCurrentPhoto!!.absolutePath + " " + mCurrentPhoto!!.exists() + " " + mCurrentPhoto!!.length())
+            var file: Single<File>? = null
+            if (mCurrentPhoto == null || !mCurrentPhoto!!.exists() || mCurrentPhoto!!.length() == 0L) {
+                if (uri != null) {
+                    file = getCacheFile(requireContext(), uri)
+                }
+            } else {
+                file = Single.just(mCurrentPhoto)
+            }
+            mCurrentPhoto = null
+            if (file == null) {
+                Toast.makeText(activity, "Can't find picture", Toast.LENGTH_SHORT).show()
+                return
+            }
+            startFileSend(file.flatMapCompletable { f -> sendFile(f) })
+        } else if (requestCode == REQUEST_CODE_SAVE_FILE) {
+            if (resultCode == Activity.RESULT_OK && uri != null) {
+                writeToFile(uri)
+            }
+        }
+    }
+
+    private fun writeToFile(data: Uri) {
+        val path = mCurrentFileAbsolutePath ?: return
+        val cr = context?.contentResolver ?: return
+        val input = File(path)
+        mCompositeDisposable.add(
+            copyFileToUri(cr, input, data)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ Toast.makeText(context, R.string.file_saved_successfully, Toast.LENGTH_SHORT).show() })
+                { Toast.makeText(context, R.string.generic_error, Toast.LENGTH_SHORT).show() })
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        var i = 0
+        val n = permissions.size
+        while (i < n) {
+            val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+            when (permissions[i]) {
+                Manifest.permission.CAMERA -> {
+                    presenter.cameraPermissionChanged(granted)
+                    if (granted) {
+                        if (requestCode == REQUEST_CODE_CAPTURE_VIDEO) {
+                            sendVideoMessage()
+                        } else if (requestCode == REQUEST_CODE_TAKE_PICTURE) {
+                            takePicture()
+                        }
+                    }
+                    return
+                }
+                Manifest.permission.RECORD_AUDIO -> {
+                    if (granted) {
+                        if (requestCode == REQUEST_CODE_CAPTURE_AUDIO) {
+                            sendAudioMessage()
+                        }
+                    }
+                    return
+                }
+                else -> {
+                }
+            }
+            i++
+        }
+    }
+
+    override fun addElement(element: Interaction) {
+        if (mLastRead != null && mLastRead == element.messageId) element.read()
+        if (mAdapter!!.add(element)) scrollToEnd()
+        loading = false
+    }
+
+    override fun updateElement(element: Interaction) {
+        mAdapter!!.update(element)
+    }
+
+    override fun removeElement(element: Interaction) {
+        mAdapter!!.remove(element)
+    }
+
+    override fun setComposingStatus(composingStatus: ComposingStatus) {
+        mAdapter!!.setComposingStatus(composingStatus)
+        if (composingStatus == ComposingStatus.Active) scrollToEnd()
+    }
+
+    override fun setLastDisplayed(interaction: Interaction) {
+        mAdapter!!.setLastDisplayed(interaction)
+    }
+
+    override fun acceptFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) {
+        if (transfer.messageId == null && transfer.fileId == null)
+            return
+        val cacheDir = requireContext().cacheDir
+        val spaceLeft = getSpaceLeft(cacheDir.toString())
+        if (spaceLeft == -1L || transfer.totalSize > spaceLeft) {
+            presenter.noSpaceLeft()
+            return
+        }
+        requireActivity().startService(Intent(DRingService.ACTION_FILE_ACCEPT, ConversationPath.toUri(accountId, conversationUri),
+            requireContext(), DRingService::class.java)
+            .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
+            .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
+        )
+    }
+
+    override fun refuseFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) {
+        if (transfer.messageId == null && transfer.fileId == null)
+            return
+        requireActivity().startService(Intent(DRingService.ACTION_FILE_CANCEL, ConversationPath.toUri(accountId, conversationUri),
+            requireContext(), DRingService::class.java)
+            .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
+            .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
+        )
+    }
+
+    override fun shareFile(path: File, displayName: String) {
+        val c = context ?: return
+        var fileUri: Uri? = null
+        try {
+            fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName)
+        } catch (e: IllegalArgumentException) {
+            Log.e("File Selector", "The selected file can't be shared: " + path.name)
+        }
+        if (fileUri != null) {
+            val sendIntent = Intent()
+            sendIntent.action = Intent.ACTION_SEND
+            sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            val type =
+                c.contentResolver.getType(fileUri.buildUpon().appendPath(displayName).build())
+            sendIntent.setDataAndType(fileUri, type)
+            sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
+            startActivity(Intent.createChooser(sendIntent, null))
+        }
+    }
+
+    override fun openFile(path: File, displayName: String) {
+        val c = context ?: return
+        var fileUri: Uri? = null
+        try {
+            fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName)
+        } catch (e: IllegalArgumentException) {
+            Log.e(TAG, "The selected file can't be shared: " + path.name)
+        }
+        if (fileUri != null) {
+            val sendIntent = Intent()
+            sendIntent.action = Intent.ACTION_VIEW
+            sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            val type =
+                c.contentResolver.getType(fileUri.buildUpon().appendPath(displayName).build())
+            sendIntent.setDataAndType(fileUri, type)
+            sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
+            //startActivity(Intent.createChooser(sendIntent, null));
+            try {
+                startActivity(sendIntent)
+            } catch (e: ActivityNotFoundException) {
+                Snackbar.make(requireView(), R.string.conversation_open_file_error, Snackbar.LENGTH_LONG)
+                    .show()
+                Log.e("File Loader", "File of unknown type, could not open: " + path.name)
+            }
+        }
+    }
+
+    fun actionSendMsgText(actionId: Int): Boolean {
+        when (actionId) {
+            EditorInfo.IME_ACTION_SEND -> {
+                sendMessageText()
+                return true
+            }
+        }
+        return false
+    }
+
+    fun onClick() {
+        presenter.clickOnGoingPane()
+    }
+
+    override fun onStart() {
+        super.onStart()
+        presenter.resume(mIsBubble)
+    }
+
+    override fun onStop() {
+        super.onStop()
+        presenter.pause()
+    }
+
+    override fun onPause() {
+        super.onPause()
+        //presenter.pause();
+    }
+
+    override fun onResume() {
+        super.onResume()
+        //presenter.resume(mIsBubble);
+    }
+
+    override fun onDestroy() {
+        mCompositeDisposable.dispose()
+        super.onDestroy()
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        if (!isVisible) {
+            return
+        }
+        inflater.inflate(R.menu.conversation_actions, menu)
+        mAudioCallBtn = menu.findItem(R.id.conv_action_audiocall)
+        mVideoCallBtn = menu.findItem(R.id.conv_action_videocall)
+    }
+
+    fun openContact() {
+        presenter.openContact()
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        val itemId = item.itemId
+        if (itemId == android.R.id.home) {
+            startActivity(Intent(activity, HomeActivity::class.java))
+            return true
+        } else if (itemId == R.id.conv_action_audiocall) {
+            presenter.goToCall(true)
+            return true
+        } else if (itemId == R.id.conv_action_videocall) {
+            presenter.goToCall(false)
+            return true
+        } else if (itemId == R.id.conv_contact_details) {
+            presenter.openContact()
+            return true
+        }
+        return super.onOptionsItemSelected(item)
+    }
+
+    override fun initPresenter(presenter: ConversationPresenter) {
+        val path = ConversationPath.fromBundle(arguments)
+        mIsBubble = requireArguments().getBoolean(NotificationServiceImpl.EXTRA_BUBBLE)
+        Log.w(TAG, "initPresenter $path")
+        if (path == null) return
+        val uri = path.conversationUri
+        mAdapter = ConversationAdapter(this, presenter)
+        presenter.init(uri, path.accountId)
+        try {
+            mPreferences = getConversationPreferences(requireContext(), path.accountId, uri).also { preferences ->
+                preferences.registerOnSharedPreferenceChangeListener(this)
+                presenter.setConversationColor(preferences.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, resources.getColor(R.color.color_primary_light)))
+                presenter.setConversationSymbol(preferences.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, resources.getText(R.string.conversation_default_emoji).toString())!!)
+                mLastRead = preferences.getString(KEY_PREFERENCE_CONVERSATION_LAST_READ, null)
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Can't load conversation preferences")
+        }
+        var connection = locationServiceConnection
+        if (connection == null) {
+            connection = object : ServiceConnection {
+                override fun onServiceConnected(name: ComponentName, service: IBinder) {
+                    Log.w(TAG, "onServiceConnected")
+                    val binder = service as LocalBinder
+                    val locationService = binder.service
+                    //val path = ConversationPath(presenter.path)
+                    if (locationService.isSharing(path)) {
+                        showMap(path.accountId, uri.uri, false)
+                    }
+                    /*try {
+                        requireContext().unbindService(locationServiceConnection!!)
+                    } catch (e: Exception) {
+                        Log.w(TAG, "Error unbinding service", e)
+                    }*/
+                }
+
+                override fun onServiceDisconnected(name: ComponentName) {
+                    Log.w(TAG, "onServiceDisconnected")
+                    locationServiceConnection = null
+                }
+            }
+            locationServiceConnection = connection
+            Log.w(TAG, "bindService")
+            requireContext().bindService(Intent(requireContext(), LocationSharingService::class.java), connection, 0)
+        }
+    }
+
+    override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
+        when (key) {
+            KEY_PREFERENCE_CONVERSATION_COLOR -> presenter.setConversationColor(
+                prefs.getInt(KEY_PREFERENCE_CONVERSATION_COLOR, resources.getColor(R.color.color_primary_light)))
+            KEY_PREFERENCE_CONVERSATION_SYMBOL -> presenter.setConversationSymbol(
+                prefs.getString(KEY_PREFERENCE_CONVERSATION_SYMBOL, resources.getText(R.string.conversation_default_emoji).toString())!!)
+        }
+    }
+
+    override fun updateContact(contact: Contact) {
+        val contactKey = contact.primaryNumber
+        val a = mSmallParticipantAvatars[contactKey]
+        if (a != null) {
+            a.update(contact)
+            mParticipantAvatars[contactKey]!!.update(contact)
+            mAdapter?.setPhoto()
+        } else {
+            mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), contact, true)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe { avatar ->
+                    mParticipantAvatars[contactKey] = avatar as AvatarDrawable
+                    mSmallParticipantAvatars[contactKey] = AvatarDrawable.Builder()
+                        .withContact(contact)
+                        .withCircleCrop(true)
+                        .withPresence(false)
+                        .build(requireContext())
+                    mAdapter?.setPhoto()
+                })
+        }
+    }
+
+    override fun displayContact(conversation: Conversation) {
+        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true)
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { d ->
+                mConversationAvatar = d as AvatarDrawable
+                mParticipantAvatars[conversation.uri.rawRingId] = AvatarDrawable(d)
+                setupActionbar(conversation)
+            })
+    }
+
+    override fun displayOnGoingCallPane(display: Boolean) {
+        binding!!.ongoingcallPane.visibility = if (display) View.VISIBLE else View.GONE
+    }
+
+    override fun displayNumberSpinner(conversation: Conversation, number: net.jami.model.Uri) {
+        binding!!.numberSelector.visibility = View.VISIBLE
+        //binding.numberSelector.setAdapter(new NumberAdapter(getActivity(), conversation.getContact(), false));
+        binding!!.numberSelector.setSelection(getIndex(binding!!.numberSelector, number))
+    }
+
+    override fun hideNumberSpinner() {
+        binding!!.numberSelector.visibility = View.GONE
+    }
+
+    override fun clearMsgEdit() {
+        binding!!.msgInputTxt.setText("")
+    }
+
+    override fun goToHome() {
+        if (activity is ConversationActivity) {
+            requireActivity().finish()
+        }
+    }
+
+    override fun goToAddContact(contact: Contact) {
+        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), REQ_ADD_CONTACT)
+    }
+
+    override fun goToCallActivity(conferenceId: String) {
+        startActivity(Intent(Intent.ACTION_VIEW)
+                .setClass(requireContext().applicationContext, CallActivity::class.java)
+                .putExtra(NotificationService.KEY_CALL_ID, conferenceId))
+    }
+
+    override fun goToContactActivity(accountId: String, uri: net.jami.model.Uri) {
+        val toolbar: Toolbar = requireActivity().findViewById(R.id.main_toolbar)
+        val logo = toolbar.findViewById<ImageView>(R.id.contact_image)
+        startActivity(Intent(Intent.ACTION_VIEW, ConversationPath.toUri(accountId, uri))
+                .setClass(requireContext().applicationContext, ContactDetailsActivity::class.java),
+            ActivityOptions.makeSceneTransitionAnimation(activity, logo, "conversationIcon")
+                .toBundle())
+    }
+
+    override fun goToCallActivityWithResult(
+        accountId: String,
+        conversationUri: net.jami.model.Uri,
+        contactUri: net.jami.model.Uri,
+        audioOnly: Boolean
+    ) {
+        val intent = Intent(Intent.ACTION_CALL)
+            .setClass(requireContext(), CallActivity::class.java)
+            .putExtras(ConversationPath.toBundle(accountId, conversationUri))
+            .putExtra(Intent.EXTRA_PHONE_NUMBER, contactUri.uri)
+            .putExtra(CallFragment.KEY_AUDIO_ONLY, audioOnly)
+        startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL)
+    }
+
+    private fun setupActionbar(conversation: Conversation) {
+        if (!isVisible) {
+            return
+        }
+        val activity: Activity = requireActivity()
+        val displayName = conversation.title
+        val identity = conversation.uriTitle
+        val toolbar: Toolbar = activity.findViewById(R.id.main_toolbar)
+        val title = toolbar.findViewById<TextView>(R.id.contact_title)
+        val subtitle = toolbar.findViewById<TextView>(R.id.contact_subtitle)
+        val logo = toolbar.findViewById<ImageView>(R.id.contact_image)
+        logo.setImageDrawable(mConversationAvatar)
+        logo.visibility = View.VISIBLE
+        title.text = displayName
+        title.textSize = 15f
+        title.setTypeface(null, Typeface.NORMAL)
+        if (identity != null && identity != displayName) {
+            subtitle.text = identity
+            subtitle.visibility = View.VISIBLE
+            /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
+            params.addRule(RelativeLayout.ALIGN_TOP, R.id.contact_image);
+            title.setLayoutParams(params);*/
+        } else {
+            subtitle.text = ""
+            subtitle.visibility = View.GONE
+
+            /*RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
+            params.removeRule(RelativeLayout.ALIGN_TOP);
+            params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
+            title.setLayoutParams(params);*/
+        }
+    }
+
+    fun blockContactRequest() {
+        presenter.onBlockIncomingContactRequest()
+    }
+
+    fun refuseContactRequest() {
+        presenter.onRefuseIncomingContactRequest()
+    }
+
+    fun acceptContactRequest() {
+        presenter.onAcceptIncomingContactRequest()
+    }
+
+    fun addContact() {
+        presenter.onAddContact()
+    }
+
+    override fun onPrepareOptionsMenu(menu: Menu) {
+        super.onPrepareOptionsMenu(menu)
+        val visible = binding!!.cvMessageInput.visibility == View.VISIBLE
+        if (mAudioCallBtn != null) mAudioCallBtn!!.isVisible = visible
+        if (mVideoCallBtn != null) mVideoCallBtn!!.isVisible = visible
+    }
+
+    override fun switchToUnknownView(contactDisplayName: String) {
+        binding?.apply {
+            cvMessageInput.visibility = View.GONE
+            unknownContactPrompt.visibility = View.VISIBLE
+            trustRequestPrompt.visibility = View.GONE
+            tvTrustRequestMessage.text = String.format(getString(R.string.message_contact_not_trusted), contactDisplayName)
+            trustRequestMessageLayout.visibility = View.VISIBLE
+            currentBottomView = unknownContactPrompt
+        }
+        requireActivity().invalidateOptionsMenu()
+        updateListPadding()
+    }
+
+    override fun switchToIncomingTrustRequestView(contactDisplayName: String) {
+        binding?.apply {
+            cvMessageInput.visibility = View.GONE
+            unknownContactPrompt.visibility = View.GONE
+            trustRequestPrompt.visibility = View.VISIBLE
+            tvTrustRequestMessage.text = String.format(getString(R.string.message_contact_not_trusted_yet), contactDisplayName)
+            trustRequestMessageLayout.visibility = View.VISIBLE
+            currentBottomView = trustRequestPrompt
+        }
+        requireActivity().invalidateOptionsMenu()
+        updateListPadding()
+    }
+
+    override fun switchToConversationView() {
+        binding?.apply {
+            cvMessageInput.visibility = View.VISIBLE
+            unknownContactPrompt.visibility = View.GONE
+            trustRequestPrompt.visibility = View.GONE
+            trustRequestMessageLayout.visibility = View.GONE
+            currentBottomView = cvMessageInput
+        }
+        requireActivity().invalidateOptionsMenu()
+        updateListPadding()
+    }
+
+    override fun switchToSyncingView() {
+        binding?.apply {
+            cvMessageInput.visibility = View.GONE
+            unknownContactPrompt.visibility = View.GONE
+            trustRequestPrompt.visibility = View.GONE
+            trustRequestMessageLayout.visibility = View.VISIBLE
+            tvTrustRequestMessage.text = "Syncing conversation..."
+        }
+        currentBottomView = null
+        requireActivity().invalidateOptionsMenu()
+        updateListPadding()
+    }
+    override fun switchToEndedView() {
+        binding?.apply {
+            cvMessageInput.visibility = View.GONE
+            unknownContactPrompt.visibility = View.GONE
+            trustRequestPrompt.visibility = View.GONE
+            trustRequestMessageLayout.visibility = View.VISIBLE
+            tvTrustRequestMessage.text = "Conversation ended"
+        }
+        currentBottomView = null
+        requireActivity().invalidateOptionsMenu()
+        updateListPadding()
+    }
+
+    override fun positiveMediaButtonClicked() {
+        presenter.clickOnGoingPane()
+    }
+
+    override fun negativeMediaButtonClicked() {
+        presenter.clickOnGoingPane()
+    }
+
+    override fun toggleMediaButtonClicked() {
+        presenter.clickOnGoingPane()
+    }
+
+    private fun setLoading(isLoading: Boolean) {
+        if (binding == null) return
+        if (isLoading) {
+            binding!!.btnTakePicture.visibility = View.GONE
+            binding!!.pbDataTransfer.visibility = View.VISIBLE
+        } else {
+            binding!!.btnTakePicture.visibility = View.VISIBLE
+            binding!!.pbDataTransfer.visibility = View.GONE
+        }
+    }
+
+    fun handleShareIntent(intent: Intent) {
+        Log.w(TAG, "handleShareIntent $intent")
+        val action = intent.action
+        if (Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action) {
+            val type = intent.type
+            if (type == null) {
+                Log.w(TAG, "Can't share with no type")
+                return
+            }
+            if (type.startsWith("text/plain")) {
+                binding!!.msgInputTxt.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
+            } else {
+                var uri = intent.data
+                val clip = intent.clipData
+                if (uri == null && clip != null && clip.itemCount > 0) uri = clip.getItemAt(0).uri
+                if (uri == null) return
+                startFileSend(
+                    getCacheFile(requireContext(), uri)
+                        .flatMapCompletable { file -> sendFile(file) })
+            }
+        } else if (Intent.ACTION_VIEW == action) {
+            val path = ConversationPath.fromIntent(intent)
+            if (path != null && intent.getBooleanExtra(EXTRA_SHOW_MAP, false)) {
+                shareLocation()
+            }
+        }
+    }
+
+    /**
+     * Creates an intent using Android Storage Access Framework
+     * This intent is then received by applications that can handle it like
+     * Downloads or Google drive
+     * @param file DataTransfer of the file that is going to be stored
+     * @param currentFileAbsolutePath absolute path of the file we want to save
+     */
+    override fun startSaveFile(file: DataTransfer, currentFileAbsolutePath: String) {
+        //Get the current file absolute path and store it
+        mCurrentFileAbsolutePath = currentFileAbsolutePath
+        try {
+            //Use Android Storage File Access to download the file
+            val downloadFileIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+            downloadFileIntent.type = getMimeTypeFromExtension(file.extension)
+            downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE)
+            downloadFileIntent.putExtra(Intent.EXTRA_TITLE, file.displayName)
+            startActivityForResult(downloadFileIntent, REQUEST_CODE_SAVE_FILE)
+        } catch (e: Exception) {
+            Log.i(TAG, "No app detected for saving files.")
+            val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+            if (!directory.exists()) {
+                directory.mkdirs()
+            }
+            writeToFile(Uri.fromFile(File(directory, file.displayName)))
+        }
+    }
+
+    override fun displayNetworkErrorPanel() {
+        binding?.apply {
+            errorMsgPane.visibility = View.VISIBLE
+            errorMsgPane.setOnClickListener(null)
+            errorMsgPane.setText(R.string.error_no_network)
+        }
+    }
+
+    override fun displayAccountOfflineErrorPanel() {
+        binding?.apply {
+            errorMsgPane.visibility = View.VISIBLE
+            errorMsgPane.setOnClickListener(null)
+            errorMsgPane.setText(R.string.error_account_offline)
+            for (idx in 0 until btnContainer.childCount) {
+                btnContainer.getChildAt(idx).isEnabled = false
+            }
+        }
+    }
+
+    override fun setReadIndicatorStatus(show: Boolean) {
+        mAdapter?.setReadIndicatorStatus(show)
+    }
+
+    override fun updateLastRead(last: String) {
+        Log.w(TAG, "Updated last read $mLastRead")
+        mLastRead = last
+        mPreferences?.edit()?.putString(KEY_PREFERENCE_CONVERSATION_LAST_READ, last)?.apply()
+    }
+
+    override fun hideErrorPanel() {
+        binding?.errorMsgPane?.visibility = View.GONE
+    }
+
+    companion object {
+        private val TAG = ConversationFragment::class.java.simpleName
+        const val REQ_ADD_CONTACT = 42
+        const val KEY_PREFERENCE_PENDING_MESSAGE = "pendingMessage"
+        const val KEY_PREFERENCE_CONVERSATION_COLOR = "color"
+        const val KEY_PREFERENCE_CONVERSATION_LAST_READ = "lastRead"
+        const val KEY_PREFERENCE_CONVERSATION_SYMBOL = "symbol"
+        const val EXTRA_SHOW_MAP = "showMap"
+        private const val REQUEST_CODE_FILE_PICKER = 1000
+        private const val REQUEST_PERMISSION_CAMERA = 1001
+        private const val REQUEST_CODE_TAKE_PICTURE = 1002
+        private const val REQUEST_CODE_SAVE_FILE = 1003
+        private const val REQUEST_CODE_CAPTURE_AUDIO = 1004
+        private const val REQUEST_CODE_CAPTURE_VIDEO = 1005
+        private fun getIndex(spinner: Spinner, myString: net.jami.model.Uri): Int {
+            var i = 0
+            val n = spinner.count
+            while (i < n) {
+                if ((spinner.getItemAtPosition(i) as Phone).number == myString) {
+                    return i
+                }
+                i++
+            }
+            return 0
+        }
+
+        private fun setBottomPadding(view: View, padding: Int) {
+            view.setPadding(view.paddingLeft, view.paddingTop, view.paddingRight, padding)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
index 7c4e1d188..f414197c3 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java
@@ -50,7 +50,9 @@ import net.jami.utils.Tuple;
 import cx.ring.views.EditTextIntegerPreference;
 import cx.ring.views.EditTextPreferenceDialog;
 import cx.ring.views.PasswordPreference;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class GeneralAccountFragment extends BasePreferenceFragment<GeneralAccountPresenter> implements GeneralAccountView {
 
     public static final String TAG = GeneralAccountFragment.class.getSimpleName();
@@ -152,7 +154,6 @@ public class GeneralAccountFragment extends BasePreferenceFragment<GeneralAccoun
 
     @Override
     public void onCreatePreferences(Bundle bundle, String rootKey) {
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
         super.onCreatePreferences(bundle, rootKey);
 
         Bundle args = getArguments();
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
index 32690a975..eb4b9b6ac 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java
@@ -38,17 +38,16 @@ public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> {
 
     private static final String TAG = GeneralAccountPresenter.class.getSimpleName();
 
-    protected AccountService mAccountService;
+    private final AccountService mAccountService;
+    private final HardwareService mHardwareService;
+    private final PreferencesService mPreferenceService;
 
-    protected HardwareService mHardwareService;
-
-    protected PreferencesService mPreferenceService;
-
-    private Account mAccount;
     @Inject
     @Named("UiScheduler")
     protected Scheduler mUiScheduler;
 
+    private Account mAccount;
+
     @Inject
     GeneralAccountPresenter(AccountService accountService, HardwareService hardwareService, PreferencesService preferencesService) {
         this.mAccountService = accountService;
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java
index 5e04bac96..5f167d250 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java
@@ -54,7 +54,9 @@ import cx.ring.databinding.FragLinkDeviceBinding;
 import cx.ring.mvp.BaseBottomSheetFragment;
 import cx.ring.utils.DeviceUtils;
 import cx.ring.utils.KeyboardVisibilityManager;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class LinkDeviceFragment extends BaseBottomSheetFragment<LinkDevicePresenter> implements LinkDeviceView {
 
     public static final String TAG = LinkDeviceFragment.class.getSimpleName();
@@ -77,7 +79,6 @@ public class LinkDeviceFragment extends BaseBottomSheetFragment<LinkDevicePresen
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
         mBinding = FragLinkDeviceBinding.inflate(inflater, container, false);
         return mBinding.getRoot();
     }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.java
deleted file mode 100644
index 251add4e1..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.java
+++ /dev/null
@@ -1,628 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.fragments;
-
-import android.Manifest;
-import android.animation.LayoutTransition;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.BitmapDrawable;
-import android.icu.text.MeasureFormat;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
-import android.location.Location;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import androidx.activity.OnBackPressedCallback;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Uri;
-
-import org.osmdroid.config.Configuration;
-import org.osmdroid.config.IConfigurationProvider;
-import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
-import org.osmdroid.util.BoundingBox;
-import org.osmdroid.util.GeoPoint;
-import org.osmdroid.views.CustomZoomButtonsController;
-import org.osmdroid.views.overlay.Marker;
-import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer;
-import org.osmdroid.views.overlay.mylocation.IMyLocationProvider;
-import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.views.AvatarFactory;
-import cx.ring.databinding.FragLocationSharingBinding;
-import cx.ring.service.LocationSharingService;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.TouchClickListener;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class LocationSharingFragment extends Fragment {
-    private static final String TAG = LocationSharingFragment.class.getSimpleName();
-    private static final int REQUEST_CODE_LOCATION = 47892;
-    private static final String KEY_SHOW_CONTROLS = "showControls";
-
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-    private final CompositeDisposable mServiceDisposableBag = new CompositeDisposable();
-    private Disposable mCountdownDisposable = null;
-
-    enum MapState {NONE, MINI, FULL}
-
-    @Inject
-    ConversationFacade mConversationFacade;
-
-    private ConversationPath mPath;
-    private Contact mContact;
-
-    private final Subject<Boolean> mShowControlsSubject = BehaviorSubject.create();
-    private final Subject<Boolean> mIsSharingSubject = BehaviorSubject.create();
-    private final Subject<Boolean> mIsContactSharingSubject = BehaviorSubject.create();
-    private final Observable<MapState> mShowMapSubject = Observable.combineLatest(
-            mShowControlsSubject,
-            mIsSharingSubject,
-            mIsContactSharingSubject,
-            (showControls, isSharing, isContactSharing) -> showControls
-                    ? MapState.FULL
-                    : ((isSharing || isContactSharing) ? MapState.MINI : MapState.NONE))
-            .distinctUntilChanged();
-
-    private int bubbleSize;
-
-    private MyLocationNewOverlay overlay;
-    private Marker marker;
-    private BoundingBox lastBoundingBox = null;
-    private boolean trackAll = true;
-    private Integer mStartSharingPending = null;
-
-    private FragLocationSharingBinding binding = null;
-    private LocationSharingService mService = null;
-    private boolean mBound = false;
-
-    @Nullable
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragLocationSharingBinding.inflate(inflater, container, false);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    public static LocationSharingFragment newInstance(String accountId, String conversationId, boolean showControls) {
-        LocationSharingFragment fragment = new LocationSharingFragment();
-        Bundle args = ConversationPath.toBundle(accountId, conversationId);
-        args.putBoolean(KEY_SHOW_CONTROLS, showControls);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        setRetainInstance(true);
-
-        Bundle args = getArguments();
-        if (args != null) {
-            mPath = ConversationPath.fromBundle(args);
-            mShowControlsSubject.onNext(args.getBoolean(KEY_SHOW_CONTROLS, true));
-        }
-
-        Context ctx = requireContext();
-        File osmPath = new File(ctx.getCacheDir(), "osm");
-        IConfigurationProvider configuration = Configuration.getInstance();
-        configuration.setOsmdroidBasePath(osmPath);
-        configuration.setOsmdroidTileCache(new File(osmPath, "tiles"));
-        configuration.setUserAgentValue("net.jami.android");
-        configuration.setMapViewHardwareAccelerated(true);
-        configuration.setMapViewRecyclerFriendly(false);
-        bubbleSize = ctx.getResources().getDimensionPixelSize(R.dimen.location_sharing_avatar_size);
-    }
-
-    @RequiresApi(api = Build.VERSION_CODES.N)
-    private static CharSequence formatDuration(long millis, MeasureFormat.FormatWidth width) {
-        final MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), width);
-        if (millis >= DateUtils.HOUR_IN_MILLIS) {
-            final int hours = (int) ((millis + DateUtils.HOUR_IN_MILLIS/2) / DateUtils.HOUR_IN_MILLIS);
-            return formatter.format(new Measure(hours, MeasureUnit.HOUR));
-        } else if (millis >= DateUtils.MINUTE_IN_MILLIS) {
-            final int minutes = (int) ((millis + DateUtils.MINUTE_IN_MILLIS/2) / DateUtils.MINUTE_IN_MILLIS);
-            return formatter.format(new Measure(minutes, MeasureUnit.MINUTE));
-        } else {
-            final int seconds = (int) ((millis + DateUtils.SECOND_IN_MILLIS/2) / DateUtils.SECOND_IN_MILLIS);
-            return formatter.format(new Measure(seconds, MeasureUnit.SECOND));
-        }
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            binding.locationShareTime1h.setText(formatDuration( DateUtils.HOUR_IN_MILLIS, MeasureFormat.FormatWidth.WIDE));
-            binding.locationShareTime10m.setText(formatDuration( 10 * DateUtils.MINUTE_IN_MILLIS, MeasureFormat.FormatWidth.WIDE));
-        }
-        binding.infoBtn.setOnClickListener(v -> {
-            int padding = v.getResources().getDimensionPixelSize(R.dimen.padding_large);
-            TextView textView = new TextView(v.getContext());
-            textView.setText(R.string.location_share_about_message);
-            textView.setOnClickListener(tv -> tv.getContext().startActivity(new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(getString(R.string.location_share_about_osm_copy_url)))));
-            textView.setPadding(padding, padding, padding, padding);
-            new MaterialAlertDialogBuilder(view.getContext())
-                    .setTitle(R.string.location_share_about_title)
-                    .setView(textView)
-                    .create().show();
-        });
-
-        View locateView = view.findViewById(R.id.btn_center_position);
-        locateView.setOnClickListener(v -> {
-            if (overlay != null) {
-                trackAll = true;
-                if (lastBoundingBox != null)
-                    binding.map.zoomToBoundingBox(lastBoundingBox, true);
-                else
-                    overlay.enableFollowLocation();
-            }
-        });
-        binding.locationShareTimeGroup.setOnCheckedChangeListener((group, id) -> {
-            if (id == View.NO_ID)
-                group.check(R.id.location_share_time_1h);
-        });
-        binding.locshareToolbar.setNavigationOnClickListener(v -> mShowControlsSubject.onNext(false));
-        binding.locationShareStop.setOnClickListener(v -> stopSharing());
-
-        binding.map.setTileSource(TileSourceFactory.MAPNIK);
-        binding.map.setHorizontalMapRepetitionEnabled(false);
-        binding.map.setTilesScaledToDpi(true);
-        binding.map.setMapOrientation(0, false);
-        binding.map.setMinZoomLevel(1d);
-        binding.map.setMaxZoomLevel(19.d);
-        binding.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
-        binding.map.getController().setZoom(14.0);
-        ((ViewGroup)view).getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
-    }
-
-    private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
-        @Override
-        public void handleOnBackPressed() {
-            mShowControlsSubject.onNext(false);
-        }
-    };
-
-    @Override
-    public void onAttach(@NonNull Context context) {
-        super.onAttach(context);
-        requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mShowControlsSubject.onComplete();
-        mIsSharingSubject.onComplete();
-        mIsContactSharingSubject.onComplete();
-    }
-
-    public void onResume() {
-        super.onResume();
-        binding.map.onResume();
-        if (overlay != null) {
-            try {
-                overlay.enableMyLocation();
-            } catch (Exception e) {
-                Log.w(TAG, e);
-            }
-        }
-    }
-
-    public void onPause(){
-        super.onPause();
-        binding.map.onPause();
-        if (overlay != null)
-            overlay.disableMyLocation();
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        if (requestCode == REQUEST_CODE_LOCATION) {
-            boolean granted = false;
-            for (int result : grantResults)
-                granted |= (result == PackageManager.PERMISSION_GRANTED);
-            if (granted) {
-                startService();
-            } else {
-                mIsSharingSubject.onNext(false);
-                mShowControlsSubject.onNext(false);
-            }
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mDisposableBag.add(mServiceDisposableBag);
-        mDisposableBag.add(mShowControlsSubject.subscribe(this::setShowControls));
-        mDisposableBag.add(mIsSharingSubject.subscribe(this::setIsSharing));
-        mDisposableBag.add(mShowMapSubject
-                .subscribeOn(AndroidSchedulers.mainThread())
-                .subscribe(state -> {
-                    Fragment p = getParentFragment();
-                    if (p instanceof ConversationFragment) {
-                        ConversationFragment parent = (ConversationFragment) p;
-                        if (state == MapState.FULL)
-                            parent.openLocationSharing();
-                        else
-                            parent.closeLocationSharing(state == MapState.MINI);
-                    }
-                }));
-        mDisposableBag.add(mIsContactSharingSubject
-                .distinctUntilChanged()
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(sharing -> {
-                    if (sharing) {
-                        String sharingString = getString(R.string.location_share_contact, mContact.getDisplayName());
-                        binding.locshareToolbar.setSubtitle(sharingString);
-                        binding.locshareSnipetTxt.setVisibility(View.VISIBLE);
-                        binding.locshareSnipetTxtShadow.setVisibility(View.VISIBLE);
-                        binding.locshareSnipetTxt.setText(sharingString);
-                    } else {
-                        binding.locshareToolbar.setSubtitle(null);
-                        binding.locshareSnipetTxt.setVisibility(View.GONE);
-                        binding.locshareSnipetTxtShadow.setVisibility(View.GONE);
-                        binding.locshareSnipetTxt.setText(null);
-                    }
-                }));
-
-        final Uri contactUri = mPath.getConversationUri();
-
-        mDisposableBag.add(mConversationFacade
-                .getAccountSubject(mPath.getAccountId())
-                .flatMapObservable(account -> account.getLocationsUpdates()
-                        .map(locations -> {
-                            List<Observable<LocationViewModel>> r = new ArrayList<>(locations.size());
-                            boolean isContactSharing = false;
-                            for (Map.Entry<Contact, Observable<Account.ContactLocation>> l : locations.entrySet()) {
-                                if (l.getKey() == account.getContactFromCache(contactUri)) {
-                                    isContactSharing = true;
-                                    mContact = l.getKey();
-                                }
-                                r.add(l.getValue().map(cl -> new LocationViewModel(l.getKey(), cl)));
-                            }
-                            mIsContactSharingSubject.onNext(isContactSharing);
-                            return r;
-                        }))
-                .flatMap(locations -> Observable.combineLatest(locations, locsArray -> {
-                    List<LocationViewModel> list = new ArrayList<>(locsArray.length);
-                    for (Object vm : locsArray)
-                        list.add((LocationViewModel)vm);
-                    return list;
-                }))
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(locations -> {
-                    Context context = getContext();
-                    if (context != null) {
-                        binding.map.getOverlays().clear();
-                        if (overlay != null)
-                            binding.map.getOverlays().add(overlay);
-                        if (marker != null)
-                            binding.map.getOverlays().add(marker);
-
-                        List<GeoPoint> geoLocations = new ArrayList<>(locations.size() + 1);
-                        GeoPoint myLoc = overlay == null ? null : overlay.getMyLocation();
-                        if (myLoc != null) {
-                            geoLocations.add(myLoc);
-                        }
-
-                        for (LocationViewModel vm : locations) {
-                            Marker m = new Marker(binding.map);
-                            GeoPoint position = new GeoPoint(vm.location.latitude, vm.location.longitude);
-                            m.setInfoWindow(null);
-                            m.setPosition(position);
-                            m.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
-                            geoLocations.add(position);
-                            mDisposableBag.add(AvatarFactory.getBitmapAvatar(context, vm.contact, bubbleSize, false).subscribe(avatar ->  {
-                                BitmapDrawable bd = new BitmapDrawable(context.getResources(), avatar);
-                                m.setIcon(bd);
-                                m.setInfoWindow(null);
-                                binding.map.getOverlays().add(m);
-                            }));
-                        }
-
-                        if (trackAll) {
-                            if (geoLocations.size() == 1) {
-                                lastBoundingBox = null;
-                                binding.map.getController().animateTo(geoLocations.get(0));
-                            } else {
-                                BoundingBox bb = BoundingBox.fromGeoPointsSafe(geoLocations);
-                                bb = bb.increaseByScale(1.5f);
-                                lastBoundingBox = bb;
-                                binding.map.zoomToBoundingBox(bb, true);
-                            }
-                        }
-                    }
-                }, e -> Log.w(TAG, "Error updating contact position", e))
-        );
-
-        Context ctx = requireContext();
-        if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
-                && ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
-            mIsSharingSubject.onNext(false);
-            mDisposableBag.add(mShowControlsSubject
-                    .firstElement()
-                    .subscribe(showControls -> {
-                        if (showControls) {
-                            requestPermissions(new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, REQUEST_CODE_LOCATION);
-                        }
-                    }));
-        } else {
-            startService();
-        }
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mBound) {
-            requireContext().unbindService(mConnection);
-            mConnection.onServiceDisconnected(null);
-            mBound = false;
-        }
-        mDisposableBag.clear();
-    }
-
-    private void startService() {
-        Context ctx = requireContext();
-        ctx.bindService(new Intent(ctx, LocationSharingService.class), mConnection, Context.BIND_AUTO_CREATE);
-    }
-
-    void showControls() {
-        mShowControlsSubject.onNext(true);
-    }
-
-    void hideControls() {
-        mShowControlsSubject.onNext(false);
-    }
-
-    private void setShowControls(boolean show) {
-        if (show) {
-            onBackPressedCallback.setEnabled(true);
-            binding.locshareSnipet.setVisibility(View.GONE);
-            binding.shareControlsMini.setVisibility(View.GONE);
-            binding.shareControlsMini.postDelayed(() -> {
-                if (binding != null) {
-                    binding.shareControlsMini.setVisibility(View.GONE);
-                    binding.locshareSnipet.setVisibility(View.GONE);
-                }
-            }, 300);
-            binding.shareControls.setVisibility(View.VISIBLE);
-            binding.locshareToolbar.setVisibility(View.VISIBLE);
-            binding.map.setOnTouchListener(null);
-            binding.map.setMultiTouchControls(true);
-        } else {
-            onBackPressedCallback.setEnabled(false);
-            binding.shareControls.setVisibility(View.GONE);
-            binding.shareControlsMini.postDelayed(() -> {
-                if (binding != null) {
-                    binding.shareControlsMini.setVisibility(View.VISIBLE);
-                    binding.locshareSnipet.setVisibility(View.VISIBLE);
-                }
-            }, 300);
-            binding.locshareToolbar.setVisibility(View.GONE);
-            binding.map.setMultiTouchControls(false);
-            binding.map.setOnTouchListener(new TouchClickListener(binding.map.getContext(), v -> mShowControlsSubject.onNext(true)));
-        }
-    }
-
-    static class RxLocationListener implements IMyLocationProvider {
-        private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-        private Observable<Location> mLocation;
-
-        RxLocationListener(Observable<Location> location) {
-            mLocation = location;
-        }
-
-        @Override
-        public boolean startLocationProvider(IMyLocationConsumer myLocationConsumer) {
-            mDisposableBag.add(mLocation.subscribe(loc -> myLocationConsumer.onLocationChanged(loc, this)));
-            return false;
-        }
-
-        @Override
-        public void stopLocationProvider() {
-            mDisposableBag.clear();
-        }
-
-        @Override
-        public Location getLastKnownLocation() {
-            return mLocation.blockingFirst();
-        }
-
-        @Override
-        public void destroy() {
-            mDisposableBag.dispose();
-            mLocation = null;
-        }
-    }
-
-    static class LocationViewModel {
-        Contact contact;
-        Account.ContactLocation location;
-        LocationViewModel(Contact c, Account.ContactLocation cl) {
-            contact = c;
-            location = cl;
-        }
-    }
-
-    private ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            Log.w(TAG, "onServiceConnected");
-            LocationSharingService.LocalBinder binder = (LocationSharingService.LocalBinder) service;
-            mService = binder.getService();
-            mBound = true;
-
-            if (marker == null) {
-                marker = new Marker(binding.map);
-                marker.setInfoWindow(null);
-                marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
-                mServiceDisposableBag.add(mConversationFacade
-                        .getAccountSubject(mPath.getAccountId())
-                        .flatMap(account -> AvatarFactory.getBitmapAvatar(requireContext(), account, bubbleSize))
-                        .subscribe(avatar -> {
-                            marker.setIcon(new BitmapDrawable(requireContext().getResources(), avatar));
-                            binding.map.getOverlays().add(marker);
-                        }));
-            }
-
-            mServiceDisposableBag.add(mService.getContactSharing()
-                    .subscribe(location -> mIsSharingSubject.onNext(location.contains(mPath))));
-            mServiceDisposableBag.add(mService.getMyLocation()
-                    .subscribe(location -> marker.setPosition(new GeoPoint(location))));
-            mServiceDisposableBag.add(mService.getMyLocation()
-                    .firstElement()
-                    .observeOn(AndroidSchedulers.mainThread())
-                    .subscribe(location  -> {
-                        // start map on first location
-                        binding.map.setExpectedCenter(new GeoPoint(location));
-                        overlay = new MyLocationNewOverlay(new RxLocationListener(mService.getMyLocation()), binding.map);
-                        overlay.enableMyLocation();
-                        binding.map.getOverlays().add(overlay);
-                    }));
-
-            if (mStartSharingPending != null) {
-                Integer pending = mStartSharingPending;
-                mStartSharingPending = null;
-                startSharing(pending);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            Log.w(TAG, "onServiceDisconnected");
-            mBound = false;
-            mServiceDisposableBag.clear();
-            mService = null;
-        }
-    };
-
-    private int getSelectedDuration() {
-        switch (binding.locationShareTimeGroup.getCheckedChipId()) {
-            case R.id.location_share_time_10m:
-                return 10 * 60;
-            case R.id.location_share_time_1h:
-            default:
-                return 60 * 60;
-        }
-    }
-
-    private void setIsSharing(boolean sharing) {
-        if (sharing) {
-            binding.btnShareLocation.setBackgroundColor(ContextCompat.getColor(binding.btnShareLocation.getContext(), R.color.design_default_color_error));
-            binding.btnShareLocation.setText(R.string.location_share_action_stop);
-            binding.btnShareLocation.setOnClickListener(v -> stopSharing());
-            binding.locationShareTimeGroup.setVisibility(View.GONE);
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                if (mService != null) {
-                    binding.locationShareTimeRemaining.setVisibility(View.VISIBLE);
-                    if (mCountdownDisposable == null || mCountdownDisposable.isDisposed())  {
-                        mCountdownDisposable = mService.getContactSharingExpiration(mPath)
-                                .subscribe(l -> binding.locationShareTimeRemaining.setText(formatDuration(l, MeasureFormat.FormatWidth.SHORT)));
-                        mServiceDisposableBag.add(mCountdownDisposable);
-                    }
-                }
-            }
-            binding.locationShareStop.setVisibility(View.VISIBLE);
-            requireView().post(this::hideControls);
-        } else {
-            if (mCountdownDisposable != null) {
-                mCountdownDisposable.dispose();
-                mCountdownDisposable = null;
-            }
-            binding.btnShareLocation.setBackgroundColor(ContextCompat.getColor(binding.btnShareLocation.getContext(), R.color.colorSecondary));
-            binding.btnShareLocation.setText(R.string.location_share_action_start);
-            binding.btnShareLocation.setOnClickListener(v -> startSharing(getSelectedDuration()));
-            binding.locationShareTimeRemaining.setVisibility(View.GONE);
-            binding.locationShareTimeGroup.setVisibility(View.VISIBLE);
-            binding.locationShareStop.setVisibility(View.GONE);
-        }
-    }
-
-    private void startSharing(int durationSec) {
-        Context ctx = requireContext();
-        try {
-            if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
-                    && ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
-                mStartSharingPending = durationSec;
-                requestPermissions(new String[]{ Manifest.permission.ACCESS_FINE_LOCATION }, REQUEST_CODE_LOCATION);
-            } else {
-                Intent intent = new Intent(LocationSharingService.ACTION_START, mPath.toUri(), ctx, LocationSharingService.class)
-                        .putExtra(LocationSharingService.EXTRA_SHARING_DURATION, durationSec);
-                ContextCompat.startForegroundService(ctx, intent);
-            }
-        } catch (Exception e) {
-            Toast.makeText(ctx, "Error starting location sharing: " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
-        }
-    }
-
-    private void stopSharing() {
-        Context ctx = requireContext();
-        try {
-            Intent intent = new Intent(LocationSharingService.ACTION_STOP, mPath.toUri(), ctx, LocationSharingService.class);
-            ctx.startService(intent);
-        } catch (Exception e) {
-            Log.w(TAG, "Error stopping location sharing", e);
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.kt
new file mode 100644
index 000000000..ef3fa55f5
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/LocationSharingFragment.kt
@@ -0,0 +1,587 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.fragments
+
+import android.Manifest
+import android.animation.LayoutTransition
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.icu.text.MeasureFormat
+import android.icu.text.MeasureFormat.FormatWidth
+import android.icu.util.Measure
+import android.icu.util.MeasureUnit
+import android.location.Location
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
+import android.text.format.DateUtils
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.RequiresApi
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.google.android.material.chip.ChipGroup
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.databinding.FragLocationSharingBinding
+import cx.ring.service.LocationSharingService
+import cx.ring.service.LocationSharingService.LocalBinder
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.TouchClickListener
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.services.ConversationFacade
+import net.jami.model.Account
+import net.jami.model.Account.ContactLocation
+import net.jami.model.Contact
+import org.osmdroid.config.Configuration
+import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.BoundingBox
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.CustomZoomButtonsController
+import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer
+import org.osmdroid.views.overlay.mylocation.IMyLocationProvider
+import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
+import java.io.File
+import java.util.*
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class LocationSharingFragment : Fragment() {
+    private val mDisposableBag = CompositeDisposable()
+    private val mServiceDisposableBag = CompositeDisposable()
+    private var mCountdownDisposable: Disposable? = null
+
+    internal enum class MapState { NONE, MINI, FULL }
+
+    @Inject
+    lateinit var mConversationFacade: ConversationFacade
+
+    private lateinit var mPath: ConversationPath
+    private var mContact: Contact? = null
+    private val mShowControlsSubject: Subject<Boolean> = BehaviorSubject.create()
+    private val mIsSharingSubject: Subject<Boolean> = BehaviorSubject.create()
+    private val mIsContactSharingSubject: Subject<Boolean> = BehaviorSubject.create()
+    private val mShowMapSubject = Observable.combineLatest(
+        mShowControlsSubject,
+        mIsSharingSubject,
+        mIsContactSharingSubject,
+        { showControls, isSharing, isContactSharing ->
+            if (showControls)
+                MapState.FULL
+            else if (isSharing || isContactSharing)
+                MapState.MINI
+            else
+                MapState.NONE
+        })
+        .distinctUntilChanged()
+
+    private var bubbleSize = 0
+    private var overlay: MyLocationNewOverlay? = null
+    private var marker: Marker? = null
+    private var lastBoundingBox: BoundingBox? = null
+    private var trackAll = true
+    private var mStartSharingPending: Int? = null
+    private var binding: FragLocationSharingBinding? = null
+    private var mService: LocationSharingService? = null
+    private var mBound = false
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        binding = FragLocationSharingBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        requireArguments().let { args ->
+            mPath = ConversationPath.fromBundle(args)!!
+            mShowControlsSubject.onNext(args.getBoolean(KEY_SHOW_CONTROLS, true))
+        }
+        val ctx = requireContext()
+        val osmPath = File(ctx.cacheDir, "osm")
+        val configuration = Configuration.getInstance()
+        configuration.osmdroidBasePath = osmPath
+        configuration.osmdroidTileCache = File(osmPath, "tiles")
+        configuration.userAgentValue = "net.jami.android"
+        configuration.isMapViewHardwareAccelerated = true
+        configuration.isMapViewRecyclerFriendly = false
+        bubbleSize = ctx.resources.getDimensionPixelSize(R.dimen.location_sharing_avatar_size)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        binding?.let { binding ->
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                binding.locationShareTime1h.text = formatDuration(DateUtils.HOUR_IN_MILLIS, FormatWidth.WIDE)
+                binding.locationShareTime10m.text = formatDuration(10 * DateUtils.MINUTE_IN_MILLIS, FormatWidth.WIDE)
+            }
+            binding.infoBtn.setOnClickListener { v: View ->
+                val padding = v.resources.getDimensionPixelSize(R.dimen.padding_large)
+                val textView = TextView(v.context)
+                textView.setText(R.string.location_share_about_message)
+                textView.setOnClickListener { tv -> tv.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.location_share_about_osm_copy_url)))) }
+                textView.setPadding(padding, padding, padding, padding)
+                MaterialAlertDialogBuilder(view.context)
+                    .setTitle(R.string.location_share_about_title)
+                    .setView(textView)
+                    .create().show()
+            }
+            binding.btnCenterPosition.setOnClickListener {
+                overlay?.let { overlay ->
+                    trackAll = true
+                    if (lastBoundingBox != null) binding.map.zoomToBoundingBox(lastBoundingBox, true)
+                    else overlay.enableFollowLocation()
+                }
+            }
+            binding.locationShareTimeGroup.setOnCheckedChangeListener { group: ChipGroup, id: Int ->
+                if (id == View.NO_ID) group.check(R.id.location_share_time_1h)
+            }
+            binding.locshareToolbar.setNavigationOnClickListener { mShowControlsSubject.onNext(false) }
+            binding.locationShareStop.setOnClickListener { stopSharing() }
+            binding.map.setTileSource(TileSourceFactory.MAPNIK)
+            binding.map.isHorizontalMapRepetitionEnabled = false
+            binding.map.isTilesScaledToDpi = true
+            binding.map.setMapOrientation(0f, false)
+            binding.map.minZoomLevel = 1.0
+            binding.map.maxZoomLevel = 19.0
+            binding.map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
+            binding.map.controller.setZoom(14.0)
+        }
+        (view as ViewGroup).layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
+    }
+
+    private val onBackPressedCallback: OnBackPressedCallback =
+        object : OnBackPressedCallback(false) {
+            override fun handleOnBackPressed() {
+                mShowControlsSubject.onNext(false)
+            }
+        }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mShowControlsSubject.onComplete()
+        mIsSharingSubject.onComplete()
+        mIsContactSharingSubject.onComplete()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        binding?.map?.onResume()
+        try {
+            overlay?.enableMyLocation()
+        } catch (e: Exception) {
+            Log.w(TAG, e)
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        binding?.map?.onPause()
+        overlay?.disableMyLocation()
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        if (requestCode == REQUEST_CODE_LOCATION) {
+            var granted = false
+            for (result in grantResults) granted =
+                granted or (result == PackageManager.PERMISSION_GRANTED)
+            if (granted) {
+                startService()
+            } else {
+                mIsSharingSubject.onNext(false)
+                mShowControlsSubject.onNext(false)
+            }
+        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+        mDisposableBag.add(mServiceDisposableBag)
+        mDisposableBag.add(mShowControlsSubject.subscribe { show: Boolean -> setShowControls(show) })
+        mDisposableBag.add(mIsSharingSubject.subscribe { sharing: Boolean -> setIsSharing(sharing) })
+        mDisposableBag.add(mShowMapSubject
+            .subscribeOn(AndroidSchedulers.mainThread())
+            .subscribe { state: MapState ->
+                val p = parentFragment
+                if (p is ConversationFragment) {
+                    if (state == MapState.FULL)
+                        p.openLocationSharing()
+                    else
+                        p.closeLocationSharing(state == MapState.MINI)
+                }
+            })
+        mDisposableBag.add(mIsContactSharingSubject
+            .distinctUntilChanged()
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { sharing ->
+                binding?.let { binding  ->
+                    if (sharing) {
+                        val sharingString =
+                            getString(R.string.location_share_contact, mContact!!.displayName)
+                        binding.locshareToolbar.subtitle = sharingString
+                        binding.locshareSnipetTxt.visibility = View.VISIBLE
+                        binding.locshareSnipetTxtShadow.visibility = View.VISIBLE
+                        binding.locshareSnipetTxt.text = sharingString
+                    } else {
+                        binding.locshareToolbar.subtitle = null
+                        binding.locshareSnipetTxt.visibility = View.GONE
+                        binding.locshareSnipetTxtShadow.visibility = View.GONE
+                        binding.locshareSnipetTxt.text = null
+                    }
+                }
+            })
+        val contactUri = mPath.conversationUri
+        mDisposableBag.add(mConversationFacade
+            .getAccountSubject(mPath.accountId)
+            .flatMapObservable { account: Account ->
+                account.locationsUpdates
+                    .map<List<Observable<LocationViewModel>>> { locations ->
+                        val r: MutableList<Observable<LocationViewModel>> = ArrayList(locations.size)
+                        var isContactSharing = false
+                        for ((key, value) in locations) {
+                            if (key === account.getContactFromCache(contactUri)) {
+                                isContactSharing = true
+                                mContact = key
+                            }
+                            r.add(value.map { cl -> LocationViewModel(key, cl) })
+                        }
+                        mIsContactSharingSubject.onNext(isContactSharing)
+                        r
+                    }
+            }
+            .flatMap { locations: List<Observable<LocationViewModel>>? ->
+                Observable.combineLatest<LocationViewModel, List<LocationViewModel>>(locations) { locsArray: Array<Any> ->
+                    val list: MutableList<LocationViewModel> = ArrayList(locsArray.size)
+                    for (vm in locsArray) list.add(vm as LocationViewModel)
+                    list
+                }
+            }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ locations: List<LocationViewModel> ->
+                val context = context
+                if (context != null) {
+                    binding!!.map.overlays.clear()
+                    if (overlay != null) binding!!.map.overlays.add(overlay)
+                    if (marker != null) binding!!.map.overlays.add(marker)
+                    val geoLocations: MutableList<GeoPoint> = ArrayList(locations.size + 1)
+                    overlay?.myLocation?.let { myLoc -> geoLocations.add(myLoc) }
+                    for (vm in locations) {
+                        val m = Marker(binding!!.map)
+                        val position = GeoPoint(vm.location.latitude, vm.location.longitude)
+                        m.setInfoWindow(null)
+                        m.position = position
+                        m.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+                        geoLocations.add(position)
+                        mDisposableBag.add(
+                            AvatarFactory.getBitmapAvatar(context, vm.contact, bubbleSize, false)
+                                .subscribe { avatar: Bitmap ->
+                                    val bd = BitmapDrawable(context.resources, avatar)
+                                    m.icon = bd
+                                    m.setInfoWindow(null)
+                                    binding!!.map.overlays.add(m)
+                                })
+                    }
+                    if (trackAll) {
+                        if (geoLocations.size == 1) {
+                            lastBoundingBox = null
+                            binding!!.map.controller.animateTo(geoLocations[0])
+                        } else {
+                            var bb = BoundingBox.fromGeoPointsSafe(geoLocations)
+                            bb = bb.increaseByScale(1.5f)
+                            lastBoundingBox = bb
+                            binding!!.map.zoomToBoundingBox(bb, true)
+                        }
+                    }
+                }
+            }) { e: Throwable -> Log.w(TAG, "Error updating contact position", e) }
+        )
+        val ctx = requireContext()
+        if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+            && ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+            mIsSharingSubject.onNext(false)
+            mDisposableBag.add(mShowControlsSubject
+                .firstElement()
+                .subscribe { showControls ->
+                    if (showControls) {
+                        requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_CODE_LOCATION)
+                    }
+                })
+        } else {
+            startService()
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (mBound) {
+            requireContext().unbindService(mConnection)
+            mConnection.onServiceDisconnected(null)
+            mBound = false
+        }
+        mDisposableBag.clear()
+    }
+
+    private fun startService() {
+        val ctx = requireContext()
+        ctx.bindService(Intent(ctx, LocationSharingService::class.java), mConnection, Context.BIND_AUTO_CREATE)
+    }
+
+    fun showControls() {
+        mShowControlsSubject.onNext(true)
+    }
+
+    fun hideControls() {
+        mShowControlsSubject.onNext(false)
+    }
+
+    private fun setShowControls(show: Boolean) {
+        binding?.let { b ->
+            if (show) {
+                onBackPressedCallback.isEnabled = true
+                b.locshareSnipet.visibility = View.GONE
+                b.shareControlsMini.visibility = View.GONE
+                b.shareControlsMini.postDelayed({
+                    binding?.let { b ->
+                        b.shareControlsMini.visibility = View.GONE
+                        b.locshareSnipet.visibility = View.GONE
+                    }
+                }, 300)
+                b.shareControls.visibility = View.VISIBLE
+                b.locshareToolbar.visibility = View.VISIBLE
+                b.map.setOnTouchListener(null)
+                b.map.setMultiTouchControls(true)
+            } else {
+                onBackPressedCallback.isEnabled = false
+                b.shareControls.visibility = View.GONE
+                b.shareControlsMini.postDelayed({
+                        binding?.let { b ->
+                        b.shareControlsMini.visibility = View.VISIBLE
+                        b.locshareSnipet.visibility = View.VISIBLE
+                    }
+                }, 300)
+                b.locshareToolbar.visibility = View.GONE
+                b.map.setMultiTouchControls(false)
+                b.map.setOnTouchListener(TouchClickListener(binding!!.map.context) { mShowControlsSubject.onNext(true) })
+            }
+        }
+    }
+
+    internal class RxLocationListener(private val mLocation: Observable<Location>) : IMyLocationProvider {
+        private val mDisposableBag = CompositeDisposable()
+
+        override fun startLocationProvider(myLocationConsumer: IMyLocationConsumer): Boolean {
+            mDisposableBag.add(mLocation.subscribe { loc -> myLocationConsumer.onLocationChanged(loc, this) })
+            return false
+        }
+
+        override fun stopLocationProvider() {
+            mDisposableBag.clear()
+        }
+
+        override fun getLastKnownLocation(): Location {
+            return mLocation.blockingFirst()
+        }
+
+        override fun destroy() {
+            mDisposableBag.dispose()
+        }
+    }
+
+    internal class LocationViewModel(var contact: Contact, var location: ContactLocation)
+
+    private val mConnection: ServiceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            Log.w(TAG, "onServiceConnected")
+            val binder = service as LocalBinder
+            mService = binder.service
+            mBound = true
+            if (marker == null) {
+                marker = Marker(binding!!.map).apply {
+                    setInfoWindow(null)
+                    setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+                }
+                mServiceDisposableBag.add(mConversationFacade
+                    .getAccountSubject(mPath.accountId)
+                    .flatMap { account -> AvatarFactory.getBitmapAvatar(requireContext(), account, bubbleSize) }
+                    .subscribe { avatar ->
+                        marker!!.icon = BitmapDrawable(requireContext().resources, avatar)
+                        binding!!.map.overlays.add(marker)
+                    })
+            }
+            mServiceDisposableBag.add(binder.service.contactSharing
+                    .subscribe { location -> mIsSharingSubject.onNext(location.contains(mPath)) })
+            mServiceDisposableBag.add(binder.service.myLocation
+                    .subscribe { location -> marker!!.position = GeoPoint(location) })
+            mServiceDisposableBag.add(binder.service.myLocation
+                    .firstElement()
+                    .observeOn(AndroidSchedulers.mainThread())
+                    .subscribe { location ->
+                        // start map on first location
+                        binding?.let { binding ->
+                            binding.map.setExpectedCenter(GeoPoint(location))
+                            overlay = MyLocationNewOverlay(RxLocationListener(binder.service.myLocation), binding.map)
+                                .apply { enableMyLocation() }
+                            binding.map.overlays.add(overlay)
+                        }
+                    })
+            mStartSharingPending?.let { pending ->
+                mStartSharingPending = null
+                startSharing(pending)
+            }
+        }
+
+        override fun onServiceDisconnected(name: ComponentName?) {
+            Log.w(TAG, "onServiceDisconnected")
+            mBound = false
+            mServiceDisposableBag.clear()
+            mService = null
+        }
+    }
+    private val selectedDuration: Int
+        get() = when (binding!!.locationShareTimeGroup.checkedChipId) {
+            R.id.location_share_time_10m -> 10 * 60
+            R.id.location_share_time_1h -> 60 * 60
+            else -> 60 * 60
+        }
+
+    private fun setIsSharing(sharing: Boolean) {
+        binding?.let { binding ->
+            if (sharing) {
+                binding.btnShareLocation.setBackgroundColor(ContextCompat.getColor(binding.btnShareLocation.context, R.color.design_default_color_error))
+                binding.btnShareLocation.setText(R.string.location_share_action_stop)
+                binding.btnShareLocation.setOnClickListener { v: View? -> stopSharing() }
+                binding.locationShareTimeGroup.visibility = View.GONE
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    mService?.let { service ->
+                        binding.locationShareTimeRemaining.visibility = View.VISIBLE
+                        if (mCountdownDisposable == null || mCountdownDisposable!!.isDisposed) {
+                            mCountdownDisposable = service.getContactSharingExpiration(mPath)
+                                .subscribe { l -> binding.locationShareTimeRemaining.text = formatDuration(l, FormatWidth.SHORT) }
+                            mServiceDisposableBag.add(mCountdownDisposable)
+                        }
+                    }
+                }
+                binding.locationShareStop.visibility = View.VISIBLE
+                requireView().post { hideControls() }
+            } else {
+                mCountdownDisposable?.let { disposable ->
+                    disposable.dispose()
+                    mCountdownDisposable = null
+                }
+                binding.btnShareLocation.setBackgroundColor(ContextCompat.getColor(binding.btnShareLocation.context, R.color.colorSecondary))
+                binding.btnShareLocation.setText(R.string.location_share_action_start)
+                binding.btnShareLocation.setOnClickListener { startSharing(selectedDuration) }
+                binding.locationShareTimeRemaining.visibility = View.GONE
+                binding.locationShareTimeGroup.visibility = View.VISIBLE
+                binding.locationShareStop.visibility = View.GONE
+            }
+        }
+    }
+
+    private fun startSharing(durationSec: Int) {
+        val ctx = requireContext()
+        try {
+            if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+                && ActivityCompat.checkSelfPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+                mStartSharingPending = durationSec
+                requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_CODE_LOCATION)
+            } else {
+                val intent = Intent(LocationSharingService.ACTION_START, mPath.toUri(), ctx, LocationSharingService::class.java)
+                    .putExtra(LocationSharingService.EXTRA_SHARING_DURATION, durationSec)
+                ContextCompat.startForegroundService(ctx, intent)
+            }
+        } catch (e: Exception) {
+            Toast.makeText(ctx, "Error starting location sharing: " + e.localizedMessage, Toast.LENGTH_SHORT).show()
+        }
+    }
+
+    private fun stopSharing() {
+        try {
+            val ctx = requireContext()
+            ctx.startService(Intent(LocationSharingService.ACTION_STOP, mPath.toUri(), ctx, LocationSharingService::class.java))
+        } catch (e: Exception) {
+            Log.w(TAG, "Error stopping location sharing", e)
+        }
+    }
+
+    companion object {
+        private val TAG = LocationSharingFragment::class.java.simpleName
+        private const val REQUEST_CODE_LOCATION = 47892
+        private const val KEY_SHOW_CONTROLS = "showControls"
+
+        fun newInstance(accountId: String, conversationId: String, showControls: Boolean): LocationSharingFragment {
+            val fragment = LocationSharingFragment()
+            val args = ConversationPath.toBundle(accountId, conversationId)
+            args.putBoolean(KEY_SHOW_CONTROLS, showControls)
+            fragment.arguments = args
+            return fragment
+        }
+
+        @RequiresApi(api = Build.VERSION_CODES.N)
+        private fun formatDuration(millis: Long, width: FormatWidth): CharSequence {
+            val formatter = MeasureFormat.getInstance(Locale.getDefault(), width)
+            return when {
+                millis >= DateUtils.HOUR_IN_MILLIS -> {
+                    val hours = ((millis + DateUtils.HOUR_IN_MILLIS / 2) / DateUtils.HOUR_IN_MILLIS).toInt()
+                    formatter.format(Measure(hours, MeasureUnit.HOUR))
+                }
+                millis >= DateUtils.MINUTE_IN_MILLIS -> {
+                    val minutes = ((millis + DateUtils.MINUTE_IN_MILLIS / 2) / DateUtils.MINUTE_IN_MILLIS).toInt()
+                    formatter.format(Measure(minutes, MeasureUnit.MINUTE))
+                }
+                else -> {
+                    val seconds = ((millis + DateUtils.SECOND_IN_MILLIS / 2) / DateUtils.SECOND_IN_MILLIS).toInt()
+                    formatter.format(Measure(seconds, MeasureUnit.SECOND))
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java
index de2dea99e..e85f508d5 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java
@@ -42,7 +42,9 @@ import net.jami.model.AccountConfig;
 import net.jami.model.Codec;
 import net.jami.model.ConfigKey;
 import cx.ring.mvp.BasePreferenceFragment;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class MediaPreferenceFragment extends BasePreferenceFragment<MediaPreferencePresenter> implements MediaPreferenceView {
 
     public static final String TAG = MediaPreferenceFragment.class.getSimpleName();
@@ -74,7 +76,6 @@ public class MediaPreferenceFragment extends BasePreferenceFragment<MediaPrefere
 
     @Override
     public void onCreatePreferences(Bundle bundle, String rootKey) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         super.onCreatePreferences(bundle, rootKey);
 
         String accountId = getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY);
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.java
deleted file mode 100644
index f33207d6e..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package cx.ring.fragments;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import net.jami.daemon.JamiService;
-
-import cx.ring.databinding.FragPluginHandlersListBinding;
-import cx.ring.plugins.PluginUtils;
-import cx.ring.settings.pluginssettings.PluginDetails;
-import cx.ring.settings.pluginssettings.PluginsListAdapter;
-import cx.ring.utils.ConversationPath;
-
-
-public class PluginHandlersListFragment extends Fragment implements PluginsListAdapter.PluginListItemListener {
-    public static final String TAG = "PluginListHandlers";
-    private FragPluginHandlersListBinding binding;
-    private PluginsListAdapter mAdapter;
-    private ConversationPath mPath;
-
-
-    @Nullable
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragPluginHandlersListBinding.inflate(inflater, container, false);
-
-        binding.handlerList.setHasFixedSize(true);
-        mAdapter = new PluginsListAdapter(PluginUtils.getChatHandlersDetails(binding.handlerList.getContext(), mPath.getAccountId(), mPath.getConversationId()), this);
-        binding.handlerList.setAdapter(mAdapter);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Bundle args = getArguments();
-        if (args != null) {
-            mPath = ConversationPath.fromBundle(args);
-        }
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        binding.chatPluginsToolbar.setVisibility(View.VISIBLE);
-        binding.chatPluginsToolbar.setOnClickListener(v -> {
-            Fragment fragment = getParentFragment();
-            if (fragment instanceof ConversationFragment) {
-                ConversationFragment parent = (ConversationFragment) fragment;
-                parent.hidePluginListHandlers();
-            }
-        });
-    }
-
-    public static PluginHandlersListFragment newInstance(String accountId, String peerId) {
-        PluginHandlersListFragment fragment = new PluginHandlersListFragment();
-
-        Bundle args = ConversationPath.toBundle(accountId, peerId);
-
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Override
-    public void onPluginItemClicked(PluginDetails pluginDetails) {
-        JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.getAccountId(), mPath.getConversationId(), pluginDetails.isEnabled());
-    }
-
-    @Override
-    public void onPluginEnabled(PluginDetails pluginDetails) {
-        JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.getAccountId(), mPath.getConversationId(), pluginDetails.isEnabled());
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt
new file mode 100644
index 000000000..85d83be18
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt
@@ -0,0 +1,70 @@
+package cx.ring.fragments
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import cx.ring.databinding.FragPluginHandlersListBinding
+import cx.ring.plugins.PluginUtils
+import cx.ring.settings.pluginssettings.PluginDetails
+import cx.ring.settings.pluginssettings.PluginsListAdapter
+import cx.ring.settings.pluginssettings.PluginsListAdapter.PluginListItemListener
+import cx.ring.utils.ConversationPath
+import net.jami.daemon.JamiService
+
+class PluginHandlersListFragment : Fragment(), PluginListItemListener {
+    private var binding: FragPluginHandlersListBinding? = null
+    private lateinit var mPath: ConversationPath
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        mPath = ConversationPath.fromBundle(requireArguments())!!
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        return FragPluginHandlersListBinding.inflate(inflater, container, false).also { b ->
+            b.handlerList.setHasFixedSize(true)
+            b.handlerList.adapter = PluginsListAdapter(
+                PluginUtils.getChatHandlersDetails(b.handlerList.context, mPath.accountId, mPath.conversationId), this)
+            binding = b
+        }.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        binding!!.chatPluginsToolbar.visibility = View.VISIBLE
+        binding!!.chatPluginsToolbar.setOnClickListener { v: View? ->
+            val fragment = parentFragment
+            if (fragment is ConversationFragment) {
+                fragment.hidePluginListHandlers()
+            }
+        }
+    }
+
+    override fun onPluginItemClicked(pluginDetails: PluginDetails) {
+        JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.accountId, mPath.conversationId, pluginDetails.isEnabled)
+    }
+
+    override fun onPluginEnabled(pluginDetails: PluginDetails) {
+        JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.accountId, mPath.conversationId, pluginDetails.isEnabled)
+    }
+
+    companion object {
+        const val TAG = "PluginListHandlers"
+        fun newInstance(accountId: String, peerId: String): PluginHandlersListFragment {
+            val fragment = PluginHandlersListFragment()
+            fragment.arguments = ConversationPath.toBundle(accountId, peerId)
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.java
deleted file mode 100644
index d1e35ed9f..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- * Authors:    AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.fragments;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentPagerAdapter;
-
-import com.google.android.material.bottomsheet.BottomSheetBehavior;
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
-
-import cx.ring.R;
-import cx.ring.databinding.FragQrcodeBinding;
-import cx.ring.share.ScanFragment;
-import cx.ring.share.ShareFragment;
-import cx.ring.utils.DeviceUtils;
-
-public class QRCodeFragment extends BottomSheetDialogFragment {
-
-    public static final String TAG = QRCodeFragment.class.getSimpleName();
-    public static final String ARG_START_PAGE_INDEX = "start_page";
-
-    public static final int INDEX_CODE = 0;
-    public static final int INDEX_SCAN = 1;
-
-    public static QRCodeFragment newInstance(int startPage) {
-        QRCodeFragment fragment = new QRCodeFragment();
-        Bundle args = new Bundle();
-        args.putInt(ARG_START_PAGE_INDEX, startPage);
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    private FragQrcodeBinding mBinding = null;
-    private int mStartPageIndex;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        super.onCreateView(inflater, container, savedInstanceState);
-
-        Bundle args = getArguments();
-        mStartPageIndex = args.getInt(ARG_START_PAGE_INDEX, 0);
-
-        mBinding = FragQrcodeBinding.inflate(inflater, container, false);
-        mBinding.viewPager.setAdapter(new SectionsPagerAdapter(getContext(), getChildFragmentManager()));
-        mBinding.tabs.setupWithViewPager(mBinding.viewPager);
-        return mBinding.getRoot();
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        if (mStartPageIndex != 0) {
-            mBinding.tabs.getTabAt(mStartPageIndex).select();
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        mBinding = null;
-        super.onDestroyView();
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(Bundle savedInstanceState) {
-        final Dialog dialog = super.onCreateDialog(savedInstanceState);
-        dialog.setOnShowListener(dialogINterface -> {
-            if (DeviceUtils.isTablet(getContext())) {
-                dialog.getWindow().setLayout(
-                        ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.MATCH_PARENT);
-            }
-        });
-        return dialog;
-    }
-
-    static class SectionsPagerAdapter extends FragmentPagerAdapter {
-        @StringRes
-        private final int[] TAB_TITLES = new int[]{R.string.tab_code, R.string.tab_scan};
-        private final Context mContext;
-
-        SectionsPagerAdapter(Context context, FragmentManager fm) {
-            super(fm);
-            mContext = context;
-        }
-
-        @NonNull
-        @Override
-        public Fragment getItem(int position) {
-            switch (position) {
-                case 0:
-                    return new ShareFragment();
-                case 1:
-                    return new ScanFragment();
-                default:
-                    return null;
-            }
-        }
-
-        @Nullable
-        @Override
-        public CharSequence getPageTitle(int position) {
-            return mContext.getResources().getString(TAB_TITLES[position]);
-        }
-
-        @Override
-        public int getCount() {
-            return TAB_TITLES.length;
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        addGlobalLayoutListener(getView());
-    }
-
-    private void addGlobalLayoutListener(final View view) {
-        view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                setPeekHeight(v.getMeasuredHeight());
-                v.removeOnLayoutChangeListener(this);
-            }
-        });
-    }
-
-    public void setPeekHeight(int peekHeight) {
-        BottomSheetBehavior<?> behavior = getBottomSheetBehaviour();
-        if (behavior == null) {
-            return;
-        }
-
-        behavior.setPeekHeight(peekHeight);
-    }
-
-    private BottomSheetBehavior<?> getBottomSheetBehaviour() {
-        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) ((View) getView().getParent()).getLayoutParams();
-        CoordinatorLayout.Behavior<?> behavior = layoutParams.getBehavior();
-        if (behavior instanceof BottomSheetBehavior) {
-            return (BottomSheetBehavior<?>) behavior;
-        }
-
-        return null;
-    }
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.kt
new file mode 100644
index 000000000..0b2b028c8
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/QRCodeFragment.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Authors:    AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.fragments
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.StringRes
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentPagerAdapter
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import cx.ring.R
+import cx.ring.databinding.FragQrcodeBinding
+import cx.ring.share.ScanFragment
+import cx.ring.share.ShareFragment
+import cx.ring.utils.DeviceUtils.isTablet
+
+class QRCodeFragment : BottomSheetDialogFragment() {
+    private var mBinding: FragQrcodeBinding? = null
+    private var mStartPageIndex = 0
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        super.onCreateView(inflater, container, savedInstanceState)
+        val args = requireArguments()
+        mStartPageIndex = args.getInt(ARG_START_PAGE_INDEX, 0)
+        return FragQrcodeBinding.inflate(inflater, container, false).apply {
+            viewPager.adapter = SectionsPagerAdapter(root.context, childFragmentManager)
+            tabs.setupWithViewPager(viewPager)
+            mBinding = this
+        }.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        if (mStartPageIndex != 0) {
+            mBinding?.tabs?.getTabAt(mStartPageIndex)?.select()
+        }
+    }
+
+    override fun onDestroyView() {
+        mBinding = null
+        super.onDestroyView()
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val dialog = super.onCreateDialog(savedInstanceState)
+        dialog.setOnShowListener {
+            if (isTablet(requireContext())) {
+                dialog.window?.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+            }
+        }
+        return dialog
+    }
+
+    internal class SectionsPagerAdapter(private val mContext: Context, fm: FragmentManager) :
+        FragmentPagerAdapter(fm) {
+        @StringRes
+        private val TAB_TITLES = intArrayOf(R.string.tab_code, R.string.tab_scan)
+
+        override fun getItem(position: Int): Fragment {
+            return when (position) {
+                0 -> ShareFragment()
+                1 -> ScanFragment()
+                else -> throw IllegalArgumentException()
+            }
+        }
+
+        override fun getPageTitle(position: Int): CharSequence {
+            return mContext.resources.getString(TAB_TITLES[position])
+        }
+
+        override fun getCount(): Int {
+            return TAB_TITLES.size
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        addGlobalLayoutListener(requireView())
+    }
+
+    private fun addGlobalLayoutListener(view: View) {
+        view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+            override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
+                setPeekHeight(v.measuredHeight)
+                v.removeOnLayoutChangeListener(this)
+            }
+        })
+    }
+
+    fun setPeekHeight(peekHeight: Int) {
+        bottomSheetBehaviour?.peekHeight = peekHeight
+    }
+
+    private val bottomSheetBehaviour: BottomSheetBehavior<*>?
+        get() {
+            val layoutParams = (requireView().parent as View).layoutParams as CoordinatorLayout.LayoutParams
+            val behavior = layoutParams.behavior
+            return if (behavior is BottomSheetBehavior<*>) {
+                behavior
+            } else null
+        }
+
+    companion object {
+        val TAG = QRCodeFragment::class.simpleName!!
+        const val ARG_START_PAGE_INDEX = "start_page"
+        const val INDEX_CODE = 0
+        const val INDEX_SCAN = 1
+
+        fun newInstance(startPage: Int): QRCodeFragment {
+            val fragment = QRCodeFragment()
+            val args = Bundle()
+            args.putInt(ARG_START_PAGE_INDEX, startPage)
+            fragment.arguments = args
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.java
deleted file mode 100644
index 893e2b02c..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.fragments;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.inputmethod.EditorInfo;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.FragAccSipCreateBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import net.jami.mvp.SIPCreationView;
-import net.jami.wizard.SIPCreationPresenter;
-
-public class SIPAccountCreationFragment extends BaseSupportFragment<SIPCreationPresenter> implements SIPCreationView {
-    public static final String TAG = SIPAccountCreationFragment.class.getSimpleName();
-
-    private ProgressDialog mProgress = null;
-    private FragAccSipCreateBinding binding = null;
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragAccSipCreateBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        binding.password.setOnEditorActionListener((v, actionId, event) -> {
-            if (actionId == EditorInfo.IME_ACTION_DONE) {
-                binding.createSipButton.callOnClick();
-            }
-            return false;
-        });
-        binding.createSipButton.setOnClickListener(v -> createSIPAccount(false));
-    }
-
-    /**
-     * Start the creation process in the presenter
-     *
-     * @param bypassWarnings boolean stating if we want to display warning to the user or create the account anyway
-     */
-    private void createSIPAccount(boolean bypassWarnings) {
-        //orientation is locked during the create of account to avoid the destruction of the thread
-        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
-        String hostname = binding.hostname.getText().toString();
-        String proxy = binding.proxy.getText().toString();
-        String username = binding.username.getText().toString();
-        String password = binding.password.getText().toString();
-        presenter.startCreation(hostname, proxy, username, password, bypassWarnings);
-    }
-
-    @Override
-    public void showUsernameError() {
-        binding.username.setError(getString(R.string.error_field_required));
-        binding.username.requestFocus();
-    }
-
-    @Override
-    public void showLoading() {
-        mProgress = new ProgressDialog(getActivity());
-        mProgress.setTitle(R.string.dialog_wait_create);
-        mProgress.setMessage(getString(R.string.dialog_wait_create_details));
-        mProgress.setCancelable(false);
-        mProgress.setCanceledOnTouchOutside(false);
-        mProgress.show();
-    }
-
-    @Override
-    public void resetErrors() {
-        binding.password.setError(null);
-    }
-
-    @Override
-    public void showPasswordError() {
-        binding.password.setError(getString(R.string.error_field_required));
-        binding.password.requestFocus();
-    }
-
-    @Override
-    public void showIP2IPWarning() {
-        showDialog(getActivity().getString(R.string.dialog_warn_ip2ip_account_title),
-                getActivity().getString(R.string.dialog_warn_ip2ip_account_message),
-                getActivity().getString(android.R.string.ok),
-                getActivity().getString(android.R.string.cancel),
-                (dialog, which) -> {
-                    dialog.dismiss();
-                    createSIPAccount(true);
-                },
-                null);
-    }
-
-    @Override
-    public void showRegistrationError() {
-        showDialog(getActivity().getString(R.string.account_sip_cannot_be_registered),
-                getActivity().getString(R.string.account_sip_cannot_be_registered_message),
-                getActivity().getString(android.R.string.ok),
-                getActivity().getString(R.string.account_sip_register_anyway),
-                (dialog, which) -> presenter.removeAccount(),
-                (dialog, id) -> {
-                    getActivity().setResult(Activity.RESULT_OK, new Intent());
-                    getActivity().finish();
-                });
-    }
-
-    @Override
-    public void showRegistrationNetworkError() {
-        showDialog(getActivity().getString(R.string.account_no_network_title),
-                getActivity().getString(R.string.account_no_network_message),
-                getActivity().getString(android.R.string.ok),
-                getActivity().getString(R.string.account_sip_register_anyway),
-                (dialog, which) -> presenter.removeAccount(),
-                (dialog, id) -> {
-                    getActivity().setResult(Activity.RESULT_OK, new Intent());
-                    getActivity().finish();
-                });
-    }
-
-    @Override
-    public void showRegistrationSuccess() {
-        showDialog(getActivity().getString(R.string.account_sip_success_title),
-                getActivity().getString(R.string.account_sip_success_message),
-                getActivity().getString(android.R.string.ok),
-                null,
-                (dialog, which) -> {
-                    getActivity().setResult(Activity.RESULT_OK, new Intent());
-                    getActivity().finish();
-                },
-                null);
-    }
-
-    public void showDialog(final String title,
-                           final String message,
-                           final String positive,
-                           final String negative,
-                           final DialogInterface.OnClickListener listenerPositive,
-                           final DialogInterface.OnClickListener listenerNegative) {
-        if (mProgress != null && mProgress.isShowing()) {
-            mProgress.dismiss();
-        }
-
-        //orientation is locked during the create of account to avoid the destruction of the thread
-        getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
-        new MaterialAlertDialogBuilder(requireContext())
-                .setPositiveButton(positive, listenerPositive)
-                .setNegativeButton(negative, listenerNegative)
-                .setTitle(title).setMessage(message)
-                .setOnDismissListener(dialog -> {
-                    //unlock the screen orientation
-                    getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
-                })
-                .show();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt
new file mode 100644
index 000000000..8cf94046a
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt
@@ -0,0 +1,193 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.fragments
+
+import android.app.Activity
+import android.app.ProgressDialog
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import android.widget.TextView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.databinding.FragAccSipCreateBinding
+import cx.ring.mvp.BaseSupportFragment
+import dagger.hilt.android.AndroidEntryPoint
+import net.jami.mvp.SIPCreationView
+import net.jami.wizard.SIPCreationPresenter
+
+@AndroidEntryPoint
+class SIPAccountCreationFragment : BaseSupportFragment<SIPCreationPresenter, SIPCreationView>(),
+    SIPCreationView {
+    private var mProgress: ProgressDialog? = null
+    private var binding: FragAccSipCreateBinding? = null
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        binding = FragAccSipCreateBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        binding!!.password.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? ->
+            if (actionId == EditorInfo.IME_ACTION_DONE) {
+                binding!!.createSipButton.callOnClick()
+            }
+            false
+        }
+        binding!!.createSipButton.setOnClickListener { v: View? -> createSIPAccount(false) }
+    }
+
+    /**
+     * Start the creation process in the presenter
+     *
+     * @param bypassWarnings boolean stating if we want to display warning to the user or create the account anyway
+     */
+    private fun createSIPAccount(bypassWarnings: Boolean) {
+        //orientation is locked during the create of account to avoid the destruction of the thread
+        requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
+        val hostname = binding!!.hostname.text.toString()
+        val proxy = binding!!.proxy.text.toString()
+        val username = binding!!.username.text.toString()
+        val password = binding!!.password.text.toString()
+        presenter.startCreation(hostname, proxy, username, password, bypassWarnings)
+    }
+
+    override fun showUsernameError() {
+        binding!!.username.error = getString(R.string.error_field_required)
+        binding!!.username.requestFocus()
+    }
+
+    override fun showLoading() {
+        mProgress = ProgressDialog(activity)
+        mProgress!!.setTitle(R.string.dialog_wait_create)
+        mProgress!!.setMessage(getString(R.string.dialog_wait_create_details))
+        mProgress!!.setCancelable(false)
+        mProgress!!.setCanceledOnTouchOutside(false)
+        mProgress!!.show()
+    }
+
+    override fun resetErrors() {
+        binding!!.password.error = null
+    }
+
+    override fun showPasswordError() {
+        binding!!.password.error = getString(R.string.error_field_required)
+        binding!!.password.requestFocus()
+    }
+
+    override fun showIP2IPWarning() {
+        showDialog(
+            getString(R.string.dialog_warn_ip2ip_account_title),
+            getString(R.string.dialog_warn_ip2ip_account_message),
+            getString(android.R.string.ok),
+            getString(android.R.string.cancel),
+            { dialog: DialogInterface, which: Int ->
+                dialog.dismiss()
+                createSIPAccount(true)
+            },
+            null
+        )
+    }
+
+    override fun showRegistrationError() {
+        showDialog(getString(R.string.account_sip_cannot_be_registered),
+            getString(R.string.account_sip_cannot_be_registered_message),
+            getString(android.R.string.ok),
+            getString(R.string.account_sip_register_anyway),
+            { dialog: DialogInterface?, which: Int -> presenter.removeAccount() }
+        ) { dialog: DialogInterface?, id: Int ->
+            val activity: Activity = requireActivity()
+            activity.setResult(Activity.RESULT_OK, Intent())
+            activity.finish()
+        }
+    }
+
+    override fun showRegistrationNetworkError() {
+        showDialog(getString(R.string.account_no_network_title),
+            getString(R.string.account_no_network_message),
+            getString(android.R.string.ok),
+            getString(R.string.account_sip_register_anyway),
+            { dialog: DialogInterface?, which: Int -> presenter.removeAccount() }
+        ) { dialog: DialogInterface?, id: Int ->
+            val activity: Activity = requireActivity()
+            activity.setResult(Activity.RESULT_OK, Intent())
+            activity.finish()
+        }
+    }
+
+    override fun showRegistrationSuccess() {
+        showDialog(
+            getString(R.string.account_sip_success_title),
+            getString(R.string.account_sip_success_message),
+            getString(android.R.string.ok),
+            null,
+            { dialog: DialogInterface?, which: Int ->
+                val activity: Activity = requireActivity()
+                activity.setResult(Activity.RESULT_OK, Intent())
+                activity.finish()
+            },
+            null
+        )
+    }
+
+    fun showDialog(
+        title: String?,
+        message: String?,
+        positive: String?,
+        negative: String?,
+        listenerPositive: DialogInterface.OnClickListener?,
+        listenerNegative: DialogInterface.OnClickListener?
+    ) {
+        if (mProgress != null && mProgress!!.isShowing) {
+            mProgress!!.dismiss()
+        }
+
+        //orientation is locked during the create of account to avoid the destruction of the thread
+        requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
+        MaterialAlertDialogBuilder(requireContext())
+            .setPositiveButton(positive, listenerPositive)
+            .setNegativeButton(negative, listenerNegative)
+            .setTitle(title).setMessage(message)
+            .setOnDismissListener { dialog: DialogInterface? ->
+                //unlock the screen orientation
+                requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+            }
+            .show()
+    }
+
+    companion object {
+        val TAG = SIPAccountCreationFragment::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java
index 3e8fda966..0d4a11e6b 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java
@@ -52,7 +52,9 @@ import cx.ring.utils.AndroidFileUtils;
 import net.jami.utils.Tuple;
 import cx.ring.views.CredentialPreferenceDialog;
 import cx.ring.views.CredentialsPreference;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class SecurityAccountFragment extends BasePreferenceFragment<SecurityAccountPresenter> implements SecurityAccountView {
     public static final String TAG = SecurityAccountFragment.class.getSimpleName();
 
@@ -63,18 +65,18 @@ public class SecurityAccountFragment extends BasePreferenceFragment<SecurityAcco
 
     private PreferenceCategory credentialsCategory;
     private PreferenceCategory tlsCategory;
-    private Preference.OnPreferenceChangeListener editCredentialListener = (preference, newValue) -> {
+    private final Preference.OnPreferenceChangeListener editCredentialListener = (preference, newValue) -> {
         // We need the old and new value to correctly edit the list of credentials
         Pair<AccountCredentials, AccountCredentials> result = (Pair<AccountCredentials, AccountCredentials>) newValue;
         presenter.credentialEdited(new Tuple<>(result.first, result.second));
         return false;
     };
-    private Preference.OnPreferenceChangeListener addCredentialListener = (preference, newValue) -> {
+    private final Preference.OnPreferenceChangeListener addCredentialListener = (preference, newValue) -> {
         Pair<AccountCredentials, AccountCredentials> result = (Pair<AccountCredentials, AccountCredentials>) newValue;
         presenter.credentialAdded(new Tuple<>(result.first, result.second));
         return false;
     };
-    private Preference.OnPreferenceClickListener filePickerListener = preference -> {
+    private final Preference.OnPreferenceClickListener filePickerListener = preference -> {
         if (preference.getKey().contentEquals(ConfigKey.TLS_CA_LIST_FILE.key())) {
             performFileSearch(SELECT_CA_LIST_RC);
         }
@@ -86,7 +88,7 @@ public class SecurityAccountFragment extends BasePreferenceFragment<SecurityAcco
         }
         return true;
     };
-    private Preference.OnPreferenceChangeListener tlsListener = (preference, newValue) -> {
+    private final Preference.OnPreferenceChangeListener tlsListener = (preference, newValue) -> {
         ConfigKey key = ConfigKey.fromString(preference.getKey());
 
         if (preference.getKey().contentEquals(ConfigKey.TLS_ENABLE.key())) {
@@ -110,8 +112,6 @@ public class SecurityAccountFragment extends BasePreferenceFragment<SecurityAcco
 
     @Override
     public void onCreatePreferences(Bundle bundle, String s) {
-        // dependency injection
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         super.onCreatePreferences(bundle, s);
 
         addPreferencesFromResource(R.xml.account_security_prefs);
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java
deleted file mode 100644
index 3671a9289..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.fragments;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.InflateException;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.recyclerview.widget.LinearLayoutManager;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.services.ContactService;
-import net.jami.smartlist.SmartListViewModel;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.adapters.SmartListAdapter;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.ConversationActivity;
-import cx.ring.databinding.FragSharewithBinding;
-import cx.ring.utils.ConversationPath;
-import cx.ring.viewholders.SmartListViewHolder;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class ShareWithFragment extends Fragment {
-    private final static String TAG = ShareWithFragment.class.getSimpleName();
-
-    private final CompositeDisposable mDisposable = new CompositeDisposable();
-
-    @Inject
-    @Singleton
-    ConversationFacade mConversationFacade;
-
-    @Inject
-    @Singleton
-    ContactService mContactService;
-
-    private Intent mPendingIntent = null;
-    private SmartListAdapter adapter;
-
-    private FragSharewithBinding binding;
-
-    /**
-     * Mandatory empty constructor for the fragment manager to instantiate the
-     * fragment (e.g. upon screen orientation changes).
-     */
-    public ShareWithFragment() {
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-    }
-
-    public static ShareWithFragment newInstance() {
-        return new ShareWithFragment();
-    }
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-                             @Nullable Bundle savedInstanceState) {
-        binding = FragSharewithBinding.inflate(inflater);
-
-        Context context = binding.getRoot().getContext();
-        Activity activity = getActivity();
-        if (activity instanceof AppCompatActivity) {
-            AppCompatActivity compatActivity = (AppCompatActivity) activity;
-            compatActivity.setSupportActionBar(binding.toolbar);
-            ActionBar ab = compatActivity.getSupportActionBar();
-            if (ab != null)
-                ab.setDisplayHomeAsUpEnabled(true);
-        }
-
-        if (mPendingIntent != null) {
-            String type = mPendingIntent.getType();
-            ClipData clip = mPendingIntent.getClipData();
-            if (type.startsWith("text/")) {
-                binding.previewText.setText(mPendingIntent.getStringExtra(Intent.EXTRA_TEXT));
-                binding.previewText.setVisibility(View.VISIBLE);
-            } else if (type.startsWith("image/")) {
-                Uri data = mPendingIntent.getData();
-                if (data == null && clip != null && clip.getItemCount() > 0)
-                    data = clip.getItemAt(0).getUri();
-                binding.previewImage.setImageURI(data);
-                binding.previewImage.setVisibility(View.VISIBLE);
-            } else if (type.startsWith("video/")) {
-                Uri data = mPendingIntent.getData();
-                if (data == null && clip != null && clip.getItemCount() > 0)
-                    data = clip.getItemAt(0).getUri();
-                try {
-                    binding.previewVideo.setVideoURI(data);
-                    binding.previewVideo.setVisibility(View.VISIBLE);
-                } catch (NullPointerException | InflateException | NumberFormatException e) {
-                    Log.e(TAG, e.getMessage());
-                }
-                binding.previewVideo.setOnCompletionListener(mediaPlayer -> binding.previewVideo.start());
-            }
-        }
-
-        adapter = new SmartListAdapter(null, new SmartListViewHolder.SmartListListeners() {
-            @Override
-            public void onItemClick(SmartListViewModel smartListViewModel) {
-                if (mPendingIntent != null) {
-                    Intent intent = mPendingIntent;
-                    mPendingIntent = null;
-                    String type = intent.getType();
-                    if (type != null && type.startsWith("text/")) {
-                        intent.putExtra(Intent.EXTRA_TEXT, binding.previewText.getText().toString());
-                    }
-                    intent.putExtras(ConversationPath.toBundle(smartListViewModel.getAccountId(), smartListViewModel.getUri()));
-                    intent.setClass(requireActivity(), ConversationActivity.class);
-                    startActivity(intent);
-                }
-            }
-
-            @Override
-            public void onItemLongClick(SmartListViewModel smartListViewModel) {
-
-            }
-        }, mDisposable);
-        binding.shareList.setLayoutManager(new LinearLayoutManager(context));
-        binding.shareList.setAdapter(adapter);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (mPendingIntent == null)
-            getActivity().finish();
-        mDisposable.add(mConversationFacade
-                .getCurrentAccountSubject()
-                .switchMap(a -> a.getConversationsViewModels(false))
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(list -> {
-                    if (adapter != null)
-                        adapter.update(list);
-                }));
-        if (binding != null && binding.previewVideo.getVisibility() != View.GONE) {
-            binding.previewVideo.start();
-        }
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mDisposable.clear();
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle bundle) {
-        super.onCreate(bundle);
-        /*Intent intent = getActivity().getIntent();
-        Bundle extra = intent.getExtras();
-        if (ConversationPath.fromBundle(extra) != null) {
-            intent.setClass(getActivity(), ConversationActivity.class);
-            startActivity(intent);
-            return;
-        }*/
-        mPendingIntent = getActivity().getIntent();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mPendingIntent = null;
-        adapter = null;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt
new file mode 100644
index 000000000..c17089713
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt
@@ -0,0 +1,183 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.fragments
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.InflateException
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import cx.ring.adapters.SmartListAdapter
+import cx.ring.client.ConversationActivity
+import cx.ring.databinding.FragSharewithBinding
+import cx.ring.utils.ConversationPath
+import cx.ring.viewholders.SmartListViewHolder.SmartListListeners
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.services.ConversationFacade
+import net.jami.model.Account
+import net.jami.services.ContactService
+import net.jami.smartlist.SmartListViewModel
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class ShareWithFragment : Fragment() {
+    private val mDisposable = CompositeDisposable()
+
+    @JvmField
+    @Inject
+    @Singleton
+    var mConversationFacade: ConversationFacade? = null
+
+    @JvmField
+    @Inject
+    @Singleton
+    var mContactService: ContactService? = null
+    private var mPendingIntent: Intent? = null
+    private var adapter: SmartListAdapter? = null
+    private var binding: FragSharewithBinding? = null
+    override fun onCreateView(
+        inflater: LayoutInflater, container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        binding = FragSharewithBinding.inflate(inflater)
+        val context = binding!!.root.context
+        val activity: Activity? = activity
+        if (activity is AppCompatActivity) {
+            activity.setSupportActionBar(binding!!.toolbar)
+            val ab = activity.supportActionBar
+            ab?.setDisplayHomeAsUpEnabled(true)
+        }
+        if (mPendingIntent != null) {
+            val type = mPendingIntent!!.type!!
+            val clip = mPendingIntent!!.clipData
+            when {
+                type.startsWith("text/") -> {
+                    binding!!.previewText.setText(mPendingIntent!!.getStringExtra(Intent.EXTRA_TEXT))
+                    binding!!.previewText.visibility = View.VISIBLE
+                }
+                type.startsWith("image/") -> {
+                    var data = mPendingIntent!!.data
+                    if (data == null && clip != null && clip.itemCount > 0) data = clip.getItemAt(0).uri
+                    binding!!.previewImage.setImageURI(data)
+                    binding!!.previewImage.visibility = View.VISIBLE
+                }
+                type.startsWith("video/") -> {
+                    var data = mPendingIntent!!.data
+                    if (data == null && clip != null && clip.itemCount > 0) data = clip.getItemAt(0).uri
+                    try {
+                        binding!!.previewVideo.setVideoURI(data)
+                        binding!!.previewVideo.visibility = View.VISIBLE
+                    } catch (e: NullPointerException) {
+                        Log.e(TAG, e.message!!)
+                    } catch (e: InflateException) {
+                        Log.e(TAG, e.message!!)
+                    } catch (e: NumberFormatException) {
+                        Log.e(TAG, e.message!!)
+                    }
+                    binding!!.previewVideo.setOnCompletionListener { binding!!.previewVideo.start() }
+                }
+            }
+        }
+        adapter = SmartListAdapter(null, object : SmartListListeners {
+            override fun onItemClick(smartListViewModel: SmartListViewModel) {
+                mPendingIntent?.let { intent ->
+                    mPendingIntent = null
+                    val type = intent.type
+                    if (type != null && type.startsWith("text/")) {
+                        intent.putExtra(Intent.EXTRA_TEXT, binding!!.previewText.text.toString())
+                    }
+                    intent.putExtras(
+                        ConversationPath.toBundle(
+                            smartListViewModel.accountId,
+                            smartListViewModel.uri
+                        )
+                    )
+                    intent.setClass(requireActivity(), ConversationActivity::class.java)
+                    startActivity(intent)
+                }
+            }
+
+            override fun onItemLongClick(smartListViewModel: SmartListViewModel) {}
+        }, mDisposable)
+        binding!!.shareList.layoutManager = LinearLayoutManager(context)
+        binding!!.shareList.adapter = adapter
+        return binding!!.root
+    }
+
+    override fun onStart() {
+        super.onStart()
+        if (mPendingIntent == null) requireActivity().finish()
+        mDisposable.add(mConversationFacade!!
+            .currentAccountSubject
+            .switchMap { a: Account -> a.getConversationsViewModels(false) }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { list: MutableList<SmartListViewModel> ->
+                adapter?.update(list)
+            })
+        if (binding != null && binding!!.previewVideo.visibility != View.GONE) {
+            binding!!.previewVideo.start()
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+        mDisposable.clear()
+    }
+
+    override fun onCreate(bundle: Bundle?) {
+        super.onCreate(bundle)
+        /*Intent intent = getActivity().getIntent();
+        Bundle extra = intent.getExtras();
+        if (ConversationPath.fromBundle(extra) != null) {
+            intent.setClass(getActivity(), ConversationActivity.class);
+            startActivity(intent);
+            return;
+        }*/mPendingIntent = requireActivity().intent
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mPendingIntent = null
+        adapter = null
+    }
+
+    companion object {
+        private val TAG = ShareWithFragment::class.java.simpleName
+
+        /**
+         * Mandatory empty constructor for the fragment manager to instantiate the
+         * fragment (e.g. upon screen orientation changes).
+         */
+        /*public ShareWithFragment() {
+        JamiApplication.getInstance().getInjectionComponent().inject(this);
+    }*/
+        fun newInstance(): ShareWithFragment {
+            return ShareWithFragment()
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
deleted file mode 100644
index d5a5288c8..000000000
--- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *           Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.fragments;
-
-import android.app.Activity;
-import android.app.SearchManager;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.InputType;
-import android.util.Log;
-import android.util.TypedValue;
-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.view.inputmethod.EditorInfo;
-import android.widget.EditText;
-import android.widget.RelativeLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.widget.SearchView;
-import androidx.appcompat.widget.Toolbar;
-import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
-import com.google.android.material.snackbar.Snackbar;
-
-import net.jami.model.Conversation;
-import net.jami.services.AccountService;
-import net.jami.smartlist.SmartListPresenter;
-import net.jami.smartlist.SmartListView;
-import net.jami.smartlist.SmartListViewModel;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.adapters.SmartListAdapter;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.databinding.FragSmartlistBinding;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.utils.ActionHelper;
-import cx.ring.utils.ClipboardHelper;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import cx.ring.viewholders.SmartListViewHolder;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class SmartListFragment extends BaseSupportFragment<SmartListPresenter> implements SearchView.OnQueryTextListener,
-        SmartListViewHolder.SmartListListeners,
-        Conversation.ConversationActionCallback,
-        SmartListView {
-    private static final String TAG = SmartListFragment.class.getSimpleName();
-    private static final String STATE_LOADING = TAG + ".STATE_LOADING";
-
-    private static final int SCROLL_DIRECTION_UP = -1;
-
-    @Inject
-    AccountService mAccountService;
-
-    private SmartListAdapter mSmartListAdapter;
-
-    private SearchView mSearchView = null;
-    private MenuItem mSearchMenuItem = null;
-    private MenuItem mDialpadMenuItem = null;
-    private FragSmartlistBinding binding;
-
-    @Override
-    public void onCreateOptionsMenu(final Menu menu, MenuInflater inflater) {
-        menu.clear();
-
-        inflater.inflate(R.menu.smartlist_menu, menu);
-        mSearchMenuItem = menu.findItem(R.id.menu_contact_search);
-        mDialpadMenuItem = menu.findItem(R.id.menu_contact_dial);
-        mSearchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
-            @Override
-            public boolean onMenuItemActionCollapse(MenuItem item) {
-                mDialpadMenuItem.setVisible(false);
-                binding.newconvFab.show();
-                setOverflowMenuVisible(menu, true);
-                changeSeparatorHeight(false);
-                binding.qrCode.setVisibility(View.GONE);
-                //binding.newGroup.setVisibility(View.GONE);
-                setTabletQRLayout(false);
-                return true;
-            }
-
-            @Override
-            public boolean onMenuItemActionExpand(MenuItem item) {
-                mDialpadMenuItem.setVisible(true);
-                binding.newconvFab.hide();
-                setOverflowMenuVisible(menu, false);
-                changeSeparatorHeight(true);
-                binding.qrCode.setVisibility(View.VISIBLE);
-                //binding.newGroup.setVisibility(View.VISIBLE);
-                setTabletQRLayout(true);
-                return true;
-            }
-        });
-
-        mSearchView = (SearchView) mSearchMenuItem.getActionView();
-        mSearchView.setOnQueryTextListener(this);
-        mSearchView.setQueryHint(getString(R.string.searchbar_hint));
-        mSearchView.setLayoutParams(new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.MATCH_PARENT));
-        mSearchView.setImeOptions(EditorInfo.IME_ACTION_GO);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            EditText editText = mSearchView.findViewById(R.id.search_src_text);
-            if (editText != null) {
-                editText.setAutofillHints(View.AUTOFILL_HINT_USERNAME);
-            }
-        }
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        Activity activity = getActivity();
-        Intent intent = activity == null ? null : activity.getIntent();
-        if (intent != null)
-            handleIntent(intent);
-    }
-
-    public void handleIntent(@NonNull Intent intent) {
-        if (mSearchView != null && intent.getAction() != null) {
-            switch (intent.getAction()) {
-                case Intent.ACTION_VIEW:
-                case Intent.ACTION_CALL:
-                    mSearchView.setQuery(intent.getDataString(), true);
-                    break;
-                case Intent.ACTION_DIAL:
-                    mSearchMenuItem.expandActionView();
-                    mSearchView.setQuery(intent.getDataString(), false);
-                    break;
-                case Intent.ACTION_SEARCH:
-                    mSearchMenuItem.expandActionView();
-                    mSearchView.setQuery(intent.getStringExtra(SearchManager.QUERY), true);
-                    break;
-                default:
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        int itemId = item.getItemId();
-        if (itemId == R.id.menu_contact_search) {
-            mSearchView.setInputType(EditorInfo.TYPE_CLASS_TEXT
-                    | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
-            );
-            return false;
-        } else if (itemId == R.id.menu_contact_dial) {
-            if (mSearchView.getInputType() == EditorInfo.TYPE_CLASS_PHONE) {
-                mSearchView.setInputType(EditorInfo.TYPE_CLASS_TEXT);
-                mDialpadMenuItem.setIcon(R.drawable.baseline_dialpad_24);
-            } else {
-                mSearchView.setInputType(EditorInfo.TYPE_CLASS_PHONE);
-                mDialpadMenuItem.setIcon(R.drawable.baseline_keyboard_24);
-            }
-            return true;
-        } else if (itemId == R.id.menu_settings) {
-            ((HomeActivity) requireActivity()).goToSettings();
-            return true;
-        } else if (itemId == R.id.menu_about) {
-            ((HomeActivity) requireActivity()).goToAbout();
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean onQueryTextSubmit(String query) {
-        // presenter.newContactClicked();
-        return true;
-    }
-
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        // if there's another fragment on top of this one, when a rotation is done, this fragment is destroyed and
-        // in the process of recreating it, as it is not shown on the top of the screen, the "onCreateView" method is never called, so the mLoader is null
-        if (binding != null)
-            outState.putBoolean(STATE_LOADING, binding.loadingIndicator.isShown());
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    public boolean onQueryTextChange(final String query) {
-        presenter.queryTextChanged(query);
-        return true;
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragSmartlistBinding.inflate(inflater, container, false);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        setHasOptionsMenu(true);
-        super.onViewCreated(view, savedInstanceState);
-
-        binding.qrCode.setOnClickListener(v -> presenter.clickQRSearch());
-        //binding.newGroup.setOnClickListener(v -> startNewGroup());
-
-        binding.confsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                boolean canScrollUp = recyclerView.canScrollVertically(SCROLL_DIRECTION_UP);
-                ExtendedFloatingActionButton btn = binding.newconvFab;
-                boolean isExtended = btn.isExtended();
-                if (dy > 0 && isExtended) {
-                    btn.shrink();
-                } else if ((dy < 0 || !canScrollUp) && !isExtended) {
-                    btn.extend();
-                }
-
-                HomeActivity activity = (HomeActivity) getActivity();
-                if (activity != null)
-                    activity.setToolbarElevation(canScrollUp);
-            }
-        });
-
-        DefaultItemAnimator animator = (DefaultItemAnimator) binding.confsList.getItemAnimator();
-        if (animator != null) {
-            animator.setSupportsChangeAnimations(false);
-        }
-
-        binding.newconvFab.setOnClickListener(v -> presenter.fabButtonClicked());
-    }
-
-    private void startNewGroup() {
-        ContactPickerFragment fragment = ContactPickerFragment.newInstance();
-        fragment.show(getParentFragmentManager(), ContactPickerFragment.TAG);
-        binding.qrCode.setVisibility(View.GONE);
-        //binding.newGroup.setVisibility(View.GONE);
-        setTabletQRLayout(false);
-    }
-
-    @Override
-    public void setLoading(final boolean loading) {
-        binding.loadingIndicator.setVisibility(loading ? View.VISIBLE : View.GONE);
-    }
-
-    /**
-     * Handles the visibility of some menus to hide / show the overflow menu
-     *
-     * @param menu    the menu containing the menuitems we need to access
-     * @param visible true to display the overflow menu, false otherwise
-     */
-    private void setOverflowMenuVisible(final Menu menu, boolean visible) {
-        if (null != menu) {
-            MenuItem overflowMenuItem = menu.findItem(R.id.menu_overflow);
-            if (null != overflowMenuItem) {
-                overflowMenuItem.setVisible(visible);
-            }
-        }
-    }
-
-    @Override
-    public void removeConversation(net.jami.model.Uri conversationUri) {
-        presenter.removeConversation(conversationUri);
-    }
-
-    @Override
-    public void clearConversation(net.jami.model.Uri callContact) {
-        presenter.clearConversation(callContact);
-    }
-
-    @Override
-    public void copyContactNumberToClipboard(String contactNumber) {
-        ClipboardHelper.copyToClipboard(requireContext(), contactNumber);
-        String snackbarText = getString(R.string.conversation_action_copied_peer_number_clipboard,
-                ActionHelper.getShortenedNumber(contactNumber));
-        Snackbar.make(binding.listCoordinator, snackbarText, Snackbar.LENGTH_LONG).show();
-    }
-
-    public void onFabButtonClicked() {
-        presenter.fabButtonClicked();
-    }
-
-    @Override
-    public void displayChooseNumberDialog(final CharSequence[] numbers) {
-        final Context context = requireContext();
-        new MaterialAlertDialogBuilder(context)
-                .setTitle(R.string.choose_number)
-                .setItems(numbers, (dialog, which) -> {
-                    CharSequence selected = numbers[which];
-                    Intent intent = new Intent(CallActivity.ACTION_CALL)
-                            .setClass(context, CallActivity.class)
-                            .setData(Uri.parse(selected.toString()));
-                    startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
-                })
-                .show();
-    }
-
-    @Override
-    public void displayNoConversationMessage() {
-        binding.placeholder.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    public void hideNoConversationMessage() {
-        binding.placeholder.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void displayConversationDialog(final SmartListViewModel smartListViewModel) {
-        if (smartListViewModel.isSwarm()) {
-            new MaterialAlertDialogBuilder(requireContext())
-                    .setItems(R.array.swarm_actions, (dialog, which) -> {
-                        switch (which) {
-                            case 0:
-                                presenter.copyNumber(smartListViewModel);
-                                break;
-                            case 1:
-                                presenter.removeConversation(smartListViewModel);
-                                break;
-                            case 2:
-                                presenter.banContact(smartListViewModel);
-                                break;
-                        }
-                    })
-                    .show();
-        } else {
-            new MaterialAlertDialogBuilder(requireContext())
-                    .setItems(R.array.conversation_actions, (dialog, which) -> {
-                        switch (which) {
-                            case ActionHelper.ACTION_COPY:
-                                presenter.copyNumber(smartListViewModel);
-                                break;
-                            case ActionHelper.ACTION_CLEAR:
-                                presenter.clearConversation(smartListViewModel);
-                                break;
-                            case ActionHelper.ACTION_DELETE:
-                                presenter.removeConversation(smartListViewModel);
-                                break;
-                            case ActionHelper.ACTION_BLOCK:
-                                presenter.banContact(smartListViewModel);
-                                break;
-                        }
-                    })
-                    .show();
-        }
-    }
-
-    @Override
-    public void displayClearDialog(net.jami.model.Uri uri) {
-        ActionHelper.launchClearAction(getActivity(), uri, SmartListFragment.this);
-    }
-
-    @Override
-    public void displayDeleteDialog(net.jami.model.Uri uri) {
-        ActionHelper.launchDeleteAction(getActivity(), uri, SmartListFragment.this);
-    }
-
-    @Override
-    public void copyNumber(net.jami.model.Uri uri) {
-        ActionHelper.launchCopyNumberToClipboardFromContact(getActivity(), uri, this);
-    }
-
-    @Override
-    public void displayMenuItem() {
-        if (mSearchMenuItem != null) {
-            mSearchMenuItem.expandActionView();
-        }
-    }
-
-    @Override
-    public void hideList() {
-        binding.confsList.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void updateList(@Nullable final List<SmartListViewModel> smartListViewModels, CompositeDisposable parentDisposable) {
-        if (binding == null)
-            return;
-        if (binding.confsList.getAdapter() == null) {
-            mSmartListAdapter = new SmartListAdapter(smartListViewModels, SmartListFragment.this, parentDisposable);
-            binding.confsList.setAdapter(mSmartListAdapter);
-            binding.confsList.setHasFixedSize(true);
-            LinearLayoutManager llm = new LinearLayoutManager(getActivity());
-            llm.setOrientation(RecyclerView.VERTICAL);
-            binding.confsList.setLayoutManager(llm);
-        } else {
-            mSmartListAdapter.update(smartListViewModels);
-        }
-        binding.confsList.setVisibility(View.VISIBLE);
-    }
-
-    @Override
-    public void update(int position) {
-        Log.w(TAG, "update " + position + " " + mSmartListAdapter);
-        if (mSmartListAdapter != null) {
-            mSmartListAdapter.notifyItemChanged(position);
-        }
-    }
-
-    @Override
-    public void update(SmartListViewModel model) {
-        if (mSmartListAdapter != null)
-            mSmartListAdapter.update(model);
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        if (requestCode == HomeActivity.REQUEST_CODE_QR_CONVERSATION && data != null && resultCode == Activity.RESULT_OK) {
-            String contactId = data.getStringExtra(ConversationPath.KEY_CONVERSATION_URI);
-            if (contactId != null) {
-                presenter.startConversation(net.jami.model.Uri.fromString(contactId));
-            }
-        }
-    }
-
-    @Override
-    public void goToConversation(String accountId, net.jami.model.Uri conversationUri) {
-        Log.w(TAG, "goToConversation " + accountId + " " + conversationUri);
-        if (mSearchMenuItem != null) {
-            mSearchMenuItem.collapseActionView();
-        }
-        ((HomeActivity) requireActivity()).startConversation(accountId, conversationUri);
-    }
-
-    @Override
-    public void goToCallActivity(String accountId, net.jami.model.Uri conversationUri, String contactId) {
-        Intent intent = new Intent(CallActivity.ACTION_CALL)
-                .setClass(requireContext(), CallActivity.class)
-                .putExtras(ConversationPath.toBundle(accountId, conversationUri))
-                .putExtra(CallFragment.KEY_AUDIO_ONLY, false)
-                .putExtra(Intent.EXTRA_PHONE_NUMBER, contactId);
-        startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL);
-    }
-
-    @Override
-    public void goToQRFragment() {
-        QRCodeFragment qrCodeFragment = QRCodeFragment.newInstance(QRCodeFragment.INDEX_SCAN);
-        qrCodeFragment.show(getParentFragmentManager(), QRCodeFragment.TAG);
-        binding.qrCode.setVisibility(View.GONE);
-        //binding.newGroup.setVisibility(View.GONE);
-        setTabletQRLayout(false);
-    }
-
-    @Override
-    public void scrollToTop() {
-        if (binding != null)
-            binding.confsList.scrollToPosition(0);
-    }
-
-    @Override
-    public void onItemClick(SmartListViewModel smartListViewModel) {
-        presenter.conversationClicked(smartListViewModel);
-    }
-
-    @Override
-    public void onItemLongClick(SmartListViewModel smartListViewModel) {
-        presenter.conversationLongClicked(smartListViewModel);
-    }
-
-    private void changeSeparatorHeight(boolean open) {
-        if (binding == null || binding.separator == null)
-            return;
-
-        if (DeviceUtils.isTablet(binding.getRoot().getContext())) {
-            int margin = 0;
-
-            if (open) {
-                Activity activity = getActivity();
-                if (activity != null) {
-                    Toolbar toolbar = activity.findViewById(R.id.main_toolbar);
-                    if (toolbar != null)
-                        margin = toolbar.getHeight();
-                }
-            }
-
-            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.separator.getLayoutParams();
-            params.topMargin = margin;
-            binding.separator.setLayoutParams(params);
-        }
-    }
-
-    private void setTabletQRLayout(boolean show) {
-        Context context = requireContext();
-        if (!DeviceUtils.isTablet(context))
-            return;
-
-        RelativeLayout.LayoutParams params =
-                (RelativeLayout.LayoutParams) binding.listCoordinator.getLayoutParams();
-        if (show) {
-            params.addRule(RelativeLayout.BELOW, R.id.qr_code);
-            params.topMargin = 0;
-        } else {
-            params.removeRule(RelativeLayout.BELOW);
-            TypedValue value = new TypedValue();
-            if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, value, true)) {
-                params.topMargin = TypedValue.complexToDimensionPixelSize(value.data, context.getResources().getDisplayMetrics());
-            }
-        }
-        binding.listCoordinator.setLayoutParams(params);
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt
new file mode 100644
index 000000000..d797f6f46
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt
@@ -0,0 +1,424 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *           Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.fragments
+
+import android.app.Activity
+import android.app.SearchManager
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.text.InputType
+import android.util.Log
+import android.util.TypedValue
+import android.view.*
+import android.view.inputmethod.EditorInfo
+import android.widget.EditText
+import android.widget.RelativeLayout
+import androidx.appcompat.widget.SearchView
+import androidx.appcompat.widget.Toolbar
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.adapters.SmartListAdapter
+import cx.ring.client.CallActivity
+import cx.ring.client.HomeActivity
+import cx.ring.databinding.FragSmartlistBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.utils.ActionHelper
+import cx.ring.utils.ClipboardHelper
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.DeviceUtils
+import cx.ring.viewholders.SmartListViewHolder.SmartListListeners
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.model.Conversation.ConversationActionCallback
+import net.jami.model.Uri
+import net.jami.smartlist.SmartListPresenter
+import net.jami.smartlist.SmartListView
+import net.jami.smartlist.SmartListViewModel
+
+@AndroidEntryPoint
+class SmartListFragment : BaseSupportFragment<SmartListPresenter, SmartListView>(),
+    SearchView.OnQueryTextListener, SmartListListeners, ConversationActionCallback, SmartListView {
+    private var mSmartListAdapter: SmartListAdapter? = null
+    private var mSearchView: SearchView? = null
+    private var mSearchMenuItem: MenuItem? = null
+    private var mDialpadMenuItem: MenuItem? = null
+    private var binding: FragSmartlistBinding? = null
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        menu.clear()
+        inflater.inflate(R.menu.smartlist_menu, menu)
+        val searchMenuItem = menu.findItem(R.id.menu_contact_search)
+        val dialpadMenuItem = menu.findItem(R.id.menu_contact_dial)
+        searchMenuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
+            override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
+                dialpadMenuItem.isVisible = false
+                binding!!.newconvFab.show()
+                setOverflowMenuVisible(menu, true)
+                changeSeparatorHeight(false)
+                binding!!.qrCode.visibility = View.GONE
+                //binding.newGroup.setVisibility(View.GONE);
+                setTabletQRLayout(false)
+                return true
+            }
+
+            override fun onMenuItemActionExpand(item: MenuItem): Boolean {
+                dialpadMenuItem.isVisible = true
+                binding!!.newconvFab.hide()
+                setOverflowMenuVisible(menu, false)
+                changeSeparatorHeight(true)
+                binding!!.qrCode.visibility = View.VISIBLE
+                //binding.newGroup.setVisibility(View.VISIBLE);
+                setTabletQRLayout(true)
+                return true
+            }
+        })
+        val searchView = searchMenuItem.actionView as SearchView
+        searchView.setOnQueryTextListener(this)
+        searchView.queryHint = getString(R.string.searchbar_hint)
+        searchView.layoutParams = Toolbar.LayoutParams(
+            Toolbar.LayoutParams.WRAP_CONTENT,
+            Toolbar.LayoutParams.MATCH_PARENT
+        )
+        searchView.imeOptions = EditorInfo.IME_ACTION_GO
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val editText = searchView.findViewById<EditText>(R.id.search_src_text)
+            editText?.setAutofillHints(View.AUTOFILL_HINT_USERNAME)
+        }
+        mSearchMenuItem = searchMenuItem
+        mDialpadMenuItem = dialpadMenuItem
+        mSearchView = searchView
+    }
+
+    override fun onStart() {
+        super.onStart()
+        activity?.intent?.let { handleIntent(it) }
+    }
+
+    fun handleIntent(intent: Intent) {
+        if (mSearchView != null && intent.action != null) {
+            when (intent.action) {
+                Intent.ACTION_VIEW, Intent.ACTION_CALL -> mSearchView!!.setQuery(intent.dataString, true)
+                Intent.ACTION_DIAL -> {
+                    mSearchMenuItem?.expandActionView()
+                    mSearchView?.setQuery(intent.dataString, false)
+                }
+                Intent.ACTION_SEARCH -> {
+                    mSearchMenuItem?.expandActionView()
+                    mSearchView?.setQuery(intent.getStringExtra(SearchManager.QUERY), true)
+                }
+                else -> {}
+            }
+        }
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.menu_contact_search -> {
+                mSearchView!!.inputType = (EditorInfo.TYPE_CLASS_TEXT
+                        or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+                        )
+                return false
+            }
+            R.id.menu_contact_dial -> {
+                if (mSearchView!!.inputType == EditorInfo.TYPE_CLASS_PHONE) {
+                    mSearchView!!.inputType = EditorInfo.TYPE_CLASS_TEXT
+                    mDialpadMenuItem!!.setIcon(R.drawable.baseline_dialpad_24)
+                } else {
+                    mSearchView!!.inputType = EditorInfo.TYPE_CLASS_PHONE
+                    mDialpadMenuItem!!.setIcon(R.drawable.baseline_keyboard_24)
+                }
+                return true
+            }
+            R.id.menu_settings -> {
+                (requireActivity() as HomeActivity).goToSettings()
+                return true
+            }
+            R.id.menu_about -> {
+                (requireActivity() as HomeActivity).goToAbout()
+                return true
+            }
+            else -> return false
+        }
+    }
+
+    override fun onQueryTextSubmit(query: String): Boolean {
+        // presenter.newContactClicked();
+        return true
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        binding?.apply { outState.putBoolean(STATE_LOADING, loadingIndicator.isShown) }
+        super.onSaveInstanceState(outState)
+    }
+
+    override fun onQueryTextChange(query: String): Boolean {
+        presenter.queryTextChanged(query)
+        return true
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        setHasOptionsMenu(true)
+        return FragSmartlistBinding.inflate(inflater, container, false).apply {
+            qrCode.setOnClickListener { presenter.clickQRSearch() }
+            newconvFab.setOnClickListener { presenter.fabButtonClicked() }
+            confsList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+                    val canScrollUp = recyclerView.canScrollVertically(SCROLL_DIRECTION_UP)
+                    val isExtended = newconvFab.isExtended
+                    if (dy > 0 && isExtended) {
+                        newconvFab.shrink()
+                    } else if ((dy < 0 || !canScrollUp) && !isExtended) {
+                        newconvFab.extend()
+                    }
+                    (activity as HomeActivity?)?.setToolbarElevation(canScrollUp)
+                }
+            })
+            (confsList.itemAnimator as DefaultItemAnimator?)?.supportsChangeAnimations = false
+            binding = this
+        }.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+    }
+
+    private fun startNewGroup() {
+        val fragment = ContactPickerFragment.newInstance()
+        fragment.show(parentFragmentManager, ContactPickerFragment.TAG)
+        binding!!.qrCode.visibility = View.GONE
+        //binding.newGroup.setVisibility(View.GONE);
+        setTabletQRLayout(false)
+    }
+
+    override fun setLoading(loading: Boolean) {
+        binding!!.loadingIndicator.visibility = if (loading) View.VISIBLE else View.GONE
+    }
+
+    /**
+     * Handles the visibility of some menus to hide / show the overflow menu
+     *
+     * @param menu    the menu containing the menuitems we need to access
+     * @param visible true to display the overflow menu, false otherwise
+     */
+    private fun setOverflowMenuVisible(menu: Menu?, visible: Boolean) {
+        menu?.findItem(R.id.menu_overflow)?.isVisible = visible
+    }
+
+    override fun removeConversation(conversationUri: Uri) {
+        presenter.removeConversation(conversationUri)
+    }
+
+    override fun clearConversation(callContact: Uri) {
+        presenter.clearConversation(callContact)
+    }
+
+    override fun copyContactNumberToClipboard(contactNumber: String) {
+        ClipboardHelper.copyToClipboard(requireContext(), contactNumber)
+        val snackbarText = getString(
+            R.string.conversation_action_copied_peer_number_clipboard,
+            ActionHelper.getShortenedNumber(contactNumber)
+        )
+        Snackbar.make(binding!!.listCoordinator, snackbarText, Snackbar.LENGTH_LONG).show()
+    }
+
+    fun onFabButtonClicked() {
+        presenter.fabButtonClicked()
+    }
+
+    override fun displayChooseNumberDialog(numbers: Array<CharSequence>) {
+        val context = requireContext()
+        MaterialAlertDialogBuilder(context)
+            .setTitle(R.string.choose_number)
+            .setItems(numbers) { _: DialogInterface?, which: Int ->
+                val selected = numbers[which]
+                val intent = Intent(CallActivity.ACTION_CALL)
+                    .setClass(context, CallActivity::class.java)
+                    .setData(android.net.Uri.parse(selected.toString()))
+                startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL)
+            }
+            .show()
+    }
+
+    override fun displayNoConversationMessage() {
+        binding!!.placeholder.visibility = View.VISIBLE
+    }
+
+    override fun hideNoConversationMessage() {
+        binding!!.placeholder.visibility = View.GONE
+    }
+
+    override fun displayConversationDialog(smartListViewModel: SmartListViewModel) {
+        if (smartListViewModel.isSwarm) {
+            MaterialAlertDialogBuilder(requireContext())
+                .setItems(R.array.swarm_actions) { dialog, which ->
+                    when (which) {
+                        0 -> presenter.copyNumber(smartListViewModel)
+                        1 -> presenter.removeConversation(smartListViewModel)
+                        2 -> presenter.banContact(smartListViewModel)
+                    }
+                }
+                .show()
+        } else {
+            MaterialAlertDialogBuilder(requireContext())
+                .setItems(R.array.conversation_actions) { dialog, which ->
+                    when (which) {
+                        ActionHelper.ACTION_COPY -> presenter.copyNumber(smartListViewModel)
+                        ActionHelper.ACTION_CLEAR -> presenter.clearConversation(smartListViewModel)
+                        ActionHelper.ACTION_DELETE -> presenter.removeConversation(smartListViewModel)
+                        ActionHelper.ACTION_BLOCK -> presenter.banContact(smartListViewModel)
+                    }
+                }
+                .show()
+        }
+    }
+
+    override fun displayClearDialog(uri: Uri) {
+        ActionHelper.launchClearAction(activity, uri, this@SmartListFragment)
+    }
+
+    override fun displayDeleteDialog(uri: Uri) {
+        ActionHelper.launchDeleteAction(activity, uri, this@SmartListFragment)
+    }
+
+    override fun copyNumber(uri: Uri) {
+        ActionHelper.launchCopyNumberToClipboardFromContact(activity, uri, this)
+    }
+
+    override fun displayMenuItem() {
+        mSearchMenuItem?.expandActionView()
+    }
+
+    override fun hideList() {
+        binding!!.confsList.visibility = View.GONE
+        mSmartListAdapter?.update(null)
+    }
+
+    override fun updateList(smartListViewModels: MutableList<SmartListViewModel>?, parentDisposable: CompositeDisposable) {
+        binding?.apply {
+            if (confsList.adapter == null) {
+                confsList.adapter = SmartListAdapter(smartListViewModels, this@SmartListFragment, parentDisposable).apply {
+                    mSmartListAdapter = this
+                }
+                confsList.setHasFixedSize(true)
+                confsList.layoutManager = LinearLayoutManager(requireContext()).apply {
+                    orientation = RecyclerView.VERTICAL
+                }
+            } else {
+                mSmartListAdapter?.update(smartListViewModels)
+            }
+            confsList.visibility = View.VISIBLE
+        }
+    }
+
+    override fun update(position: Int) {
+        Log.w(TAG, "update $position $mSmartListAdapter")
+        mSmartListAdapter?.notifyItemChanged(position)
+    }
+
+    override fun update(model: SmartListViewModel) {
+        mSmartListAdapter?.update(model)
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == HomeActivity.REQUEST_CODE_QR_CONVERSATION && data != null && resultCode == Activity.RESULT_OK) {
+            val contactId = data.getStringExtra(ConversationPath.KEY_CONVERSATION_URI)
+            if (contactId != null) {
+                presenter.startConversation(Uri.fromString(contactId))
+            }
+        }
+    }
+
+    override fun goToConversation(accountId: String, conversationUri: Uri) {
+        Log.w(TAG, "goToConversation $accountId $conversationUri")
+        mSearchMenuItem?.collapseActionView()
+        (requireActivity() as HomeActivity).startConversation(accountId, conversationUri)
+    }
+
+    override fun goToCallActivity(accountId: String, conversationUri: Uri, contactId: String) {
+        val intent = Intent(CallActivity.ACTION_CALL)
+            .setClass(requireContext(), CallActivity::class.java)
+            .putExtras(ConversationPath.toBundle(accountId, conversationUri))
+            .putExtra(CallFragment.KEY_AUDIO_ONLY, false)
+            .putExtra(Intent.EXTRA_PHONE_NUMBER, contactId)
+        startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL)
+    }
+
+    override fun goToQRFragment() {
+        val qrCodeFragment = QRCodeFragment.newInstance(QRCodeFragment.INDEX_SCAN)
+        qrCodeFragment.show(parentFragmentManager, QRCodeFragment.TAG)
+        binding!!.qrCode.visibility = View.GONE
+        //binding.newGroup.setVisibility(View.GONE);
+        setTabletQRLayout(false)
+    }
+
+    override fun scrollToTop() {
+        binding?.apply { confsList.scrollToPosition(0) }
+    }
+
+    override fun onItemClick(smartListViewModel: SmartListViewModel) {
+        presenter.conversationClicked(smartListViewModel)
+    }
+
+    override fun onItemLongClick(smartListViewModel: SmartListViewModel) {
+        presenter.conversationLongClicked(smartListViewModel)
+    }
+
+    private fun changeSeparatorHeight(open: Boolean) {
+        binding?.let { binding -> binding.separator?.let { separator ->
+            if (DeviceUtils.isTablet(binding.root.context)) {
+                val params = separator.layoutParams as RelativeLayout.LayoutParams
+                params.topMargin = if (open) activity?.findViewById<Toolbar>(R.id.main_toolbar)?.height ?: 0 else 0
+                separator.layoutParams = params
+            }
+        }}
+    }
+
+    private fun setTabletQRLayout(show: Boolean) {
+        val context = requireContext()
+        if (!DeviceUtils.isTablet(context)) return
+        val params = binding!!.listCoordinator.layoutParams as RelativeLayout.LayoutParams
+        if (show) {
+            params.addRule(RelativeLayout.BELOW, R.id.qr_code)
+            params.topMargin = 0
+        } else {
+            params.removeRule(RelativeLayout.BELOW)
+            val value = TypedValue()
+            if (context.theme.resolveAttribute(android.R.attr.actionBarSize, value, true)) {
+                params.topMargin = TypedValue.complexToDimensionPixelSize(value.data, context.resources.displayMetrics)
+            }
+        }
+        binding!!.listCoordinator.layoutParams = params
+    }
+
+    companion object {
+        private val TAG = SmartListFragment::class.simpleName!!
+        private val STATE_LOADING = "$TAG.STATE_LOADING"
+        private const val SCROLL_DIRECTION_UP = -1
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java b/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java
deleted file mode 100644
index c4ca6d0ed..000000000
--- a/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.history;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.util.Log;
-
-import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
-import com.j256.ormlite.dao.Dao;
-import com.j256.ormlite.support.ConnectionSource;
-import com.j256.ormlite.table.TableUtils;
-
-import java.sql.SQLException;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-import cx.ring.application.JamiApplication;
-import net.jami.model.ConversationHistory;
-import net.jami.model.DataTransfer;
-import net.jami.model.Interaction;
-import net.jami.services.HistoryService;
-
-/*
- * Database History Version
- * 7 : changing columns names. See https://gerrit-ring.savoirfairelinux.com/#/c/4297
- * 10: Switches to per account database system and implements new interaction and conversations table.
- */
-
-/**
- * Database helper class used to manage the creation and upgrading of your database. This class also usually provides
- * the DAOs used by the other classes.
- */
-public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
-    private static final String TAG = DatabaseHelper.class.getSimpleName();
-    // any time you make changes to your database objects, you may have to increase the database version
-    private static final int DATABASE_VERSION = 10;
-
-    private Dao<Interaction, Integer> interactionDataDao = null;
-    private Dao<ConversationHistory, Integer> conversationDataDao = null;
-
-    @Inject
-    HistoryService mHistoryService;
-
-    public DatabaseHelper(Context context, String dbDirectory) {
-        super(context, dbDirectory, null, DATABASE_VERSION);
-        Log.d(TAG, "Helper initialized for " + dbDirectory);
-        ((JamiApplication) context.getApplicationContext()).getInjectionComponent().inject(this);
-    }
-
-    /**
-     * This is called when the database is first created. Usually you should call createTable statements here to create
-     * the tables that will store your data.
-     */
-    @Override
-    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
-        try {
-            db.beginTransaction();
-            try {
-                TableUtils.createTable(connectionSource, ConversationHistory.class);
-                TableUtils.createTable(connectionSource, Interaction.class);
-                db.setTransactionSuccessful();
-            } catch (SQLException e) {
-                Log.e(TAG, "Can't create database", e);
-                throw new RuntimeException(e);
-            }
-        } finally {
-            db.endTransaction();
-        }
-    }
-
-    /**
-     * This is called when your application is upgraded and it has a higher version number. This allows you to adjust
-     * the various data to match the new version number.
-     */
-    @Override
-    public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
-        Log.i(TAG, "onUpgrade " + oldVersion + " -> " + newVersion);
-        try {
-            // if we are under version 10, it must first wait for account splitting to be complete which occurs in history service
-            if (oldVersion >= 10)
-                updateDatabase(oldVersion, db, connectionSource);
-        } catch (SQLException exc) {
-            exc.printStackTrace();
-            clearDatabase(db);
-            onCreate(db, connectionSource);
-        }
-    }
-
-    /**
-     * Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
-     * value.
-     */
-
-    public Dao<Interaction, Integer> getInteractionDataDao() throws SQLException {
-        if (interactionDataDao == null) {
-            interactionDataDao = getDao(Interaction.class);
-        }
-        return interactionDataDao;
-    }
-
-    public Dao<ConversationHistory, Integer> getConversationDataDao() throws SQLException {
-        if (conversationDataDao == null) {
-            conversationDataDao = getDao(ConversationHistory.class);
-        }
-        return conversationDataDao;
-    }
-
-    /**
-     * Close the database connections and clear any cached DAOs.
-     */
-    @Override
-    public void close() {
-        super.close();
-        interactionDataDao = null;
-        conversationDataDao = null;
-    }
-
-    /**
-     * Main method to update the database from an old version to the last
-     *
-     * @param fromDatabaseVersion the old version of the database
-     * @param db                  the SQLiteDatabase to work with
-     * @throws SQLiteException database has failed to update to the last version
-     */
-    private void updateDatabase(int fromDatabaseVersion, SQLiteDatabase db, ConnectionSource connectionSource) throws SQLException {
-        try {
-            while (fromDatabaseVersion < DATABASE_VERSION) {
-                switch (fromDatabaseVersion) {
-                    case 6:
-                        updateDatabaseFrom6(db);
-                        break;
-                    case 7:
-                        updateDatabaseFrom7(db);
-                        break;
-                    case 8:
-                        updateDatabaseFrom8(connectionSource);
-                        break;
-                    case 9:
-                        updateDatabaseFrom9(db);
-                        break;
-                }
-                fromDatabaseVersion++;
-            }
-            Log.d(TAG, "updateDatabase: Database has been updated to the last version.");
-        } catch (SQLException exc) {
-            Log.e(TAG, "updateDatabase: Database has failed to update to the last version.");
-            throw exc;
-        }
-    }
-
-    /**
-     * Executes the migration from the database version 6 to the next
-     *
-     * @param db the SQLiteDatabase to work with
-     * @throws SQLiteException migration from database version 6 to next, failed
-     */
-    private void updateDatabaseFrom6(SQLiteDatabase db) throws SQLiteException {
-        if (db != null && db.isOpen()) {
-            try {
-                Log.d(TAG, "updateDatabaseFrom6: Will begin migration from database version 6 to next.");
-                db.beginTransaction();
-                //~ Create the new historyCall table and int index
-                db.execSQL("CREATE TABLE IF NOT EXISTS `historycall` (`accountID` VARCHAR , `callID` VARCHAR , " +
-                        "`call_end` BIGINT , `TIMESTAMP_START` BIGINT , `contactID` BIGINT , " +
-                        "`contactKey` VARCHAR , `direction` INTEGER , `missed` SMALLINT , " +
-                        "`number` VARCHAR , `recordPath` VARCHAR ) ;");
-                db.execSQL("CREATE INDEX IF NOT EXISTS `historycall_TIMESTAMP_START_idx` ON `historycall` " +
-                        "( `TIMESTAMP_START` );");
-                //~ Create the new historyText table and int indexes
-                db.execSQL("CREATE TABLE IF NOT EXISTS `historytext` (`accountID` VARCHAR , `callID` VARCHAR , " +
-                        "`contactID` BIGINT , `contactKey` VARCHAR , `direction` INTEGER , " +
-                        "`id` BIGINT , `message` VARCHAR , `number` VARCHAR , `read` SMALLINT , " +
-                        "`TIMESTAMP` BIGINT , PRIMARY KEY (`id`) );");
-                db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_TIMESTAMP_idx` ON `historytext` ( `TIMESTAMP` );");
-                db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_id_idx` ON `historytext` ( `id` );");
-
-                try (Cursor hasATable = db.rawQuery("SELECT name FROM sqlite_master WHERE type=? AND name=?;",
-                        new String[]{"table", "a"})) {
-                    if (hasATable.getCount() > 0) {
-                        //~ Copying data from the old table "a"
-                        db.execSQL("INSERT INTO `historycall` (TIMESTAMP_START, call_end, number, missed," +
-                                "direction, recordPath, accountID, contactID, contactKey, callID) " +
-                                "SELECT TIMESTAMP_START,b,c,d,e,f,g,h,i,j FROM a;");
-                        db.execSQL("DROP TABLE IF EXISTS a_TIMESTAMP_START_idx;");
-                        db.execSQL("DROP TABLE a;");
-                    }
-                }
-
-                try (Cursor hasETable = db.rawQuery("SELECT name FROM sqlite_master WHERE type=? AND name=?;",
-                        new String[]{"table", "e"})) {
-                    if (hasETable.getCount() > 0) {
-                        //~ Copying data from the old table "e"
-                        db.execSQL("INSERT INTO historytext (id, TIMESTAMP, number, direction, accountID," +
-                                "contactID, contactKey, callID, message, read) " +
-                                "SELECT id,TIMESTAMP,c,d,e,f,g,h,i,j FROM e;");
-                        //~ Remove old tables "a" and "e"
-                        db.execSQL("DROP TABLE IF EXISTS e_TIMESTAMP_idx;");
-                        db.execSQL("DROP TABLE IF EXISTS e_id_idx;");
-                        db.execSQL("DROP TABLE e;");
-                    }
-                }
-
-                db.setTransactionSuccessful();
-                db.endTransaction();
-                Log.d(TAG, "updateDatabaseFrom6: Migration from database version 6 to next, done.");
-            } catch (SQLiteException exception) {
-                Log.e(TAG, "updateDatabaseFrom6: Migration from database version 6 to next, failed.");
-                throw exception;
-            }
-        }
-    }
-
-    private void updateDatabaseFrom7(SQLiteDatabase db) throws SQLiteException {
-        if (db != null && db.isOpen()) {
-            try {
-                Log.d(TAG, "updateDatabaseFrom7: Will begin migration from database version 7 to next.");
-                db.beginTransaction();
-                db.execSQL("ALTER TABLE historytext ADD COLUMN state VARCHAR DEFAULT ''");
-                db.setTransactionSuccessful();
-                db.endTransaction();
-                Log.d(TAG, "updateDatabaseFrom7: Migration from database version 7 to next, done.");
-            } catch (SQLiteException exception) {
-                Log.e(TAG, "updateDatabaseFrom7: Migration from database version 7 to next, failed.");
-                throw exception;
-            }
-        }
-    }
-
-    private void updateDatabaseFrom8(ConnectionSource connectionSource) throws SQLException {
-        try {
-            TableUtils.createTable(connectionSource, DataTransfer.class);
-            Log.d(TAG, "Migration from database version 8 to next, done.");
-        } catch (SQLException e) {
-            Log.e(TAG, "Migration from database version 8 to next, failed.", e);
-            throw e;
-        }
-    }
-
-    /**
-     * This updates the database to version 10 which includes the switch to interaction and conversation tables.
-     * It will delete previous tables.
-     *
-     * @param db the database to migrate
-     * @throws SQLiteException
-     */
-    private void updateDatabaseFrom9(SQLiteDatabase db) throws SQLiteException {
-        if (db != null && db.isOpen()) {
-            try {
-                Log.d(TAG, "updateDatabaseFrom9: Will begin migration from database version 9 to next for db: " + db.getPath());
-                db.beginTransaction();
-
-                // removing ring prefix from both call and text database
-
-                // where clause improves performance (not required)
-
-                db.execSQL("UPDATE historytext \n" +
-                        "SET number = replace( number, 'ring:', '' )\n" +
-                        "WHERE number LIKE 'ring:%'");
-
-                db.execSQL("UPDATE historycall \n" +
-                        "SET number = replace( number, 'ring:', '' )\n" +
-                        "WHERE number LIKE 'ring:%'");
-
-                // populating conversations table
-
-                db.execSQL("INSERT INTO conversations (participant)\n" +
-                        "SELECT DISTINCT historytext.number\n" +
-                        "FROM   historytext \n" +
-                        "       LEFT JOIN historycall \n" +
-                        "\t   ON historycall.number = historytext.number\n" +
-                        "UNION \n" +
-                        "SELECT DISTINCT historycall.number\n" +
-                        "FROM   historycall\n" +
-                        "       LEFT JOIN historytext\n" +
-                        "\t   ON historytext.number = historycall.number\n" +
-                        "UNION\n" +
-                        "SELECT DISTINCT historydata.peerId\n" +
-                        "FROM   historydata\n" +
-                        "       LEFT JOIN historytext\n" +
-                        "\t   ON historytext.number = historydata.peerId");
-
-
-
-                // DATA TRANSFER TABLE
-
-                // Data transfer migration is done first as we maintain the same ID's as in the previous database
-
-                // updating the statuses to the new schema
-
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_CREATED' WHERE dataTransferEventCode='CREATED'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ERROR' WHERE dataTransferEventCode='UNSUPPORTED'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_AWAITING_PEER' WHERE dataTransferEventCode='WAIT_PEER_ACCEPTANCE'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_AWAITING_HOST' WHERE dataTransferEventCode='WAIT_HOST_ACCEPTANCE'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ONGOING' WHERE dataTransferEventCode='ONGOING'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_FINISHED' WHERE dataTransferEventCode='FINISHED'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='CLOSED_BY_HOST'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='CLOSED_BY_PEER'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ERROR' WHERE dataTransferEventCode='INVALID_PATHNAME'");
-                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='UNJOINABLE_PEER'");
-
-                // migration
-
-                db.execSQL("INSERT INTO interactions (id, author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT historydata.id, historydata.peerId, null, historydata.displayName, 1, historydata.dataTransferEventCode, historydata.TIMESTAMP, 'DATA_TRANSFER', '{}', conversations.id\n" +
-                        "FROM historydata\n" +
-                        "JOIN conversations ON conversations.participant = historydata.peerId\n" +
-                        "WHERE isOutgoing = 0\n"
-                );
-
-                db.execSQL("INSERT INTO interactions (id, author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT historydata.id, null, null, historydata.displayName, 1, historydata.dataTransferEventCode, historydata.TIMESTAMP, 'DATA_TRANSFER', '{}', conversations.id\n" +
-                        "FROM historydata\n" +
-                        "JOIN conversations ON conversations.participant = historydata.peerId\n" +
-                        "WHERE isOutgoing = 1\n"
-                );
-
-
-                // MESSAGE TABLE
-
-                // updating status in text message table
-
-                db.execSQL("UPDATE historytext SET state='SUCCESS' WHERE state='SENT'");
-                db.execSQL("UPDATE historytext SET state='SUCCESS' WHERE state='READ'");
-                db.execSQL("UPDATE historytext SET state='FAILURE' WHERE state='FAILURE'");
-                db.execSQL("UPDATE historytext SET state='INVALID' WHERE state= null");
-
-                // migration
-
-                // divided into two similar functions, where author needs to be null in case of outgoing message
-
-                db.execSQL("INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT null, historytext.id, historytext.message, historytext.read, historytext.state, historytext.TIMESTAMP, 'TEXT','{}', conversations.id\n" +
-                        "FROM historytext\n" +
-                        "JOIN conversations ON conversations.participant = historytext.number\n" +
-                        "WHERE direction = 2\n"
-                );
-
-                db.execSQL("INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT historytext.number, historytext.id, historytext.message, historytext.read, historytext.state, historytext.TIMESTAMP, 'TEXT','{}', conversations.id\n" +
-                        "FROM historytext\n" +
-                        "JOIN conversations ON conversations.participant = historytext.number\n" +
-                        "WHERE direction = 1\n"
-                );
-
-
-                // CALL TABLE
-
-                // setting the timestamp end to the duration string before migration
-                db.execSQL("UPDATE historycall SET call_end='{\"duration\":' || (historycall.call_end - historycall.TIMESTAMP_START) || '}' WHERE missed = 0");
-                db.execSQL("UPDATE historycall SET call_end='{}' WHERE missed = 1");
-
-
-                db.execSQL("INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT null, historycall.callID, null, 1, 'SUCCEEDED', historycall.TIMESTAMP_START, 'CALL', historycall.call_end, conversations.id\n" +
-                        "FROM historycall\n" +
-                        "JOIN conversations ON conversations.participant = historycall.number\n" +
-                        "WHERE direction = 1\n"
-                );
-
-                db.execSQL("INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)\n" +
-                        "SELECT historycall.number, historycall.callID, null, 1, 'SUCCEEDED', historycall.TIMESTAMP_START, 'CALL', historycall.call_end, conversations.id\n" +
-                        "FROM historycall\n" +
-                        "JOIN conversations ON conversations.participant = historycall.number\n" +
-                        "WHERE direction = 0\n"
-                );
-
-                // drop old tables
-
-                db.execSQL("DROP TABLE historycall;");
-                db.execSQL("DROP TABLE historytext;");
-                db.execSQL("DROP TABLE historydata;");
-
-                db.setTransactionSuccessful();
-                db.endTransaction();
-                Log.d(TAG, "updateDatabaseFrom9: Migration from database version 9 to next, done.");
-            } catch (SQLiteException exception) {
-                Log.e(TAG, "updateDatabaseFrom9: Migration from database version 9 to next, failed.", exception);
-            }
-        }
-    }
-
-    /**
-     * Removes all the data from the database, ie all the tables.
-     *
-     * @param db the SQLiteDatabase to work with
-     */
-    private void clearDatabase(SQLiteDatabase db) {
-        if (db != null && db.isOpen()) {
-            Log.d(TAG, "clearDatabase: Will clear database.");
-            ArrayList<String> tableNames = new ArrayList<>();
-
-            try (Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null)) {
-                tableNames.ensureCapacity(c.getCount());
-                while (c.moveToNext())
-                    tableNames.add(c.getString(0));
-            }
-
-            try {
-                db.beginTransaction();
-                for (String tableName : tableNames) {
-                    db.execSQL("DROP TABLE " + tableName + ";");
-                }
-                db.setTransactionSuccessful();
-                db.endTransaction();
-                Log.d(TAG, "clearDatabase: Database is cleared");
-            } catch (SQLiteException exc) {
-                exc.printStackTrace();
-            }
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.kt b/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.kt
new file mode 100644
index 000000000..39babd204
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.kt
@@ -0,0 +1,438 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.history
+
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteException
+import android.util.Log
+import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
+import com.j256.ormlite.dao.Dao
+import com.j256.ormlite.support.ConnectionSource
+import com.j256.ormlite.table.TableUtils
+import net.jami.model.ConversationHistory
+import net.jami.model.DataTransfer
+import net.jami.model.Interaction
+import java.sql.SQLException
+import java.util.*
+
+/**
+ * Database helper class used to manage the creation and upgrading of your database. This class also usually provides
+ * the DAOs used by the other classes.
+ */
+class DatabaseHelper(context: Context?, dbDirectory: String) :
+    OrmLiteSqliteOpenHelper(context, dbDirectory, null, DATABASE_VERSION) {
+    val interactionDataDao: Dao<Interaction, Int> by lazy { getDao(Interaction::class.java) }
+    val conversationDataDao: Dao<ConversationHistory, Int> by lazy { getDao(ConversationHistory::class.java) }
+
+    /**
+     * This is called when the database is first created. Usually you should call createTable statements here to create
+     * the tables that will store your data.
+     */
+    override fun onCreate(db: SQLiteDatabase, connectionSource: ConnectionSource) {
+        try {
+            db.beginTransaction()
+            try {
+                TableUtils.createTable(connectionSource, ConversationHistory::class.java)
+                TableUtils.createTable(connectionSource, Interaction::class.java)
+                db.setTransactionSuccessful()
+            } catch (e: SQLException) {
+                Log.e(TAG, "Can't create database", e)
+                throw RuntimeException(e)
+            }
+        } finally {
+            db.endTransaction()
+        }
+    }
+
+    /**
+     * This is called when your application is upgraded and it has a higher version number. This allows you to adjust
+     * the various data to match the new version number.
+     */
+    override fun onUpgrade(
+        db: SQLiteDatabase,
+        connectionSource: ConnectionSource,
+        oldVersion: Int,
+        newVersion: Int
+    ) {
+        Log.i(TAG, "onUpgrade $oldVersion -> $newVersion")
+        try {
+            // if we are under version 10, it must first wait for account splitting to be complete which occurs in history service
+            if (oldVersion >= 10) updateDatabase(oldVersion, db, connectionSource)
+        } catch (exc: SQLException) {
+            exc.printStackTrace()
+            clearDatabase(db)
+            onCreate(db, connectionSource)
+        }
+    }
+
+    /**
+     * Main method to update the database from an old version to the last
+     *
+     * @param fromDatabaseVersion the old version of the database
+     * @param db                  the SQLiteDatabase to work with
+     * @throws SQLiteException database has failed to update to the last version
+     */
+    @Throws(SQLException::class)
+    private fun updateDatabase(
+        fromDatabaseVersion: Int,
+        db: SQLiteDatabase,
+        connectionSource: ConnectionSource
+    ) {
+        var fromVersion = fromDatabaseVersion
+        try {
+            while (fromVersion < DATABASE_VERSION) {
+                when (fromVersion) {
+                    6 -> updateDatabaseFrom6(db)
+                    7 -> updateDatabaseFrom7(db)
+                    8 -> updateDatabaseFrom8(connectionSource)
+                    9 -> updateDatabaseFrom9(db)
+                }
+                fromVersion++
+            }
+            Log.d(TAG, "updateDatabase: Database has been updated to the last version.")
+        } catch (exc: SQLException) {
+            Log.e(TAG, "updateDatabase: Database has failed to update to the last version.")
+            throw exc
+        }
+    }
+
+    /**
+     * Executes the migration from the database version 6 to the next
+     *
+     * @param db the SQLiteDatabase to work with
+     * @throws SQLiteException migration from database version 6 to next, failed
+     */
+    @Throws(SQLiteException::class)
+    private fun updateDatabaseFrom6(db: SQLiteDatabase?) {
+        if (db != null && db.isOpen) {
+            try {
+                Log.d(
+                    TAG,
+                    "updateDatabaseFrom6: Will begin migration from database version 6 to next."
+                )
+                db.beginTransaction()
+                //~ Create the new historyCall table and int index
+                db.execSQL(
+                    "CREATE TABLE IF NOT EXISTS `historycall` (`accountID` VARCHAR , `callID` VARCHAR , " +
+                            "`call_end` BIGINT , `TIMESTAMP_START` BIGINT , `contactID` BIGINT , " +
+                            "`contactKey` VARCHAR , `direction` INTEGER , `missed` SMALLINT , " +
+                            "`number` VARCHAR , `recordPath` VARCHAR ) ;"
+                )
+                db.execSQL(
+                    "CREATE INDEX IF NOT EXISTS `historycall_TIMESTAMP_START_idx` ON `historycall` " +
+                            "( `TIMESTAMP_START` );"
+                )
+                //~ Create the new historyText table and int indexes
+                db.execSQL(
+                    "CREATE TABLE IF NOT EXISTS `historytext` (`accountID` VARCHAR , `callID` VARCHAR , " +
+                            "`contactID` BIGINT , `contactKey` VARCHAR , `direction` INTEGER , " +
+                            "`id` BIGINT , `message` VARCHAR , `number` VARCHAR , `read` SMALLINT , " +
+                            "`TIMESTAMP` BIGINT , PRIMARY KEY (`id`) );"
+                )
+                db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_TIMESTAMP_idx` ON `historytext` ( `TIMESTAMP` );")
+                db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_id_idx` ON `historytext` ( `id` );")
+                db.rawQuery(
+                    "SELECT name FROM sqlite_master WHERE type=? AND name=?;",
+                    arrayOf("table", "a")
+                ).use { hasATable ->
+                    if (hasATable.count > 0) {
+                        //~ Copying data from the old table "a"
+                        db.execSQL(
+                            "INSERT INTO `historycall` (TIMESTAMP_START, call_end, number, missed," +
+                                    "direction, recordPath, accountID, contactID, contactKey, callID) " +
+                                    "SELECT TIMESTAMP_START,b,c,d,e,f,g,h,i,j FROM a;"
+                        )
+                        db.execSQL("DROP TABLE IF EXISTS a_TIMESTAMP_START_idx;")
+                        db.execSQL("DROP TABLE a;")
+                    }
+                }
+                db.rawQuery(
+                    "SELECT name FROM sqlite_master WHERE type=? AND name=?;",
+                    arrayOf("table", "e")
+                ).use { hasETable ->
+                    if (hasETable.count > 0) {
+                        //~ Copying data from the old table "e"
+                        db.execSQL(
+                            "INSERT INTO historytext (id, TIMESTAMP, number, direction, accountID," +
+                                    "contactID, contactKey, callID, message, read) " +
+                                    "SELECT id,TIMESTAMP,c,d,e,f,g,h,i,j FROM e;"
+                        )
+                        //~ Remove old tables "a" and "e"
+                        db.execSQL("DROP TABLE IF EXISTS e_TIMESTAMP_idx;")
+                        db.execSQL("DROP TABLE IF EXISTS e_id_idx;")
+                        db.execSQL("DROP TABLE e;")
+                    }
+                }
+                db.setTransactionSuccessful()
+                db.endTransaction()
+                Log.d(TAG, "updateDatabaseFrom6: Migration from database version 6 to next, done.")
+            } catch (exception: SQLiteException) {
+                Log.e(
+                    TAG,
+                    "updateDatabaseFrom6: Migration from database version 6 to next, failed."
+                )
+                throw exception
+            }
+        }
+    }
+
+    @Throws(SQLiteException::class)
+    private fun updateDatabaseFrom7(db: SQLiteDatabase?) {
+        if (db != null && db.isOpen) {
+            try {
+                Log.d(
+                    TAG,
+                    "updateDatabaseFrom7: Will begin migration from database version 7 to next."
+                )
+                db.beginTransaction()
+                db.execSQL("ALTER TABLE historytext ADD COLUMN state VARCHAR DEFAULT ''")
+                db.setTransactionSuccessful()
+                db.endTransaction()
+                Log.d(TAG, "updateDatabaseFrom7: Migration from database version 7 to next, done.")
+            } catch (exception: SQLiteException) {
+                Log.e(
+                    TAG,
+                    "updateDatabaseFrom7: Migration from database version 7 to next, failed."
+                )
+                throw exception
+            }
+        }
+    }
+
+    @Throws(SQLException::class)
+    private fun updateDatabaseFrom8(connectionSource: ConnectionSource) {
+        try {
+            TableUtils.createTable(connectionSource, DataTransfer::class.java)
+            Log.d(TAG, "Migration from database version 8 to next, done.")
+        } catch (e: SQLException) {
+            Log.e(TAG, "Migration from database version 8 to next, failed.", e)
+            throw e
+        }
+    }
+
+    /**
+     * This updates the database to version 10 which includes the switch to interaction and conversation tables.
+     * It will delete previous tables.
+     *
+     * @param db the database to migrate
+     * @throws SQLiteException
+     */
+    @Throws(SQLiteException::class)
+    private fun updateDatabaseFrom9(db: SQLiteDatabase?) {
+        if (db != null && db.isOpen) {
+            try {
+                Log.d(
+                    TAG,
+                    "updateDatabaseFrom9: Will begin migration from database version 9 to next for db: " + db.path
+                )
+                db.beginTransaction()
+
+                // removing ring prefix from both call and text database
+
+                // where clause improves performance (not required)
+                db.execSQL(
+                    """
+    UPDATE historytext 
+    SET number = replace( number, 'ring:', '' )
+    WHERE number LIKE 'ring:%'
+    """.trimIndent()
+                )
+                db.execSQL(
+                    """
+    UPDATE historycall 
+    SET number = replace( number, 'ring:', '' )
+    WHERE number LIKE 'ring:%'
+    """.trimIndent()
+                )
+
+                // populating conversations table
+                db.execSQL(
+                    """INSERT INTO conversations (participant)
+SELECT DISTINCT historytext.number
+FROM   historytext 
+       LEFT JOIN historycall 
+	   ON historycall.number = historytext.number
+UNION 
+SELECT DISTINCT historycall.number
+FROM   historycall
+       LEFT JOIN historytext
+	   ON historytext.number = historycall.number
+UNION
+SELECT DISTINCT historydata.peerId
+FROM   historydata
+       LEFT JOIN historytext
+	   ON historytext.number = historydata.peerId"""
+                )
+
+
+                // DATA TRANSFER TABLE
+
+                // Data transfer migration is done first as we maintain the same ID's as in the previous database
+
+                // updating the statuses to the new schema
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_CREATED' WHERE dataTransferEventCode='CREATED'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ERROR' WHERE dataTransferEventCode='UNSUPPORTED'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_AWAITING_PEER' WHERE dataTransferEventCode='WAIT_PEER_ACCEPTANCE'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_AWAITING_HOST' WHERE dataTransferEventCode='WAIT_HOST_ACCEPTANCE'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ONGOING' WHERE dataTransferEventCode='ONGOING'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_FINISHED' WHERE dataTransferEventCode='FINISHED'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='CLOSED_BY_HOST'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='CLOSED_BY_PEER'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_ERROR' WHERE dataTransferEventCode='INVALID_PATHNAME'")
+                db.execSQL("UPDATE historydata SET dataTransferEventCode='TRANSFER_UNJOINABLE_PEER' WHERE dataTransferEventCode='UNJOINABLE_PEER'")
+
+                // migration
+                db.execSQL(
+                    """
+    INSERT INTO interactions (id, author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT historydata.id, historydata.peerId, null, historydata.displayName, 1, historydata.dataTransferEventCode, historydata.TIMESTAMP, 'DATA_TRANSFER', '{}', conversations.id
+    FROM historydata
+    JOIN conversations ON conversations.participant = historydata.peerId
+    WHERE isOutgoing = 0
+    
+    """.trimIndent()
+                )
+                db.execSQL(
+                    """
+    INSERT INTO interactions (id, author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT historydata.id, null, null, historydata.displayName, 1, historydata.dataTransferEventCode, historydata.TIMESTAMP, 'DATA_TRANSFER', '{}', conversations.id
+    FROM historydata
+    JOIN conversations ON conversations.participant = historydata.peerId
+    WHERE isOutgoing = 1
+    
+    """.trimIndent()
+                )
+
+
+                // MESSAGE TABLE
+
+                // updating status in text message table
+                db.execSQL("UPDATE historytext SET state='SUCCESS' WHERE state='SENT'")
+                db.execSQL("UPDATE historytext SET state='SUCCESS' WHERE state='READ'")
+                db.execSQL("UPDATE historytext SET state='FAILURE' WHERE state='FAILURE'")
+                db.execSQL("UPDATE historytext SET state='INVALID' WHERE state= null")
+
+                // migration
+
+                // divided into two similar functions, where author needs to be null in case of outgoing message
+                db.execSQL(
+                    """
+    INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT null, historytext.id, historytext.message, historytext.read, historytext.state, historytext.TIMESTAMP, 'TEXT','{}', conversations.id
+    FROM historytext
+    JOIN conversations ON conversations.participant = historytext.number
+    WHERE direction = 2
+    
+    """.trimIndent()
+                )
+                db.execSQL(
+                    """
+    INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT historytext.number, historytext.id, historytext.message, historytext.read, historytext.state, historytext.TIMESTAMP, 'TEXT','{}', conversations.id
+    FROM historytext
+    JOIN conversations ON conversations.participant = historytext.number
+    WHERE direction = 1
+    
+    """.trimIndent()
+                )
+
+
+                // CALL TABLE
+
+                // setting the timestamp end to the duration string before migration
+                db.execSQL("UPDATE historycall SET call_end='{\"duration\":' || (historycall.call_end - historycall.TIMESTAMP_START) || '}' WHERE missed = 0")
+                db.execSQL("UPDATE historycall SET call_end='{}' WHERE missed = 1")
+                db.execSQL(
+                    """
+    INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT null, historycall.callID, null, 1, 'SUCCEEDED', historycall.TIMESTAMP_START, 'CALL', historycall.call_end, conversations.id
+    FROM historycall
+    JOIN conversations ON conversations.participant = historycall.number
+    WHERE direction = 1
+    
+    """.trimIndent()
+                )
+                db.execSQL(
+                    """
+    INSERT INTO interactions (author ,daemon_id, body, is_read, status, timestamp, type, extra_data, conversation)
+    SELECT historycall.number, historycall.callID, null, 1, 'SUCCEEDED', historycall.TIMESTAMP_START, 'CALL', historycall.call_end, conversations.id
+    FROM historycall
+    JOIN conversations ON conversations.participant = historycall.number
+    WHERE direction = 0
+    
+    """.trimIndent()
+                )
+
+                // drop old tables
+                db.execSQL("DROP TABLE historycall;")
+                db.execSQL("DROP TABLE historytext;")
+                db.execSQL("DROP TABLE historydata;")
+                db.setTransactionSuccessful()
+                db.endTransaction()
+                Log.d(TAG, "updateDatabaseFrom9: Migration from database version 9 to next, done.")
+            } catch (exception: SQLiteException) {
+                Log.e(
+                    TAG,
+                    "updateDatabaseFrom9: Migration from database version 9 to next, failed.",
+                    exception
+                )
+            }
+        }
+    }
+
+    /**
+     * Removes all the data from the database, ie all the tables.
+     *
+     * @param db the SQLiteDatabase to work with
+     */
+    private fun clearDatabase(db: SQLiteDatabase?) {
+        if (db != null && db.isOpen) {
+            Log.d(TAG, "clearDatabase: Will clear database.")
+            val tableNames = ArrayList<String>()
+            db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null).use { c ->
+                tableNames.ensureCapacity(c.count)
+                while (c.moveToNext()) tableNames.add(c.getString(0))
+            }
+            try {
+                db.beginTransaction()
+                for (tableName in tableNames) {
+                    db.execSQL("DROP TABLE $tableName;")
+                }
+                db.setTransactionSuccessful()
+                db.endTransaction()
+                Log.d(TAG, "clearDatabase: Database is cleared")
+            } catch (exc: SQLiteException) {
+                exc.printStackTrace()
+            }
+        }
+    }
+
+    companion object {
+        private val TAG = DatabaseHelper::class.java.simpleName
+
+        // any time you make changes to your database objects, you may have to increase the database version
+        private const val DATABASE_VERSION = 10
+    }
+
+    init {
+        Log.d(TAG, "Helper initialized for $dbDirectory")
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/linkpreview/LinkListener.kt b/ring-android/app/src/main/java/cx/ring/linkpreview/LinkListener.kt
new file mode 100644
index 000000000..0c36095d5
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/linkpreview/LinkListener.kt
@@ -0,0 +1,15 @@
+package cx.ring.linkpreview
+interface LinkListener {
+
+    /**
+     * Called when there was an error in loading the image from url, recommended to hide the view
+     */
+    fun onError()
+
+    /**
+     * Called when image from url is loaded successfully
+     *
+     * @param link to url image
+     */
+    fun onSuccess(link: PreviewData)
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/linkpreview/LinkPreview.kt b/ring-android/app/src/main/java/cx/ring/linkpreview/LinkPreview.kt
new file mode 100644
index 000000000..a67f7e821
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/linkpreview/LinkPreview.kt
@@ -0,0 +1,30 @@
+package cx.ring.linkpreview
+
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+object LinkPreview {
+
+    fun loadPreviewData(url: String) : Single<PreviewData> {
+        return Single.fromCallable {
+            val doc = Jsoup.connect(url)
+                .userAgent("Mozilla")
+                .get()
+            val imageElements = doc.select("meta[property=og:image]")
+            if (imageElements.size > 0) {
+                var it = 0
+                var chosen: String? = ""
+                while ((chosen == null || chosen.isEmpty()) && it < imageElements.size) {
+                    chosen = imageElements[it].attr("content")
+                    it += 1
+                }
+                PreviewData(doc.title(), chosen ?: "", url)
+            } else {
+                PreviewData("", "", "")
+            }
+        }.subscribeOn(Schedulers.io())
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/linkpreview/PreviewData.kt b/ring-android/app/src/main/java/cx/ring/linkpreview/PreviewData.kt
new file mode 100644
index 000000000..a54edae73
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/linkpreview/PreviewData.kt
@@ -0,0 +1,12 @@
+package cx.ring.linkpreview
+
+data class PreviewData(val title: String, val imageUrl: String, val baseUrl: String) {
+
+    fun isEmpty(): Boolean = title.isEmpty() && imageUrl.isEmpty() && baseUrl.isEmpty()
+
+    fun isNotEmpty(): Boolean = !isEmpty()
+
+    companion object  {
+        val EMPTY_DATA = PreviewData("", "", "")
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
deleted file mode 100644
index 5ac47b770..000000000
--- a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.mvp;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import android.view.View;
-import android.widget.Toast;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import net.jami.model.Error;
-import net.jami.mvp.BaseView;
-import net.jami.mvp.RootPresenter;
-
-public abstract class BaseFragment<T extends RootPresenter> extends Fragment implements BaseView {
-
-    protected static final String TAG = BaseFragment.class.getSimpleName();
-
-    @Inject
-    protected T presenter;
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        //Be sure to do the injection in onCreateView method
-        presenter.bindView(this);
-        initPresenter(presenter);
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        presenter.unbindView();
-    }
-
-    public void displayErrorToast(Error error) {
-        String errorString;
-        switch (error) {
-            case NO_INPUT:
-                errorString = getString(R.string.call_error_no_camera_no_microphone);
-                break;
-            case INVALID_FILE:
-                errorString = getString(R.string.invalid_file);
-                break;
-            case NOT_ABLE_TO_WRITE_FILE:
-                errorString = getString(R.string.not_able_to_write_file);
-                break;
-            case NO_SPACE_LEFT:
-                errorString = getString(R.string.no_space_left_on_device);
-                break;
-            default:
-                errorString = getString(R.string.generic_error);
-                break;
-        }
-
-        Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show();
-    }
-
-    protected void initPresenter(T presenter) {
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java
index 2cb836583..60e3749d0 100644
--- a/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java
@@ -27,6 +27,8 @@ import net.jami.mvp.RootPresenter;
 
 import javax.inject.Inject;
 
+import dagger.hilt.android.AndroidEntryPoint;
+
 public abstract class BasePreferenceFragment<T extends RootPresenter> extends PreferenceFragmentCompat {
     @Inject
     protected T presenter;
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.java
deleted file mode 100644
index f22e87151..000000000
--- a/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.mvp;
-
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Toast;
-
-import javax.inject.Inject;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import cx.ring.R;
-import net.jami.model.Error;
-import net.jami.mvp.BaseView;
-import net.jami.mvp.RootPresenter;
-
-public abstract class BaseSupportFragment<T extends RootPresenter> extends Fragment implements BaseView {
-
-    protected static final String TAG = BaseSupportFragment.class.getSimpleName();
-
-    @Inject
-    protected T presenter;
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        //Be sure to do the injection in onCreateView method
-        if (presenter != null) {
-            presenter.bindView(this);
-            initPresenter(presenter);
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        if (presenter != null)
-            presenter.unbindView();
-    }
-
-    public void displayErrorToast(Error error) {
-        String errorString;
-        switch (error) {
-            case NO_INPUT:
-                errorString = getString(R.string.call_error_no_camera_no_microphone);
-                break;
-            case INVALID_FILE:
-                errorString = getString(R.string.invalid_file);
-                break;
-            case NOT_ABLE_TO_WRITE_FILE:
-                errorString = getString(R.string.not_able_to_write_file);
-                break;
-            case NO_SPACE_LEFT:
-                errorString = getString(R.string.no_space_left_on_device);
-                break;
-            default:
-                errorString = getString(R.string.generic_error);
-                break;
-        }
-
-        Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show();
-    }
-
-    protected void initPresenter(T presenter) {
-    }
-
-    protected void replaceFragmentWithSlide(Fragment fragment, @IdRes int content) {
-        getFragmentManager()
-                .beginTransaction()
-                .setCustomAnimations(R.anim.slide_in_right,
-                        R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right)
-                .replace(content, fragment, TAG)
-                .addToBackStack(TAG)
-                .commit();
-    }
-
-    protected void replaceFragment(Fragment fragment, @IdRes int content) {
-        getFragmentManager()
-                .beginTransaction()
-                .replace(content, fragment, TAG)
-                .addToBackStack(TAG)
-                .commit();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt b/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt
new file mode 100644
index 000000000..752b8b5b0
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.mvp
+
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.annotation.IdRes
+import androidx.fragment.app.Fragment
+import cx.ring.R
+import net.jami.model.Error
+import net.jami.mvp.BaseView
+import net.jami.mvp.RootPresenter
+import javax.inject.Inject
+
+abstract class BaseSupportFragment<T : RootPresenter<V>, V> : Fragment(), BaseView {
+    @Inject lateinit
+    var presenter: T
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        //Be sure to do the injection in onCreateView method
+        presenter.bindView(this as V)
+        initPresenter(presenter)
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        presenter.unbindView()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        presenter.onDestroy()
+    }
+
+    override fun displayErrorToast(error: Error) {
+        val errorString: String = when (error) {
+            Error.NO_INPUT -> getString(R.string.call_error_no_camera_no_microphone)
+            Error.INVALID_FILE -> getString(R.string.invalid_file)
+            Error.NOT_ABLE_TO_WRITE_FILE -> getString(R.string.not_able_to_write_file)
+            Error.NO_SPACE_LEFT -> getString(R.string.no_space_left_on_device)
+            else -> getString(R.string.generic_error)
+        }
+        Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show()
+    }
+
+    protected open fun initPresenter(presenter: T) {}
+
+    protected fun replaceFragmentWithSlide(fragment: Fragment?, @IdRes content: Int) {
+        parentFragmentManager
+            .beginTransaction()
+            .setCustomAnimations(
+                R.anim.slide_in_right,
+                R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right
+            )
+            .replace(content, fragment!!, TAG)
+            .addToBackStack(TAG)
+            .commit()
+    }
+
+    protected fun replaceFragment(fragment: Fragment?, @IdRes content: Int) {
+        parentFragmentManager
+            .beginTransaction()
+            .replace(content, fragment!!, TAG)
+            .addToBackStack(TAG)
+            .commit()
+    }
+
+    companion object {
+        protected val TAG = BaseSupportFragment::class.simpleName
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java
index f3c52e06b..f5eb793cd 100644
--- a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java
+++ b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java
@@ -35,15 +35,9 @@ public class PluginUtils {
         for (String pluginPath : pluginsPaths) {
             File pluginFolder = new File(pluginPath);
             if(pluginFolder.isDirectory()) {
-                //We use the absolute path of a plugin as a preference name for uniqueness
-                boolean enabled = false;
-
-                if (loadedPluginsPaths.contains(pluginPath)) {
-                    enabled = true;
-                }
                 pluginsList.add(new PluginDetails(
                         pluginFolder.getName(),
-                        pluginFolder.getAbsolutePath(), enabled));
+                        pluginFolder.getAbsolutePath(), loadedPluginsPaths.contains(pluginPath)));
             }
         }
         return pluginsList;
diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPicker.java b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPicker.java
index d95578597..28e9917d6 100644
--- a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPicker.java
+++ b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPicker.java
@@ -2,44 +2,40 @@ package cx.ring.plugins.RecyclerPicker;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
 import android.view.View;
+import android.view.WindowManager;
 
 import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.List;
 
-public class RecyclerPicker implements RecyclerPickerAdapter.ItemClickListener{
-    private RecyclerView mRecyclerView;
-    private int mItemLayoutResource;
-    private RecyclerPickerAdapter mAdapter;
-    private RecyclerPickerLayoutManager mLayoutManager;
-    private int mOrientation;
-    private RecyclerPickerLayoutManager.ItemSelectedListener mItemSelectedListener;
+public class RecyclerPicker implements RecyclerPickerAdapter.ItemClickListener {
+    private final RecyclerView mRecyclerView;
+    private final RecyclerPickerAdapter mAdapter;
+    private final RecyclerPickerLayoutManager mLayoutManager;
+    private final RecyclerPickerLayoutManager.ItemSelectedListener mItemSelectedListener;
     private int paddingLeft;
     private int paddingRight;
 
-    public RecyclerPicker(RecyclerView recyclerView,
+    public RecyclerPicker(@NonNull RecyclerView recyclerView,
                           @LayoutRes int recyclerItemLayout, int orientation,
                           RecyclerPickerLayoutManager.ItemSelectedListener listener) {
         mRecyclerView = recyclerView;
-        mItemLayoutResource = recyclerItemLayout;
-        mOrientation = orientation;
         mItemSelectedListener = listener;
-        init();
-    }
-
-    private void init() {
         // use this setting to improve performance if you know that changes
         // in content do not change the layout size of the RecyclerView
         mRecyclerView.setHasFixedSize(true);
         // use a linear layout manager
-        mLayoutManager = new RecyclerPickerLayoutManager(mRecyclerView.getContext(), mOrientation,false,
+        mLayoutManager = new RecyclerPickerLayoutManager(mRecyclerView.getContext(), orientation,false,
                 mItemSelectedListener);
         mRecyclerView.setLayoutManager(mLayoutManager);
 
         // specify an adapter (see also next example)
-        mAdapter = new RecyclerPickerAdapter(mRecyclerView.getContext(), mItemLayoutResource, this);
+        mAdapter = new RecyclerPickerAdapter(mRecyclerView.getContext(), recyclerItemLayout, this);
         mRecyclerView.setAdapter(mAdapter);
         setRecyclerViewPadding();
     }
@@ -61,14 +57,14 @@ public class RecyclerPicker implements RecyclerPickerAdapter.ItemClickListener{
     }
 
     public void setFirstLastElementsWidths(int first, int last){
-        paddingLeft = RecyclerPickerUtils.getScreenWidth(mRecyclerView.getContext())/2 - RecyclerPickerUtils.dpToPx(mRecyclerView.getContext(), first/2);
-        paddingRight = RecyclerPickerUtils.getScreenWidth(mRecyclerView.getContext())/2 - RecyclerPickerUtils.dpToPx(mRecyclerView.getContext(), last/2);
+        paddingLeft = getScreenWidth(mRecyclerView.getContext())/2 - dpToPx(mRecyclerView.getContext(), first/2);
+        paddingRight = getScreenWidth(mRecyclerView.getContext())/2 - dpToPx(mRecyclerView.getContext(), last/2);
         updateRecyclerViewPadding();
     }
 
     private void setRecyclerViewPadding() {
-        paddingLeft = RecyclerPickerUtils.getScreenWidth(mRecyclerView.getContext())/2;
-        paddingRight = RecyclerPickerUtils.getScreenWidth(mRecyclerView.getContext())/2;
+        paddingLeft = getScreenWidth(mRecyclerView.getContext())/2;
+        paddingRight = getScreenWidth(mRecyclerView.getContext())/2;
         updateRecyclerViewPadding();
     }
 
@@ -79,4 +75,18 @@ public class RecyclerPicker implements RecyclerPickerAdapter.ItemClickListener{
     public void scrollToPosition(int position){
         mLayoutManager.scrollToPositionWithOffset(position, 0);
     }
+
+    private static int getScreenWidth(Context context) {
+        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        DisplayMetrics dm = new DisplayMetrics();
+        if (windowManager != null) {
+            windowManager.getDefaultDisplay().getMetrics(dm);
+        }
+        return dm.widthPixels;
+    }
+
+    private static int dpToPx(Context context, int value){
+        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) value,
+                context.getResources().getDisplayMetrics());
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerAdapter.java b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerAdapter.java
index bc68a09e9..a27025c0d 100644
--- a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerAdapter.java
@@ -17,13 +17,13 @@ import cx.ring.R;
 
 public class RecyclerPickerAdapter extends RecyclerView.Adapter<RecyclerPickerAdapter.ItemViewHolder> {
     private List<Drawable> mList;
-    private ItemClickListener mItemClickListener;
-    private int mItemLayoutResource;
+    private final ItemClickListener mItemClickListener;
+    private final int mItemLayoutResource;
     private final LayoutInflater mInflater;
 
     public RecyclerPickerAdapter(Context ctx, @LayoutRes int recyclerItemLayout, ItemClickListener itemClickListener) {
-        this.mItemLayoutResource = recyclerItemLayout;
-        this.mItemClickListener = itemClickListener;
+        mItemLayoutResource = recyclerItemLayout;
+        mItemClickListener = itemClickListener;
         mInflater = LayoutInflater.from(ctx);
     }
 
@@ -31,7 +31,7 @@ public class RecyclerPickerAdapter extends RecyclerView.Adapter<RecyclerPickerAd
     @Override
     public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
         View view = mInflater.inflate(mItemLayoutResource, parent, false);
-        view.setOnClickListener(v -> mItemClickListener.onItemClicked(v));
+        view.setOnClickListener(mItemClickListener::onItemClicked);
         return new ItemViewHolder(view);
     }
 
@@ -46,11 +46,7 @@ public class RecyclerPickerAdapter extends RecyclerView.Adapter<RecyclerPickerAd
     // Return the size of your dataset (invoked by the layout manager)
     @Override
     public int getItemCount() {
-        if(mList != null) {
-            return mList.size();
-        } else {
-            return 0;
-        }
+        return mList != null ? mList.size() : 0;
     }
 
     public void updateData(List<Drawable> newlist) {
@@ -60,8 +56,8 @@ public class RecyclerPickerAdapter extends RecyclerView.Adapter<RecyclerPickerAd
 
 
     public static class ItemViewHolder extends RecyclerView.ViewHolder{
-        private ImageView itemImageView;
-        private View view;
+        private final ImageView itemImageView;
+        private final View view;
         public ItemViewHolder(@NonNull View itemView) {
             super(itemView);
             this.view = itemView;
diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java
index c93c1ffa8..60d66b076 100644
--- a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java
+++ b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java
@@ -1,7 +1,6 @@
 package cx.ring.plugins.RecyclerPicker;
 
 import android.content.Context;
-import android.util.Log;
 import android.view.View;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -10,22 +9,20 @@ import androidx.recyclerview.widget.RecyclerView;
 
 public class RecyclerPickerLayoutManager extends LinearLayoutManager {
     private RecyclerView recyclerView;
-    private LinearSnapHelper snapHelper;
-    private ItemSelectedListener listener;
+    private final ItemSelectedListener listener;
 
     public RecyclerPickerLayoutManager(Context context, int orientation, boolean reverseLayout, ItemSelectedListener listener) {
         super(context, orientation, reverseLayout);
         this.listener = listener;
     }
 
-
     @Override
     public void onAttachedToWindow(RecyclerView view) {
         super.onAttachedToWindow(view);
         recyclerView = view;
 
         // Smart snapping
-        snapHelper = new LinearSnapHelper();
+        LinearSnapHelper snapHelper = new LinearSnapHelper();
         snapHelper.attachToRecyclerView(recyclerView);
     }
 
@@ -73,8 +70,7 @@ public class RecyclerPickerLayoutManager extends LinearLayoutManager {
     }
 
     private int getRecyclerViewCenterX() {
-        Log.i("ZZZ", "recyclerView width: " + recyclerView.getWidth() + " Right-Left: " + (recyclerView.getRight()-recyclerView.getLeft()));
-        return (recyclerView.getWidth())/2 + recyclerView.getLeft();
+        return recyclerView.getWidth()/2 + recyclerView.getLeft();
     }
 
     private void scaleDownView() {
diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerUtils.java b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerUtils.java
deleted file mode 100644
index 67671ee79..000000000
--- a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerUtils.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package cx.ring.plugins.RecyclerPicker;
-
-import android.content.Context;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.WindowManager;
-
-public class RecyclerPickerUtils {
-
-    public static int getScreenWidth(Context context) {
-        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        DisplayMetrics dm = new DisplayMetrics();
-        if(windowManager != null) {
-            windowManager.getDefaultDisplay().getMetrics(dm);
-        }
-        return dm.widthPixels;
-    }
-
-    public static int dpToPx(Context context, int value){
-        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) value,
-                context.getResources().getDisplayMetrics());
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java b/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java
index be69411d9..40cc0153b 100644
--- a/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java
+++ b/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java
@@ -28,8 +28,11 @@ import androidx.core.content.ContextCompat;
 import javax.inject.Inject;
 
 import cx.ring.application.JamiApplication;
+import dagger.hilt.android.AndroidEntryPoint;
+
 import net.jami.services.PreferencesService;
 
+@AndroidEntryPoint
 public class BootReceiver extends BroadcastReceiver {
     private static final String TAG = BootReceiver.class.getSimpleName();
 
@@ -48,7 +51,7 @@ public class BootReceiver extends BroadcastReceiver {
                 Intent.ACTION_MY_PACKAGE_REPLACED.equals(action))
         {
             try {
-                ((JamiApplication) context.getApplicationContext()).getInjectionComponent().inject(this);
+                //((JamiApplication) context.getApplicationContext()).getInjectionComponent().inject(this);
                 if (mPreferencesService.getSettings().isAllowOnStartup()) {
                     try {
                         ContextCompat.startForegroundService(context, new Intent(SyncService.ACTION_START)
diff --git a/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java b/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java
index f78049766..522754d08 100644
--- a/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java
@@ -31,11 +31,12 @@ import androidx.annotation.Nullable;
 
 import javax.inject.Inject;
 
-import cx.ring.application.JamiApplication;
 import cx.ring.services.NotificationServiceImpl;
+import dagger.hilt.android.AndroidEntryPoint;
 
 import net.jami.services.NotificationService;
 
+@AndroidEntryPoint
 public class CallNotificationService extends Service {
     public static final String ACTION_START = "START";
     public static final String ACTION_STOP = "STOP";
@@ -43,12 +44,6 @@ public class CallNotificationService extends Service {
     @Inject
     NotificationService mNotificationService;
 
-    @Override
-    public void onCreate() {
-        ((JamiApplication) getApplication()).getInjectionComponent().inject(this);
-        super.onCreate();
-    }
-
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         super.onStartCommand(intent, flags, startId);
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java
deleted file mode 100644
index 7961014f7..000000000
--- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java
+++ /dev/null
@@ -1,799 +0,0 @@
-/*
- * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- * Author: Regis Montoya <r3gis.3R@gmail.com>
- * Author: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
- * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.service;
-
-import android.app.Notification;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.RemoteInput;
-import androidx.legacy.content.WakefulBroadcastReceiver;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import cx.ring.BuildConfig;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import cx.ring.client.ConversationActivity;
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Codec;
-import net.jami.model.Settings;
-import net.jami.model.Uri;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.ContactService;
-import net.jami.services.DaemonService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.HistoryService;
-import net.jami.services.NotificationService;
-import net.jami.services.PreferencesService;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class DRingService extends Service {
-    private static final String TAG = DRingService.class.getSimpleName();
-
-    public static final String ACTION_TRUST_REQUEST_ACCEPT = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_ACCEPT";
-    public static final String ACTION_TRUST_REQUEST_REFUSE = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_REFUSE";
-    public static final String ACTION_TRUST_REQUEST_BLOCK = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_BLOCK";
-
-    static public final String ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT";
-    static public final String ACTION_CALL_HOLD_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_HOLD_ACCEPT";
-    static public final String ACTION_CALL_END_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_END_ACCEPT";
-    static public final String ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE";
-    static public final String ACTION_CALL_END = BuildConfig.APPLICATION_ID + ".action.CALL_END";
-    static public final String ACTION_CALL_VIEW = BuildConfig.APPLICATION_ID + ".action.CALL_VIEW";
-
-    static public final String ACTION_CONV_READ = BuildConfig.APPLICATION_ID + ".action.CONV_READ";
-    static public final String ACTION_CONV_DISMISS = BuildConfig.APPLICATION_ID + ".action.CONV_DISMISS";
-    static public final String ACTION_CONV_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CONV_ACCEPT";
-    static public final String ACTION_CONV_REPLY_INLINE = BuildConfig.APPLICATION_ID + ".action.CONV_REPLY";
-
-    static public final String ACTION_FILE_ACCEPT = BuildConfig.APPLICATION_ID + ".action.FILE_ACCEPT";
-    static public final String ACTION_FILE_CANCEL = BuildConfig.APPLICATION_ID + ".action.FILE_CANCEL";
-    static public final String KEY_MESSAGE_ID = "messageId";
-    static public final String KEY_TRANSFER_ID = "transferId";
-    static public final String KEY_TEXT_REPLY = "textReply";
-
-    private static final int NOTIFICATION_ID = 1;
-
-    private final ContactsContentObserver contactContentObserver = new ContactsContentObserver();
-    @Inject
-    @Singleton
-    protected DaemonService mDaemonService;
-    @Inject
-    @Singleton
-    protected CallService mCallService;
-    @Inject
-    @Singleton
-    protected AccountService mAccountService;
-    @Inject
-    @Singleton
-    protected HardwareService mHardwareService;
-    @Inject
-    @Singleton
-    protected HistoryService mHistoryService;
-    @Inject
-    @Singleton
-    protected DeviceRuntimeService mDeviceRuntimeService;
-    @Inject
-    @Singleton
-    protected NotificationService mNotificationService;
-    @Inject
-    @Singleton
-    protected ContactService mContactService;
-    @Inject
-    @Singleton
-    protected PreferencesService mPreferencesService;
-    @Inject
-    @Singleton
-    protected ConversationFacade mConversationFacade;
-
-    private final Handler mHandler = new Handler();
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-    private final Runnable mConnectivityChecker = this::updateConnectivityState;
-    public static boolean isRunning = false;
-
-    protected final IDRingService.Stub mBinder = new IDRingService.Stub() {
-
-        @Override
-        public String placeCall(final String account, final String number, final boolean video) {
-            return mConversationFacade.placeCall(account, Uri.fromString(number), video).blockingGet().getDaemonIdString();
-        }
-
-        @Override
-        public void refuse(final String callID) {
-            mCallService.refuse(callID);
-        }
-
-        @Override
-        public void accept(final String callID) {
-            mCallService.accept(callID);
-        }
-
-        @Override
-        public void hangUp(final String callID) {
-            mCallService.hangUp(callID);
-        }
-
-        @Override
-        public void hold(final String callID) {
-            mCallService.hold(callID);
-        }
-
-        @Override
-        public void unhold(final String callID) {
-            mCallService.unhold(callID);
-        }
-
-        public void sendProfile(final String callId, final String accountId) {
-            mAccountService.sendProfile(callId, accountId);
-        }
-
-        @Override
-        public boolean isStarted() throws RemoteException {
-            return mDaemonService.isStarted();
-        }
-
-        @Override
-        public Map<String, String> getCallDetails(final String callID) throws RemoteException {
-            return mCallService.getCallDetails(callID);
-        }
-
-        @Override
-        public void setAudioPlugin(final String audioPlugin) {
-            mCallService.setAudioPlugin(audioPlugin);
-        }
-
-        @Override
-        public String getCurrentAudioOutputPlugin() {
-            return mCallService.getCurrentAudioOutputPlugin();
-        }
-
-        @Override
-        public List<String> getAccountList() {
-            return mAccountService.getAccountList().blockingGet();
-        }
-
-        @Override
-        public void setAccountOrder(final String order) {
-            String[] accountIds = order.split(File.separator);
-            mAccountService.setAccountOrder(Arrays.asList(accountIds));
-        }
-
-        @Override
-        public Map<String, String> getAccountDetails(final String accountID) {
-            return mAccountService.getAccountDetails(accountID);
-        }
-
-        @SuppressWarnings("unchecked")
-        // Hashmap runtime cast
-        @Override
-        public void setAccountDetails(final String accountId, final Map map) {
-            mAccountService.setAccountDetails(accountId, map);
-        }
-
-        @Override
-        public void setAccountActive(final String accountId, final boolean active) {
-            mAccountService.setAccountActive(accountId, active);
-        }
-
-        @Override
-        public void setAccountsActive(final boolean active) {
-            mAccountService.setAccountsActive(active);
-        }
-
-        @Override
-        public Map<String, String> getVolatileAccountDetails(final String accountId) {
-            return mAccountService.getVolatileAccountDetails(accountId);
-        }
-
-        @Override
-        public Map<String, String> getAccountTemplate(final String accountType) throws RemoteException {
-            return mAccountService.getAccountTemplate(accountType).blockingGet();
-        }
-
-        @SuppressWarnings("unchecked")
-        // Hashmap runtime cast
-        @Override
-        public String addAccount(final Map map) {
-            return mAccountService.addAccount((Map<String, String>) map).blockingFirst().getAccountID();
-        }
-
-        @Override
-        public void removeAccount(final String accountId) {
-            mAccountService.removeAccount(accountId);
-        }
-
-        @Override
-        public void exportOnRing(final String accountId, final String password) {
-            mAccountService.exportOnRing(accountId, password);
-        }
-
-        public Map<String, String> getKnownRingDevices(final String accountId) {
-            return mAccountService.getKnownRingDevices(accountId);
-        }
-
-        /*************************
-         * Transfer related API
-         *************************/
-
-        @Override
-        public void transfer(final String callID, final String to) throws RemoteException {
-            mCallService.transfer(callID, to);
-        }
-
-        @Override
-        public void attendedTransfer(final String transferID, final String targetID) throws RemoteException {
-            mCallService.attendedTransfer(transferID, targetID);
-        }
-
-        /*************************
-         * Conference related API
-         *************************/
-
-        @Override
-        public void removeConference(final String confID) throws RemoteException {
-            mCallService.removeConference(confID);
-        }
-
-        @Override
-        public void joinParticipant(final String selCallID, final String dragCallID) throws RemoteException {
-            mCallService.joinParticipant(selCallID, dragCallID);
-        }
-
-        @Override
-        public void addParticipant(final String callID, final String confID) throws RemoteException {
-            mCallService.addParticipant(callID, confID);
-        }
-
-        @Override
-        public void addMainParticipant(final String confID) throws RemoteException {
-            mCallService.addMainParticipant(confID);
-        }
-
-        @Override
-        public void detachParticipant(final String callID) throws RemoteException {
-            mCallService.detachParticipant(callID);
-        }
-
-        @Override
-        public void joinConference(final String selConfID, final String dragConfID) throws RemoteException {
-            mCallService.joinConference(selConfID, dragConfID);
-        }
-
-        @Override
-        public void hangUpConference(final String confID) throws RemoteException {
-            mCallService.hangUpConference(confID);
-        }
-
-        @Override
-        public void holdConference(final String confID) throws RemoteException {
-            mCallService.holdConference(confID);
-        }
-
-        @Override
-        public void unholdConference(final String confID) throws RemoteException {
-            mCallService.unholdConference(confID);
-        }
-
-        @Override
-        public boolean isConferenceParticipant(final String callID) throws RemoteException {
-            return mCallService.isConferenceParticipant(callID);
-        }
-
-        @Override
-        public Map<String, ArrayList<String>> getConferenceList() throws RemoteException {
-            return mCallService.getConferenceList();
-        }
-
-        @Override
-        public List<String> getParticipantList(final String confID) throws RemoteException {
-            return mCallService.getParticipantList(confID);
-        }
-
-        @Override
-        public String getConferenceId(String callID) throws RemoteException {
-            return mCallService.getConferenceId(callID);
-        }
-
-        @Override
-        public String getConferenceDetails(final String callID) throws RemoteException {
-            return mCallService.getConferenceState(callID);
-        }
-
-        @Override
-        public String getRecordPath() throws RemoteException {
-            return mCallService.getRecordPath();
-        }
-
-        @Override
-        public void setRecordPath(final String path) throws RemoteException {
-            mCallService.setRecordPath(path);
-        }
-
-        @Override
-        public boolean toggleRecordingCall(final String id) throws RemoteException {
-            return mCallService.toggleRecordingCall(id);
-        }
-
-        @Override
-        public boolean startRecordedFilePlayback(final String filepath) throws RemoteException {
-            return mCallService.startRecordedFilePlayback(filepath);
-        }
-
-        @Override
-        public void stopRecordedFilePlayback(final String filepath) throws RemoteException {
-            mCallService.stopRecordedFilePlayback();
-        }
-
-        @Override
-        public void sendTextMessage(final String callID, final String msg) throws RemoteException {
-            mCallService.sendTextMessage(callID, msg);
-        }
-
-        @Override
-        public long sendAccountTextMessage(final String accountID, final String to, final String msg) {
-            return mCallService.sendAccountTextMessage(accountID, to, msg).blockingGet();
-        }
-
-        @Override
-        public List<Codec> getCodecList(final String accountID) throws RemoteException {
-            return mAccountService.getCodecList(accountID).blockingGet();
-        }
-
-        @Override
-        public Map<String, String> validateCertificatePath(final String accountID, final String certificatePath, final String privateKeyPath, final String privateKeyPass) throws RemoteException {
-            return mAccountService.validateCertificatePath(accountID, certificatePath, privateKeyPath, privateKeyPass);
-        }
-
-        @Override
-        public Map<String, String> validateCertificate(final String accountID, final String certificate) throws RemoteException {
-            return mAccountService.validateCertificate(accountID, certificate);
-        }
-
-        @Override
-        public Map<String, String> getCertificateDetailsPath(final String certificatePath) throws RemoteException {
-            return mAccountService.getCertificateDetailsPath(certificatePath);
-        }
-
-        @Override
-        public Map<String, String> getCertificateDetails(final String certificateRaw) throws RemoteException {
-            return mAccountService.getCertificateDetails(certificateRaw);
-        }
-
-        @Override
-        public void setActiveCodecList(final List codecs, final String accountID) throws RemoteException {
-            mAccountService.setActiveCodecList(accountID, codecs);
-        }
-
-        @Override
-        public void playDtmf(final String key) throws RemoteException {
-
-        }
-
-        @Override
-        public Map<String, String> getConference(final String id) throws RemoteException {
-            return mCallService.getConferenceDetails(id);
-        }
-
-        @Override
-        public void setMuted(final boolean mute) throws RemoteException {
-            mCallService.setMuted(mute);
-        }
-
-        @Override
-        public boolean isCaptureMuted() throws RemoteException {
-            return mCallService.isCaptureMuted();
-        }
-
-        @Override
-        public List<String> getTlsSupportedMethods() {
-            return mAccountService.getTlsSupportedMethods();
-        }
-
-        @Override
-        public List getCredentials(final String accountID) throws RemoteException {
-            return mAccountService.getCredentials(accountID);
-        }
-
-        @Override
-        public void setCredentials(final String accountID, final List creds) throws RemoteException {
-            mAccountService.setCredentials(accountID, creds);
-        }
-
-        @Override
-        public void registerAllAccounts() throws RemoteException {
-            mAccountService.registerAllAccounts();
-        }
-
-        @Override
-        @Deprecated
-        public void videoSurfaceAdded(String id) {
-
-        }
-
-        @Override
-        @Deprecated
-        public void videoSurfaceRemoved(String id) {
-
-        }
-
-        @Override
-        @Deprecated
-        public void videoPreviewSurfaceAdded() {
-
-        }
-
-        @Override
-        @Deprecated
-        public void videoPreviewSurfaceRemoved() {
-
-        }
-
-        @Override
-        @Deprecated
-        public void switchInput(final String id, final boolean front) {
-        }
-
-        @Override
-        @Deprecated
-        public void setPreviewSettings() {
-
-        }
-
-        @Override
-        public void connectivityChanged() {
-            mHardwareService.connectivityChanged(mPreferencesService.hasNetworkConnected());
-        }
-
-        @Override
-        public void lookupName(final String account, final String nameserver, final String name) {
-            mAccountService.lookupName(account, nameserver, name);
-        }
-
-        @Override
-        public void lookupAddress(final String account, final String nameserver, final String address) {
-            mAccountService.lookupAddress(account, nameserver, address);
-        }
-
-        @Override
-        public void registerName(final String account, final String password, final String name) {
-            mAccountService.registerName(account, password, name);
-        }
-    };
-
-    private final BroadcastReceiver receiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == null) {
-                Log.w(TAG, "onReceive: received a null action on broadcast receiver");
-                return;
-            }
-            Log.d(TAG, "receiver.onReceive: " + action);
-            switch (action) {
-                case ConnectivityManager.CONNECTIVITY_ACTION: {
-                    updateConnectivityState();
-                    break;
-                }
-                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: {
-                    mConnectivityChecker.run();
-                    mHandler.postDelayed(mConnectivityChecker, 100);
-                }
-            }
-        }
-    };
-    @Override
-    public void onCreate() {
-        Log.i(TAG, "onCreated");
-        super.onCreate();
-
-        // dependency injection
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        isRunning = true;
-
-        if (mDeviceRuntimeService.hasContactPermission()) {
-            getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactContentObserver);
-        }
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-        }
-        registerReceiver(receiver, intentFilter);
-        updateConnectivityState();
-
-        mDisposableBag.add(mPreferencesService.getSettingsSubject().subscribe(s -> {
-            showSystemNotification(s);
-        }));
-
-        JamiApplication.getInstance().bindDaemon();
-        JamiApplication.getInstance().bootstrapDaemon();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        Log.i(TAG, "onDestroy()");
-        unregisterReceiver(receiver);
-        getContentResolver().unregisterContentObserver(contactContentObserver);
-
-        mHardwareService.unregisterCameraDetectionCallback();
-        mDisposableBag.clear();
-        isRunning = false;
-    }
-
-    private void showSystemNotification(Settings settings) {
-        if (settings.isAllowPersistentNotification()) {
-            startForeground(NOTIFICATION_ID, (Notification) mNotificationService.getServiceNotification());
-        } else {
-            stopForeground(true);
-        }
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        // Log.i(TAG, "onStartCommand " + (intent == null ? "null" : intent.getAction()) + " " + flags + " " + startId);
-        if (intent != null) {
-            parseIntent(intent);
-            WakefulBroadcastReceiver.completeWakefulIntent(intent);
-        }
-        return START_STICKY; /* started and stopped explicitly */
-    }
-
-    @Override
-    public IBinder onBind(Intent arg0) {
-        Log.i(TAG, "onBound");
-        return mBinder;
-    }
-
-    /* ************************************
-     *
-     * Implement public interface for the service
-     *
-     * *********************************
-     */
-
-    private void updateConnectivityState() {
-        if (mDaemonService.isStarted()) {
-            boolean isConnected = mPreferencesService.hasNetworkConnected();
-            mAccountService.setAccountsActive(isConnected);
-            // Execute connectivityChanged to reload UPnP
-            // and reconnect active accounts if necessary.
-            mHardwareService.connectivityChanged(isConnected);
-        }
-    }
-
-    private void parseIntent(@NonNull Intent intent) {
-        String action = intent.getAction();
-        if (action == null) {
-            return;
-        }
-        Bundle extras = intent.getExtras();
-        switch (action) {
-            case ACTION_TRUST_REQUEST_ACCEPT:
-            case ACTION_TRUST_REQUEST_REFUSE:
-            case ACTION_TRUST_REQUEST_BLOCK:
-                handleTrustRequestAction(intent.getData(), action);
-                break;
-            case ACTION_CALL_ACCEPT:
-            case ACTION_CALL_HOLD_ACCEPT:
-            case ACTION_CALL_END_ACCEPT:
-            case ACTION_CALL_REFUSE:
-            case ACTION_CALL_END:
-            case ACTION_CALL_VIEW:
-                if (extras != null) {
-                    handleCallAction(action, extras);
-                }
-                break;
-            case ACTION_CONV_READ:
-            case ACTION_CONV_ACCEPT:
-            case ACTION_CONV_DISMISS:
-            case ACTION_CONV_REPLY_INLINE:
-                handleConvAction(intent, action);
-                break;
-            case ACTION_FILE_ACCEPT:
-            case ACTION_FILE_CANCEL:
-                if (extras != null) {
-                    handleFileAction(intent.getData(), action, extras);
-                }
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void handleFileAction(android.net.Uri uri, String action, Bundle extras) {
-        String messageId = extras.getString(KEY_MESSAGE_ID);
-        String id = extras.getString(KEY_TRANSFER_ID);
-        ConversationPath path = ConversationPath.fromUri(uri);
-        if (action.equals(ACTION_FILE_ACCEPT)) {
-            mNotificationService.removeTransferNotification(path.getAccountId(), path.getConversationUri(), id);
-            mAccountService.acceptFileTransfer(path.getAccountId(), path.getConversationUri(), messageId, id);
-        } else if (action.equals(ACTION_FILE_CANCEL)) {
-            mConversationFacade.cancelFileTransfer(path.getAccountId(), path.getConversationUri(), messageId, id);
-        }
-    }
-
-    private void handleTrustRequestAction(android.net.Uri uri, String action) {
-        ConversationPath path = ConversationPath.fromUri(uri);
-        if (path != null) {
-            mNotificationService.cancelTrustRequestNotification(path.getAccountId());
-            switch (action) {
-                case ACTION_TRUST_REQUEST_ACCEPT:
-                    mConversationFacade.acceptRequest(path.getAccountId(), path.getConversationUri());
-                    break;
-                case ACTION_TRUST_REQUEST_REFUSE:
-                    mConversationFacade.discardRequest(path.getAccountId(), path.getConversationUri());
-                    break;
-                case ACTION_TRUST_REQUEST_BLOCK:
-                    mConversationFacade.discardRequest(path.getAccountId(), path.getConversationUri());
-                    mAccountService.removeContact(path.getAccountId(), path.getConversationUri().getRawRingId(), true);
-                    break;
-            }
-        }
-    }
-
-    private void handleCallAction(String action, Bundle extras) {
-        String callId = extras.getString(NotificationService.KEY_CALL_ID);
-
-        if (callId == null || callId.isEmpty()) {
-            return;
-        }
-
-        switch (action) {
-            case ACTION_CALL_ACCEPT:
-                mNotificationService.cancelCallNotification();
-                startActivity(new Intent(ACTION_CALL_ACCEPT)
-                        .putExtras(extras)
-                        .setClass(getApplicationContext(), CallActivity.class)
-                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-                break;
-            case ACTION_CALL_HOLD_ACCEPT:
-                String holdId = extras.getString(NotificationService.KEY_HOLD_ID);
-                mNotificationService.cancelCallNotification();
-                mCallService.hold(holdId);
-                startActivity(new Intent(ACTION_CALL_ACCEPT)
-                        .putExtras(extras)
-                        .setClass(getApplicationContext(), CallActivity.class)
-                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-                break;
-            case ACTION_CALL_END_ACCEPT:
-                String endId = extras.getString(NotificationService.KEY_END_ID);
-                mNotificationService.cancelCallNotification();
-                mCallService.hangUp(endId);
-                startActivity(new Intent(ACTION_CALL_ACCEPT)
-                        .putExtras(extras)
-                        .setClass(getApplicationContext(), CallActivity.class)
-                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-                break;
-            case ACTION_CALL_REFUSE:
-                mCallService.refuse(callId);
-                mHardwareService.closeAudioState();
-                break;
-            case ACTION_CALL_END:
-                mCallService.hangUp(callId);
-                mHardwareService.closeAudioState();
-                break;
-            case ACTION_CALL_VIEW:
-                mNotificationService.cancelCallNotification();
-                if (DeviceUtils.isTv(this)) {
-                    startActivity(new Intent(Intent.ACTION_VIEW)
-                            .putExtras(extras)
-                            .setClass(getApplicationContext(), TVCallActivity.class)
-                            .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-
-                } else {
-                    startActivity(new Intent(Intent.ACTION_VIEW)
-                            .putExtras(extras)
-                            .setClass(getApplicationContext(), CallActivity.class)
-                            .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-                }
-                break;
-        }
-    }
-
-    private void handleConvAction(Intent intent, String action) {
-        ConversationPath path = ConversationPath.fromIntent(intent);
-        if (path == null || path.getConversationId().isEmpty()) {
-            return;
-        }
-
-        switch (action) {
-            case ACTION_CONV_READ:
-                mConversationFacade.readMessages(path.getAccountId(), path.getConversationUri());
-                break;
-            case ACTION_CONV_DISMISS:
-                break;
-            case ACTION_CONV_REPLY_INLINE: {
-                Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
-                if (remoteInput != null) {
-                    CharSequence reply = remoteInput.getCharSequence(KEY_TEXT_REPLY);
-                    if (!TextUtils.isEmpty(reply)) {
-                        Uri uri = path.getConversationUri();
-                        String message = reply.toString();
-                        mConversationFacade.startConversation(path.getAccountId(), uri)
-                                .flatMapCompletable(c -> mConversationFacade.sendTextMessage(c, uri, message)
-                                        .doOnComplete(() -> mNotificationService.showTextNotification(path.getAccountId(), c)))
-                                .subscribe();
-                    }
-                }
-                break;
-            }
-            case ACTION_CONV_ACCEPT:
-                startActivity(new Intent(Intent.ACTION_VIEW, path.toUri(), getApplicationContext(), ConversationActivity.class)
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-                break;
-            default:
-                break;
-        }
-    }
-
-    public void refreshContacts() {
-        if (mAccountService.getCurrentAccount() == null) {
-            return;
-        }
-        mContactService.loadContacts(mAccountService.hasRingAccount(), mAccountService.hasSipAccount(), mAccountService.getCurrentAccount());
-    }
-
-    private static class ContactsContentObserver extends ContentObserver {
-
-        ContactsContentObserver() {
-            super(null);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, android.net.Uri uri) {
-            super.onChange(selfChange, uri);
-            //mContactService.loadContacts(mAccountService.hasRingAccount(), mAccountService.hasSipAccount(), mAccountService.getCurrentAccount());
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.kt b/ring-android/app/src/main/java/cx/ring/service/DRingService.kt
new file mode 100644
index 000000000..46afd2f13
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.kt
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Author: Regis Montoya <r3gis.3R@gmail.com>
+ * Author: Emeric Vigier <emeric.vigier@savoirfairelinux.com>
+ * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.service
+
+import android.app.Notification
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.Uri
+import android.os.*
+import android.provider.ContactsContract
+import android.text.TextUtils
+import android.util.Log
+import androidx.core.app.RemoteInput
+import androidx.legacy.content.WakefulBroadcastReceiver
+import cx.ring.BuildConfig
+import cx.ring.application.JamiApplication
+import cx.ring.client.CallActivity
+import cx.ring.client.ConversationActivity
+import cx.ring.tv.call.TVCallActivity
+import cx.ring.utils.ConversationPath.Companion.fromIntent
+import cx.ring.utils.ConversationPath.Companion.fromUri
+import cx.ring.utils.DeviceUtils.isTv
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.services.ConversationFacade
+import net.jami.model.Conversation
+import net.jami.model.Settings
+import net.jami.services.*
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@AndroidEntryPoint
+class DRingService : Service() {
+    private val contactContentObserver = ContactsContentObserver()
+
+    @Inject
+    @Singleton
+    lateinit var mDaemonService: DaemonService
+
+    @Inject
+    @Singleton
+    lateinit var mCallService: CallService
+
+    @Inject
+    @Singleton
+    lateinit var mAccountService: AccountService
+
+    @Inject
+    @Singleton
+    lateinit var mHardwareService: HardwareService
+
+    @Inject
+    @Singleton
+    lateinit var mHistoryService: HistoryService
+
+    @Inject
+    @Singleton
+    lateinit var mDeviceRuntimeService: DeviceRuntimeService
+
+    @Inject
+    @Singleton
+    lateinit var mNotificationService: NotificationService
+
+    @Inject
+    @Singleton
+    lateinit var mContactService: ContactService
+
+    @Inject
+    @Singleton
+    lateinit var mPreferencesService: PreferencesService
+
+    @Inject
+    @Singleton
+    lateinit var mConversationFacade: ConversationFacade
+
+    private val mHandler = Handler(Looper.myLooper()!!)
+    private val mDisposableBag = CompositeDisposable()
+    private val mConnectivityChecker = Runnable { updateConnectivityState() }
+
+    private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            if (action == null) {
+                Log.w(TAG, "onReceive: received a null action on broadcast receiver")
+                return
+            }
+            Log.d(TAG, "receiver.onReceive: $action")
+            when (action) {
+                ConnectivityManager.CONNECTIVITY_ACTION -> {
+                    updateConnectivityState()
+                }
+                PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> {
+                    mConnectivityChecker.run()
+                    mHandler.postDelayed(mConnectivityChecker, 100)
+                }
+            }
+        }
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        Log.i(TAG, "onCreate")
+        isRunning = true
+        if (mDeviceRuntimeService.hasContactPermission()) {
+            contentResolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactContentObserver)
+        }
+        val intentFilter = IntentFilter()
+        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
+        }
+        registerReceiver(receiver, intentFilter)
+        updateConnectivityState()
+        mDisposableBag.add(mPreferencesService.settingsSubject.subscribe { settings: Settings ->
+            showSystemNotification(settings)
+        })
+        JamiApplication.instance!!.apply {
+            bindDaemon()
+            bootstrapDaemon()
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        Log.i(TAG, "onDestroy()")
+        unregisterReceiver(receiver)
+        contentResolver.unregisterContentObserver(contactContentObserver)
+        mHardwareService.unregisterCameraDetectionCallback()
+        mDisposableBag.clear()
+        isRunning = false
+    }
+
+    private fun showSystemNotification(settings: Settings) {
+        if (settings.isAllowPersistentNotification) {
+            startForeground(NOTIFICATION_ID, mNotificationService.serviceNotification as Notification)
+        } else {
+            stopForeground(true)
+        }
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        // Log.i(TAG, "onStartCommand " + (intent == null ? "null" : intent.getAction()) + " " + flags + " " + startId);
+        if (intent != null) {
+            parseIntent(intent)
+            WakefulBroadcastReceiver.completeWakefulIntent(intent)
+        }
+        return START_STICKY /* started and stopped explicitly */
+    }
+
+    private val binder: IBinder = Binder()
+    override fun onBind(intent: Intent): IBinder? {
+        return binder
+    }
+
+    /* ************************************
+     *
+     * Implement public interface for the service
+     *
+     * *********************************
+     */
+    private fun updateConnectivityState() {
+        if (mDaemonService.isStarted) {
+            val isConnected = mPreferencesService.hasNetworkConnected()
+            mAccountService.setAccountsActive(isConnected)
+            // Execute connectivityChanged to reload UPnP
+            // and reconnect active accounts if necessary.
+            mHardwareService.connectivityChanged(isConnected)
+        }
+    }
+
+    private fun parseIntent(intent: Intent) {
+        val action = intent.action ?: return
+        val extras = intent.extras
+        when (action) {
+            ACTION_TRUST_REQUEST_ACCEPT, ACTION_TRUST_REQUEST_REFUSE, ACTION_TRUST_REQUEST_BLOCK ->
+                handleTrustRequestAction(intent.data, action)
+            ACTION_CALL_ACCEPT, ACTION_CALL_HOLD_ACCEPT, ACTION_CALL_END_ACCEPT, ACTION_CALL_REFUSE, ACTION_CALL_END, ACTION_CALL_VIEW -> extras?.let {
+                handleCallAction(action, it)
+            }
+            ACTION_CONV_READ, ACTION_CONV_ACCEPT, ACTION_CONV_DISMISS, ACTION_CONV_REPLY_INLINE ->
+                handleConvAction(intent, action)
+            ACTION_FILE_ACCEPT, ACTION_FILE_CANCEL -> extras?.let {
+                handleFileAction(intent.data, action, it)
+            }
+        }
+    }
+
+    private fun handleFileAction(uri: Uri?, action: String, extras: Bundle) {
+        Log.w(TAG, "handleFileAction $extras")
+        val messageId = extras.getString(KEY_MESSAGE_ID)
+        val id = extras.getString(KEY_TRANSFER_ID)!!
+        val path = fromUri(uri)!!
+        if (action == ACTION_FILE_ACCEPT) {
+            mNotificationService.removeTransferNotification(path.accountId, path.conversationUri, id)
+            mAccountService.acceptFileTransfer(path.accountId, path.conversationUri, messageId, id)
+        } else if (action == ACTION_FILE_CANCEL) {
+            mConversationFacade.cancelFileTransfer(path.accountId, path.conversationUri, messageId, id)
+        }
+    }
+
+    private fun handleTrustRequestAction(uri: Uri?, action: String) {
+        fromUri(uri)?.let { path ->
+            mNotificationService.cancelTrustRequestNotification(path.accountId)
+            when (action) {
+                ACTION_TRUST_REQUEST_ACCEPT -> mConversationFacade.acceptRequest(path.accountId, path.conversationUri)
+                ACTION_TRUST_REQUEST_REFUSE -> mConversationFacade.discardRequest(path.accountId, path.conversationUri)
+                ACTION_TRUST_REQUEST_BLOCK -> {
+                    mConversationFacade.discardRequest(path.accountId, path.conversationUri)
+                    mAccountService.removeContact(path.accountId, path.conversationUri.rawRingId, true)
+                }
+            }
+        }
+    }
+
+    private fun handleCallAction(action: String, extras: Bundle) {
+        val callId = extras.getString(NotificationService.KEY_CALL_ID)
+        if (callId == null || callId.isEmpty()) {
+            return
+        }
+        when (action) {
+            ACTION_CALL_ACCEPT -> {
+                mNotificationService.cancelCallNotification()
+                startActivity(Intent(ACTION_CALL_ACCEPT)
+                        .putExtras(extras)
+                        .setClass(applicationContext, CallActivity::class.java)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK))
+            }
+            ACTION_CALL_HOLD_ACCEPT -> {
+                val holdId = extras.getString(NotificationService.KEY_HOLD_ID)!!
+                mNotificationService.cancelCallNotification()
+                mCallService.hold(holdId)
+                startActivity(Intent(ACTION_CALL_ACCEPT)
+                        .putExtras(extras)
+                        .setClass(applicationContext, CallActivity::class.java)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK))
+            }
+            ACTION_CALL_END_ACCEPT -> {
+                val endId = extras.getString(NotificationService.KEY_END_ID)!!
+                mNotificationService.cancelCallNotification()
+                mCallService.hangUp(endId)
+                startActivity(Intent(ACTION_CALL_ACCEPT)
+                        .putExtras(extras)
+                        .setClass(applicationContext, CallActivity::class.java)
+                        .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK))
+            }
+            ACTION_CALL_REFUSE -> {
+                mCallService.refuse(callId)
+                mHardwareService.closeAudioState()
+            }
+            ACTION_CALL_END -> {
+                mCallService.hangUp(callId)
+                mHardwareService.closeAudioState()
+            }
+            ACTION_CALL_VIEW -> {
+                mNotificationService.cancelCallNotification()
+                if (isTv(this)) {
+                    startActivity(
+                        Intent(Intent.ACTION_VIEW)
+                            .putExtras(extras)
+                            .setClass(applicationContext, TVCallActivity::class.java)
+                            .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+                    )
+                } else {
+                    startActivity(
+                        Intent(Intent.ACTION_VIEW)
+                            .putExtras(extras)
+                            .setClass(applicationContext, CallActivity::class.java)
+                            .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+                    )
+                }
+            }
+        }
+    }
+
+    private fun handleConvAction(intent: Intent, action: String) {
+        val path = fromIntent(intent)
+        if (path == null || path.conversationId.isEmpty()) {
+            return
+        }
+        when (action) {
+            ACTION_CONV_READ -> mConversationFacade.readMessages(path.accountId, path.conversationUri)
+            ACTION_CONV_DISMISS -> {
+            }
+            ACTION_CONV_REPLY_INLINE -> {
+                val remoteInput = RemoteInput.getResultsFromIntent(intent)
+                if (remoteInput != null) {
+                    val reply = remoteInput.getCharSequence(KEY_TEXT_REPLY)
+                    if (!TextUtils.isEmpty(reply)) {
+                        val uri = path.conversationUri
+                        val message = reply.toString()
+                        mConversationFacade.startConversation(path.accountId, uri)
+                            .flatMapCompletable { c: Conversation ->
+                                mConversationFacade.sendTextMessage(c, uri, message)
+                                    .doOnComplete { mNotificationService.showTextNotification(path.accountId, c)}
+                            }
+                            .subscribe()
+                    }
+                }
+            }
+            ACTION_CONV_ACCEPT -> startActivity(Intent(Intent.ACTION_VIEW, path.toUri(), applicationContext, ConversationActivity::class.java)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+            else -> {
+            }
+        }
+    }
+
+    fun refreshContacts() {
+        if (mAccountService.currentAccount == null) {
+            return
+        }
+        mContactService.loadContacts(
+            mAccountService.hasRingAccount(),
+            mAccountService.hasSipAccount(),
+            mAccountService.currentAccount
+        )
+    }
+
+    private class ContactsContentObserver internal constructor() : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean, uri: Uri?) {
+            super.onChange(selfChange, uri)
+            //mContactService.loadContacts(mAccountService.hasRingAccount(), mAccountService.hasSipAccount(), mAccountService.getCurrentAccount());
+        }
+    }
+
+    companion object {
+        private val TAG = DRingService::class.java.simpleName
+        const val ACTION_TRUST_REQUEST_ACCEPT = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_ACCEPT"
+        const val ACTION_TRUST_REQUEST_REFUSE = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_REFUSE"
+        const val ACTION_TRUST_REQUEST_BLOCK = BuildConfig.APPLICATION_ID + ".action.TRUST_REQUEST_BLOCK"
+        const val ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT"
+        const val ACTION_CALL_HOLD_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_HOLD_ACCEPT"
+        const val ACTION_CALL_END_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_END_ACCEPT"
+        const val ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE"
+        const val ACTION_CALL_END = BuildConfig.APPLICATION_ID + ".action.CALL_END"
+        const val ACTION_CALL_VIEW = BuildConfig.APPLICATION_ID + ".action.CALL_VIEW"
+        const val ACTION_CONV_READ = BuildConfig.APPLICATION_ID + ".action.CONV_READ"
+        const val ACTION_CONV_DISMISS = BuildConfig.APPLICATION_ID + ".action.CONV_DISMISS"
+        const val ACTION_CONV_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CONV_ACCEPT"
+        const val ACTION_CONV_REPLY_INLINE = BuildConfig.APPLICATION_ID + ".action.CONV_REPLY"
+        const val ACTION_FILE_ACCEPT = BuildConfig.APPLICATION_ID + ".action.FILE_ACCEPT"
+        const val ACTION_FILE_CANCEL = BuildConfig.APPLICATION_ID + ".action.FILE_CANCEL"
+        const val KEY_MESSAGE_ID = "messageId"
+        const val KEY_TRANSFER_ID = "transferId"
+        const val KEY_TEXT_REPLY = "textReply"
+        private const val NOTIFICATION_ID = 1
+        var isRunning = false
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl b/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
deleted file mode 100644
index 80ceb957b..000000000
--- a/ring-android/app/src/main/java/cx/ring/service/IDRingService.aidl
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package cx.ring.service;
-
-interface IDRingService {
-
-    boolean isStarted();
-
-    Map getCallDetails(in String callID);
-    String placeCall(in String account, in String number, in boolean hasVideo);
-    void refuse(in String callID);
-    void accept(in String callID);
-    void hangUp(in String callID);
-    void hold(in String callID);
-    void unhold(in String callID);
-
-    void lookupName(in String account, in String nameserver, in String name);
-    void lookupAddress(in String account, in String nameserver, in String address);
-    void registerName(in String account, in String password, in String name);
-
-    List getAccountList();
-    String addAccount(in Map accountDetails);
-    void removeAccount(in String accoundId);
-    void setAccountOrder(in String order);
-    Map getAccountDetails(in String accountID);
-    Map getVolatileAccountDetails(in String accountID);
-    Map getAccountTemplate(in String accountType);
-    void registerAllAccounts();
-    void setAccountDetails(in String accountId, in Map accountDetails);
-    void setAccountActive(in String accountId, in boolean active);
-    void setAccountsActive(in boolean active);
-    List getCredentials(in String accountID);
-    void setCredentials(in String accountID, in List creds);
-    void setAudioPlugin(in String callID);
-    String getCurrentAudioOutputPlugin();
-    List getCodecList(in String accountID);
-    void setActiveCodecList(in List codecs, in String accountID);
-    void exportOnRing(in String accountID, in String password);
-    Map getKnownRingDevices(in String accountID);
-
-    Map validateCertificatePath(in String accountID, in String certificatePath, in String privateKeyPath, in String privateKeyPass);
-    Map validateCertificate(in String accountID, in String certificateId);
-    Map getCertificateDetailsPath(in String certificatePath);
-    Map getCertificateDetails(in String certificate);
-
-    /* Recording */
-    void setRecordPath(in String path);
-    String getRecordPath();
-    boolean toggleRecordingCall(in String id);
-    boolean startRecordedFilePlayback(in String filepath);
-    void stopRecordedFilePlayback(in String filepath);
-
-    /* Mute */
-    void setMuted(boolean mute);
-    boolean isCaptureMuted();
-
-    /* Security */
-    List getTlsSupportedMethods();
-
-    /* DTMF */
-    void playDtmf(in String key);
-
-    /* IM */
-    void sendTextMessage(in String callID, in String message);
-    long sendAccountTextMessage(in String accountid, in String to, in String msg);
-    void sendProfile(in String callID, in String accountID);
-
-    void transfer(in String callID, in String to);
-    void attendedTransfer(in String transferID, in String targetID);
-
-    /* Video */
-    void setPreviewSettings();
-    void switchInput(in String call, in boolean front);
-    void videoSurfaceAdded(in String call);
-    void videoSurfaceRemoved(in String call);
-    void videoPreviewSurfaceAdded();
-    void videoPreviewSurfaceRemoved();
-
-    /* Conference related methods */
-
-    void removeConference(in String confID);
-    void joinParticipant(in String sel_callID, in String drag_callID);
-
-    void addParticipant(in String callID, in String confID);
-    void addMainParticipant(in String confID);
-    void detachParticipant(in String callID);
-    void joinConference(in String sel_confID, in String drag_confID);
-    void hangUpConference(in String confID);
-    void holdConference(in String confID);
-    void unholdConference(in String confID);
-    boolean isConferenceParticipant(in String callID);
-    Map getConferenceList();
-    List getParticipantList(in String confID);
-    String getConferenceId(in String callID);
-    String getConferenceDetails(in String callID);
-
-    Map getConference(in String id);
-
-    void connectivityChanged();
-}
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.java b/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.java
deleted file mode 100644
index 7c583ec98..000000000
--- a/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.service;
-
-import android.Manifest;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.ActivityCompat;
-import androidx.core.app.NotificationCompat;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.ConversationActivity;
-import net.jami.daemon.Blob;
-import net.jami.daemon.JamiService;
-import net.jami.daemon.StringMap;
-import net.jami.facades.ConversationFacade;
-import cx.ring.fragments.ConversationFragment;
-
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-
-import cx.ring.services.NotificationServiceImpl;
-import cx.ring.utils.ConversationPath;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class LocationSharingService extends Service implements LocationListener {
-    private static final String TAG = "LocationSharingService";
-
-    public static final int NOTIF_SYNC_SERVICE_ID = 931801;
-
-    public static final String ACTION_START = "startSharing";
-    public static final String ACTION_STOP = "stopSharing";
-    public static final String EXTRA_SHARING_DURATION = "locationShareDuration";
-
-    public static final String PREFERENCES_LOCATION = "location";
-    public static final String PREFERENCES_KEY_POS_LONG = "lastPosLongitude";
-    public static final String PREFERENCES_KEY_POS_LAT = "lastPosLatitude";
-    public static final int SHARE_DURATION_SEC = 60 * 5;
-
-    @Inject
-    ConversationFacade mConversationFacade;
-
-    private final Random mRandom = new Random();
-    private final IBinder binder = new LocalBinder();
-    private boolean started = false;
-
-    private LocationManager mLocationManager;
-    private NotificationManager mNotificationManager;
-    private SharedPreferences mPreferences;
-    private Handler mHandler;
-
-    private final Subject<Location> mMyLocationSubject = BehaviorSubject.create();
-    private final Map<ConversationPath, Date> contactLocationShare = new HashMap<>();
-    private final Subject<Set<ConversationPath>> mContactSharingSubject = BehaviorSubject.createDefault(contactLocationShare.keySet());
-
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-
-    public LocationSharingService() {
-    }
-
-    public Observable<Location> getMyLocation() {
-        return mMyLocationSubject;
-    }
-
-    public Observable<Set<ConversationPath>> getContactSharing() {
-        return mContactSharingSubject;
-    }
-
-    public Observable<Long> getContactSharingExpiration(ConversationPath path) {
-        return Observable.timer(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
-                .startWithItem(0L)
-                .repeat()
-                .map(i -> contactLocationShare.get(path).getTime() - SystemClock.elapsedRealtime())
-                .onErrorComplete();
-    }
-
-    @Override
-    public void onCreate() {
-        ((JamiApplication) getApplication()).getInjectionComponent().inject(this);
-
-        mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
-        mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        mPreferences = getSharedPreferences(PREFERENCES_LOCATION, Context.MODE_PRIVATE);
-        mHandler = new Handler(getMainLooper());
-        String posLongitude = mPreferences.getString(PREFERENCES_KEY_POS_LONG, null);
-        String posLatitude = mPreferences.getString(PREFERENCES_KEY_POS_LAT, null);
-        if (posLatitude != null && posLongitude != null) {
-            try {
-                Location location = new Location("cache");
-                location.setLatitude(Double.parseDouble(posLatitude));
-                location.setLongitude(Double.parseDouble(posLongitude));
-                mMyLocationSubject.onNext(location);
-            } catch (Exception e) {
-                Log.w(TAG, "Can't load last location", e);
-            }
-        }
-        if (mLocationManager != null) {
-            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
-                    || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
-                try {
-                    Criteria c = new Criteria();
-                    c.setAccuracy(Criteria.ACCURACY_FINE);
-                    mLocationManager.requestLocationUpdates(0, 0.f, c, this, null);
-                } catch (Exception e) {
-                    Log.e(TAG, "Can't start location tracking", e);
-                }
-            }
-        }
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.w(TAG, "onStartCommand " + intent);
-        String action = intent.getAction();
-        ConversationPath path = ConversationPath.fromIntent(intent);
-        long now = SystemClock.elapsedRealtime();
-
-        if (ACTION_START.equals(action)) {
-            int duration = intent.getIntExtra(EXTRA_SHARING_DURATION, SHARE_DURATION_SEC);
-            long expiration = now + (duration * 1000L);
-            if (contactLocationShare.put(path, new Date(expiration)) == null) {
-                mContactSharingSubject.onNext(contactLocationShare.keySet());
-            }
-            mHandler.postAtTime(this::refreshSharing, expiration);
-
-            if (!started) {
-                started = true;
-                mDisposableBag.add(getNotification(now)
-                        .subscribe(notification -> {
-                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
-                                startForeground(NOTIF_SYNC_SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
-                            else
-                                startForeground(NOTIF_SYNC_SERVICE_ID, notification);
-
-                            mHandler.postAtTime(this::refreshNotificationTimer, now + 30 * 1000);
-                            JamiApplication.getInstance().startDaemon();
-                        }));
-                mDisposableBag.add(mMyLocationSubject
-                        .throttleLatest(10, TimeUnit.SECONDS)
-                        .map(location -> {
-                            JSONObject out = new JSONObject();
-                            out.put("type", AccountService.Location.Type.position.toString());
-                            out.put("lat", location.getLatitude());
-                            out.put("long", location.getLongitude());
-                            out.put("alt", location.getAltitude());
-                            out.put("time",location.getElapsedRealtimeNanos()/1000000L);
-                            float bearing = location.getBearing();
-                            if (bearing != 0.f)
-                                out.put("bearing", bearing);
-                            float speed = location.getSpeed();
-                            if (speed != 0.f)
-                                out.put("speed", speed);
-                            return out;
-                        })
-                        .subscribe(location -> {
-                            Log.w(TAG, "location send " + location + " to " + contactLocationShare.size());
-                            StringMap msgs = new StringMap();
-                            msgs.setRaw(net.jami.services.CallService.MIME_GEOLOCATION, Blob.fromString(location.toString()));
-                            for (ConversationPath p : contactLocationShare.keySet())  {
-                                JamiService.sendAccountTextMessage(p.getAccountId(), p.getConversationId(), msgs);
-                            }
-                        }));
-            } else {
-                mDisposableBag.add(getNotification(now)
-                        .subscribe(notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification)));
-            }
-        }
-        else if (ACTION_STOP.equals(action)) {
-            if (path == null)
-                contactLocationShare.clear();
-            else {
-                contactLocationShare.remove(path);
-
-                JSONObject jsonObject = new JSONObject();
-                try {
-                    jsonObject.put("type", net.jami.services.AccountService.Location.Type.stop.toString());
-                    jsonObject.put("time", Long.MAX_VALUE);
-                } catch (JSONException e) {
-                    e.printStackTrace();
-                }
-
-                Log.w(TAG, "location send " + jsonObject + " to " + contactLocationShare.size());
-                StringMap msgs = new StringMap();
-                msgs.setRaw(CallService.MIME_GEOLOCATION, Blob.fromString(jsonObject.toString()));
-                JamiService.sendAccountTextMessage(path.getAccountId(), path.getConversationId(), msgs);
-            }
-
-            mContactSharingSubject.onNext(contactLocationShare.keySet());
-
-            if (contactLocationShare.isEmpty()) {
-                Log.w(TAG, "stopping sharing " + intent);
-                mDisposableBag.clear();
-                stopForeground(true);
-                stopSelf();
-                started = false;
-            } else {
-                mDisposableBag.add(getNotification(now)
-                        .subscribe(notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification)));
-            }
-        }
-        return START_NOT_STICKY;
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.w(TAG, "onDestroy");
-        if (mLocationManager !=  null) {
-            mLocationManager.removeUpdates(this);
-        }
-        mMyLocationSubject.onComplete();
-        mContactSharingSubject.onComplete();
-        mDisposableBag.dispose();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return binder;
-    }
-
-    @Override
-    public boolean onUnbind(Intent intent) {
-        return true;
-    }
-
-    @Override
-    public void onLocationChanged(Location location) {
-        // Log.w(TAG, "onLocationChanged " + location.toString());
-        mMyLocationSubject.onNext(location);
-        mPreferences.edit()
-                .putString(PREFERENCES_KEY_POS_LAT, Double.toString(location.getLatitude()))
-                .putString(PREFERENCES_KEY_POS_LONG, Double.toString(location.getLongitude()))
-                .apply();
-    }
-
-    @Override
-    public void onStatusChanged(String provider, int status, Bundle extras) {}
-
-    @Override
-    public void onProviderEnabled(String provider) {
-    }
-
-    @Override
-    public void onProviderDisabled(String provider) {
-    }
-
-    @NonNull
-    private Single<Notification> getNotification(long now) {
-        int contactCount = contactLocationShare.size();
-        ConversationPath firsPath = contactLocationShare.keySet().iterator().next();
-        Date largest = null;
-        for (Date d : contactLocationShare.values())
-            if (largest == null || d.after(largest))
-                largest = d;
-        final long largestDate = largest == null ? now : largest.getTime();
-        // Log.w(TAG, "getNotification " + firsPath.getContactId());
-
-        return mConversationFacade.getAccountSubject(firsPath.getAccountId())
-                .map(account -> account.getContactFromCache(firsPath.getConversationUri()))
-                .map(contact -> {
-                    String title;
-                    final Intent stopIntent = new Intent(ACTION_STOP).setClass(getApplicationContext(), LocationSharingService.class);
-                    final Intent contentIntent = new Intent(Intent.ACTION_VIEW, firsPath.toUri(), getApplicationContext(), ConversationActivity.class)
-                            .putExtra(ConversationFragment.EXTRA_SHOW_MAP, true)
-                            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
-                    if (contactCount == 1) {
-                        stopIntent.setData(firsPath.toUri());
-                        title = getString(R.string.notif_location_title, contact.getDisplayName());
-                    } else {
-                        title = getString(R.string.notif_location_multi_title, contactCount);
-                    }
-                    String subtitle = getString(R.string.notif_location_remaining, (int)Math.ceil((largestDate - now)/(double)(1000 * 60)));
-
-                    return new NotificationCompat.Builder(this, NotificationServiceImpl.NOTIF_CHANNEL_SYNC)
-                            .setContentTitle(title)
-                            .setContentText(subtitle)
-                            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
-                            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
-                            .setAutoCancel(false)
-                            .setOngoing(false)
-                            .setVibrate(null)
-                            .setColorized(true)
-                            .setColor(getResources().getColor(R.color.color_primary_dark))
-                            .setSmallIcon(R.drawable.ic_ring_logo_white)
-                            .setCategory(NotificationCompat.CATEGORY_PROGRESS)
-                            .setOnlyAlertOnce(true)
-                            .setDeleteIntent(PendingIntent.getService(getApplicationContext(), mRandom.nextInt(), stopIntent, 0))
-                            .setContentIntent(PendingIntent.getActivity(getApplicationContext(), mRandom.nextInt(), contentIntent, 0))
-                            .addAction(R.drawable.baseline_location_disabled_24,
-                                    getText(R.string.notif_location_action_stop),
-                                    PendingIntent.getService(
-                                            getApplicationContext(),
-                                            0,
-                                            stopIntent,
-                                            PendingIntent.FLAG_ONE_SHOT))
-                            .build();
-                })
-                .subscribeOn(Schedulers.computation())
-                .observeOn(AndroidSchedulers.mainThread());
-    }
-
-    private void refreshSharing() {
-        if (!started)
-            return;
-
-        boolean changed = false;
-        final Date now = new Date(SystemClock.uptimeMillis());
-        Iterator<Map.Entry<ConversationPath, Date>> it = contactLocationShare.entrySet().iterator();
-        while (it.hasNext())  {
-            Map.Entry<ConversationPath, Date> e = it.next();
-            if (e.getValue().before(now)) {
-                changed = true;
-                it.remove();
-            }
-        }
-
-        if (changed)
-            mContactSharingSubject.onNext(contactLocationShare.keySet());
-
-        if (contactLocationShare.isEmpty()) {
-            mDisposableBag.clear();
-            stopForeground(true);
-            stopSelf();
-            started = false;
-        } else if (changed) {
-            mDisposableBag.add(getNotification(now.getTime())
-                    .subscribe(notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification)));
-        }
-    }
-
-    private void refreshNotificationTimer() {
-        if (!started)
-            return;
-        long now = SystemClock.uptimeMillis();
-        mDisposableBag.add(getNotification(now)
-                .subscribe(notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification)));
-        mHandler.postAtTime(this::refreshNotificationTimer, now + (30 * 1000));
-    }
-
-    public boolean isSharing(ConversationPath path) {
-        return contactLocationShare.get(path) != null;
-    }
-
-    public class LocalBinder extends Binder {
-        public LocationSharingService getService() {
-            return LocationSharingService.this;
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.kt b/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.kt
new file mode 100644
index 000000000..a9f9b5170
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/service/LocationSharingService.kt
@@ -0,0 +1,366 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.service
+
+import android.Manifest
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.location.Criteria
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.os.*
+import android.util.Log
+import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
+import cx.ring.R
+import cx.ring.application.JamiApplication
+import cx.ring.client.ConversationActivity
+import cx.ring.fragments.ConversationFragment
+import cx.ring.services.NotificationServiceImpl
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.ConversationPath.Companion.fromIntent
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.daemon.Blob
+import net.jami.daemon.JamiService
+import net.jami.daemon.StringMap
+import net.jami.services.ConversationFacade
+import net.jami.services.AccountService
+import net.jami.services.CallService
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import kotlin.math.ceil
+
+@AndroidEntryPoint
+class LocationSharingService : Service(), LocationListener {
+    @Inject
+    lateinit var mConversationFacade: ConversationFacade
+
+    private val mRandom = Random()
+    private val binder: IBinder = LocalBinder()
+    private var started = false
+    private var mLocationManager: LocationManager? = null
+    private lateinit var mNotificationManager: NotificationManager
+    private lateinit var mPreferences: SharedPreferences
+    private lateinit var mHandler: Handler
+    private val mMyLocationSubject: Subject<Location> = BehaviorSubject.create()
+    private val contactLocationShare: MutableMap<ConversationPath, Date> = HashMap()
+    private val mContactSharingSubject: Subject<Set<ConversationPath>> =
+        BehaviorSubject.createDefault(contactLocationShare.keys)
+    private val mDisposableBag = CompositeDisposable()
+
+    val myLocation: Observable<Location>
+        get() = mMyLocationSubject
+    val contactSharing: Observable<Set<ConversationPath>>
+        get() = mContactSharingSubject
+
+    fun getContactSharingExpiration(path: ConversationPath?): Observable<Long> {
+        return Observable.timer(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
+            .startWithItem(0L)
+            .repeat()
+            .map { contactLocationShare[path]!!.time - SystemClock.elapsedRealtime() }
+            .onErrorComplete()
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+        mLocationManager = getSystemService(LOCATION_SERVICE) as LocationManager
+        mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+        mHandler = Handler(mainLooper)
+        mPreferences = getSharedPreferences(PREFERENCES_LOCATION, MODE_PRIVATE)
+        val posLongitude = mPreferences.getString(PREFERENCES_KEY_POS_LONG, null)
+        val posLatitude = mPreferences.getString(PREFERENCES_KEY_POS_LAT, null)
+        if (posLatitude != null && posLongitude != null) {
+            try {
+                val location = Location("cache")
+                location.latitude = posLatitude.toDouble()
+                location.longitude = posLongitude.toDouble()
+                mMyLocationSubject.onNext(location)
+            } catch (e: Exception) {
+                Log.w(TAG, "Can't load last location", e)
+            }
+        }
+        mLocationManager?.let { locationManager ->
+            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+                || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
+            ) {
+                try {
+                    val c = Criteria()
+                    c.accuracy = Criteria.ACCURACY_FINE
+                    locationManager.requestLocationUpdates(0, 0f, c, this, null)
+                } catch (e: Exception) {
+                    Log.e(TAG, "Can't start location tracking", e)
+                }
+            }
+        }
+    }
+
+    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+        Log.w(TAG, "onStartCommand $intent")
+        val action = intent.action
+        val path = fromIntent(intent)
+        val now = SystemClock.elapsedRealtime()
+        if (ACTION_START == action) {
+            val duration = intent.getIntExtra(EXTRA_SHARING_DURATION, SHARE_DURATION_SEC)
+            val expiration = now + duration * 1000L
+            if (contactLocationShare.put(path!!, Date(expiration)) == null) {
+                mContactSharingSubject.onNext(contactLocationShare.keys)
+            }
+            mHandler.postAtTime({ refreshSharing() }, expiration)
+            if (!started) {
+                started = true
+                mDisposableBag.add(getNotification(now)
+                    .subscribe { notification ->
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+                            startForeground(NOTIF_SYNC_SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
+                        else
+                            startForeground(NOTIF_SYNC_SERVICE_ID, notification)
+                        mHandler.postAtTime({ refreshNotificationTimer() }, now + 30 * 1000)
+                        JamiApplication.instance?.startDaemon()
+                    })
+                mDisposableBag.add(mMyLocationSubject
+                    .throttleLatest(10, TimeUnit.SECONDS)
+                    .map { location ->
+                        val out = JSONObject()
+                        out.put("type", AccountService.Location.Type.Position.toString())
+                        out.put("lat", location.latitude)
+                        out.put("long", location.longitude)
+                        out.put("alt", location.altitude)
+                        out.put("time", location.elapsedRealtimeNanos / 1000000L)
+                        val bearing = location.bearing
+                        if (bearing != 0f) out.put("bearing", bearing.toDouble())
+                        val speed = location.speed
+                        if (speed != 0f) out.put("speed", speed.toDouble())
+                        out
+                    }
+                    .subscribe { location: JSONObject ->
+                        Log.w(TAG, "location send " + location + " to " + contactLocationShare.size)
+                        val msgs = StringMap()
+                        msgs.setRaw(CallService.MIME_GEOLOCATION, Blob.fromString(location.toString()))
+                        for (p in contactLocationShare.keys)
+                            JamiService.sendAccountTextMessage(p.accountId, p.conversationId, msgs)
+                    })
+            } else {
+                mDisposableBag.add(getNotification(now)
+                    .subscribe { notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification) })
+            }
+        } else if (ACTION_STOP == action) {
+            if (path == null) contactLocationShare.clear() else {
+                contactLocationShare.remove(path)
+                val jsonObject = JSONObject()
+                try {
+                    jsonObject.put("type", AccountService.Location.Type.Stop.toString())
+                    jsonObject.put("time", Long.MAX_VALUE)
+                } catch (e: JSONException) {
+                    e.printStackTrace()
+                }
+                Log.w(TAG, "location send " + jsonObject + " to " + contactLocationShare.size)
+                val msgs = StringMap()
+                msgs.setRaw(CallService.MIME_GEOLOCATION, Blob.fromString(jsonObject.toString()))
+                JamiService.sendAccountTextMessage(path.accountId, path.conversationId, msgs)
+            }
+            mContactSharingSubject.onNext(contactLocationShare.keys)
+            if (contactLocationShare.isEmpty()) {
+                Log.w(TAG, "stopping sharing $intent")
+                mDisposableBag.clear()
+                stopForeground(true)
+                stopSelf()
+                started = false
+            } else {
+                mDisposableBag.add(getNotification(now)
+                    .subscribe { notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification) })
+            }
+        }
+        return START_NOT_STICKY
+    }
+
+    override fun onDestroy() {
+        Log.w(TAG, "onDestroy")
+        mLocationManager?.removeUpdates(this)
+        mMyLocationSubject.onComplete()
+        mContactSharingSubject.onComplete()
+        mDisposableBag.dispose()
+    }
+
+    override fun onBind(intent: Intent): IBinder {
+        return binder
+    }
+
+    override fun onUnbind(intent: Intent): Boolean {
+        return true
+    }
+
+    override fun onLocationChanged(location: Location) {
+        // Log.w(TAG, "onLocationChanged " + location.toString());
+        mMyLocationSubject.onNext(location)
+        mPreferences.edit()
+            .putString(PREFERENCES_KEY_POS_LAT, location.latitude.toString())
+            .putString(PREFERENCES_KEY_POS_LONG, location.longitude.toString())
+            .apply()
+    }
+
+    override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
+    override fun onProviderEnabled(provider: String) {}
+    override fun onProviderDisabled(provider: String) {}
+    private fun getNotification(now: Long): Single<Notification> {
+        val contactCount = contactLocationShare.size
+        val firsPath = contactLocationShare.keys.iterator().next()
+        var largest: Date? = null
+        for (d in contactLocationShare.values)
+            if (largest == null || d.after(largest))
+            largest = d
+        val largestDate = largest?.time ?: now
+        // Log.w(TAG, "getNotification " + firsPath.getContactId());
+        return mConversationFacade.getAccountSubject(firsPath.accountId)
+            .map { account -> account.getContactFromCache(firsPath.conversationUri) }
+            .map { contact ->
+                val title: String
+                val stopIntent = Intent(ACTION_STOP).setClass(applicationContext, LocationSharingService::class.java)
+                val contentIntent = Intent(
+                    Intent.ACTION_VIEW,
+                    firsPath.toUri(),
+                    applicationContext,
+                    ConversationActivity::class.java
+                )
+                    .putExtra(ConversationFragment.EXTRA_SHOW_MAP, true)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                if (contactCount == 1) {
+                    stopIntent.data = firsPath.toUri()
+                    title = getString(R.string.notif_location_title, contact.displayName)
+                } else {
+                    title = getString(R.string.notif_location_multi_title, contactCount)
+                }
+                val subtitle = getString(R.string.notif_location_remaining,
+                    ceil((largestDate - now) / (1000 * 60).toDouble()).toInt())
+                NotificationCompat.Builder(this, NotificationServiceImpl.NOTIF_CHANNEL_SYNC)
+                    .setContentTitle(title)
+                    .setContentText(subtitle)
+                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                    .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
+                    .setAutoCancel(false)
+                    .setOngoing(false)
+                    .setVibrate(null)
+                    .setColorized(true)
+                    .setColor(resources.getColor(R.color.color_primary_dark))
+                    .setSmallIcon(R.drawable.ic_ring_logo_white)
+                    .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+                    .setOnlyAlertOnce(true)
+                    .setDeleteIntent(
+                        PendingIntent.getService(
+                            applicationContext,
+                            mRandom.nextInt(),
+                            stopIntent,
+                            0
+                        )
+                    )
+                    .setContentIntent(
+                        PendingIntent.getActivity(
+                            applicationContext,
+                            mRandom.nextInt(),
+                            contentIntent,
+                            0
+                        )
+                    )
+                    .addAction(
+                        R.drawable.baseline_location_disabled_24,
+                        getText(R.string.notif_location_action_stop),
+                        PendingIntent.getService(
+                            applicationContext,
+                            0,
+                            stopIntent,
+                            PendingIntent.FLAG_ONE_SHOT
+                        )
+                    )
+                    .build()
+            }
+            .subscribeOn(Schedulers.computation())
+            .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    private fun refreshSharing() {
+        if (!started) return
+        var changed = false
+        val now = Date(SystemClock.uptimeMillis())
+        val it: MutableIterator<Map.Entry<ConversationPath?, Date?>> =
+            contactLocationShare.entries.iterator()
+        while (it.hasNext()) {
+            val e = it.next()
+            if (e.value!!.before(now)) {
+                changed = true
+                it.remove()
+            }
+        }
+        if (changed) mContactSharingSubject.onNext(contactLocationShare.keys)
+        if (contactLocationShare.isEmpty()) {
+            mDisposableBag.clear()
+            stopForeground(true)
+            stopSelf()
+            started = false
+        } else if (changed) {
+            mDisposableBag.add(getNotification(now.time)
+                .subscribe { notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification) })
+        }
+    }
+
+    private fun refreshNotificationTimer() {
+        if (!started) return
+        val now = SystemClock.uptimeMillis()
+        mDisposableBag.add(getNotification(now)
+            .subscribe { notification -> mNotificationManager.notify(NOTIF_SYNC_SERVICE_ID, notification) })
+        mHandler.postAtTime({ refreshNotificationTimer() }, now + 30 * 1000)
+    }
+
+    fun isSharing(path: ConversationPath?): Boolean {
+        return contactLocationShare[path] != null
+    }
+
+    inner class LocalBinder : Binder() {
+        val service: LocationSharingService
+            get() = this@LocationSharingService
+    }
+
+    companion object {
+        private const val TAG = "LocationSharingService"
+        const val NOTIF_SYNC_SERVICE_ID = 931801
+        const val ACTION_START = "startSharing"
+        const val ACTION_STOP = "stopSharing"
+        const val EXTRA_SHARING_DURATION = "locationShareDuration"
+        const val PREFERENCES_LOCATION = "location"
+        const val PREFERENCES_KEY_POS_LONG = "lastPosLongitude"
+        const val PREFERENCES_KEY_POS_LAT = "lastPosLatitude"
+        const val SHARE_DURATION_SEC = 60 * 5
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/service/SyncService.java b/ring-android/app/src/main/java/cx/ring/service/SyncService.java
index 9b0ac420d..48d2dab7c 100644
--- a/ring-android/app/src/main/java/cx/ring/service/SyncService.java
+++ b/ring-android/app/src/main/java/cx/ring/service/SyncService.java
@@ -42,7 +42,9 @@ import cx.ring.R;
 import cx.ring.application.JamiApplication;
 import cx.ring.client.HomeActivity;
 import cx.ring.services.NotificationServiceImpl;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class SyncService extends Service {
     public static final int NOTIF_SYNC_SERVICE_ID = 1004;
 
@@ -58,12 +60,6 @@ public class SyncService extends Service {
     @Inject
     NotificationService mNotificationService;
 
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        ((JamiApplication) getApplication()).getInjectionComponent().inject(this);
-    }
-
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         String action = intent.getAction();
diff --git a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
deleted file mode 100644
index cf9cccf02..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.provider.ContactsContract;
-import android.util.Base64;
-import android.util.Log;
-import android.util.LongSparseArray;
-
-import androidx.annotation.NonNull;
-
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.Phone;
-import net.jami.model.Uri;
-import net.jami.services.ContactService;
-import net.jami.utils.Tuple;
-import net.jami.utils.VCardUtils;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.views.AvatarFactory;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class ContactServiceImpl extends ContactService {
-
-    private static final String TAG = ContactServiceImpl.class.getSimpleName();
-
-    private static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
-            ContactsContract.Contacts._ID,
-            ContactsContract.Contacts.LOOKUP_KEY,
-            ContactsContract.Contacts.DISPLAY_NAME,
-            ContactsContract.Contacts.PHOTO_ID,
-            ContactsContract.Contacts.STARRED
-    };
-
-    private static final String[] CONTACTS_DATA_PROJECTION = new String[]{
-            ContactsContract.Data.CONTACT_ID,
-            ContactsContract.Data.MIMETYPE,
-            ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS,
-            ContactsContract.CommonDataKinds.SipAddress.TYPE,
-            ContactsContract.CommonDataKinds.SipAddress.LABEL,
-            ContactsContract.CommonDataKinds.Im.PROTOCOL,
-            ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL
-    };
-
-    private static final String[] CONTACT_PROJECTION = {
-            ContactsContract.Contacts._ID,
-            ContactsContract.Contacts.LOOKUP_KEY,
-            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
-            ContactsContract.Contacts.PHOTO_ID,
-            ContactsContract.Contacts.STARRED
-    };
-
-    private static final String[] CONTACTS_PHONES_PROJECTION = {
-            ContactsContract.CommonDataKinds.Phone.NUMBER,
-            ContactsContract.CommonDataKinds.Phone.TYPE,
-            ContactsContract.CommonDataKinds.Phone.LABEL
-    };
-
-    private static final String[] CONTACTS_SIP_PROJECTION = {
-            ContactsContract.Data.MIMETYPE,
-            ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS,
-            ContactsContract.CommonDataKinds.SipAddress.TYPE,
-            ContactsContract.CommonDataKinds.SipAddress.LABEL
-    };
-
-    private static final String[] DATA_PROJECTION = {
-            ContactsContract.Data._ID,
-            ContactsContract.RawContacts.CONTACT_ID,
-            ContactsContract.Data.LOOKUP_KEY,
-            ContactsContract.Data.DISPLAY_NAME_PRIMARY,
-            ContactsContract.Data.PHOTO_ID,
-            ContactsContract.Data.PHOTO_THUMBNAIL_URI,
-            ContactsContract.Data.STARRED
-    };
-
-    private static final String[] PHONELOOKUP_PROJECTION = {
-            ContactsContract.PhoneLookup._ID,
-            ContactsContract.PhoneLookup.LOOKUP_KEY,
-            ContactsContract.PhoneLookup.PHOTO_ID,
-            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
-    };
-
-    private static final String ID_SELECTION = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?";
-
-    @Inject
-    Context mContext;
-
-    @Override
-    public Map<Long, Contact> loadContactsFromSystem(boolean loadRingContacts, boolean loadSipContacts) {
-
-        Map<Long, Contact> systemContacts = new HashMap<>();
-        ContentResolver contentResolver = mContext.getContentResolver();
-        StringBuilder contactsIds = new StringBuilder();
-        LongSparseArray<Contact> cache;
-
-        Cursor contactCursor = contentResolver.query(ContactsContract.Data.CONTENT_URI, CONTACTS_DATA_PROJECTION,
-                ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=?",
-                new String[]{ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE}, null);
-
-        if (contactCursor != null) {
-            cache = new LongSparseArray<>(contactCursor.getCount());
-            contactsIds.ensureCapacity(contactCursor.getCount() * 4);
-
-            final int indexId = contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID);
-            final int indexMime = contactCursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
-            final int indexNumber = contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS);
-            final int indexType = contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE);
-            final int indexLabel = contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL);
-
-            while (contactCursor.moveToNext()) {
-                long contactId = contactCursor.getLong(indexId);
-
-                String contactNumber = contactCursor.getString(indexNumber);
-                int contactType = contactCursor.getInt(indexType);
-                String contactLabel = contactCursor.getString(indexLabel);
-                Uri uri = Uri.fromString(contactNumber);
-
-                Contact contact = cache.get(contactId);
-                boolean isNewContact = false;
-                if (contact == null) {
-                    contact = new Contact(uri);
-                    contact.setSystemId(contactId);
-                    isNewContact = true;
-                    contact.setFromSystem(true);
-                }
-
-                if (uri.isSingleIp() || (uri.isHexId() && loadRingContacts) || loadSipContacts) {
-                    switch (contactCursor.getString(indexMime)) {
-                        case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
-                            contact.addPhoneNumber(uri, contactType, contactLabel);
-                            break;
-                        case ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE:
-                            contact.addNumber(uri, contactType, contactLabel, Phone.NumberType.SIP);
-                            break;
-                        case ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE:
-                            if (uri.isHexId()) {
-                                contact.addNumber(uri, contactType, contactLabel, Phone.NumberType.UNKNOWN);
-                            }
-                            break;
-                    }
-                }
-
-                if (isNewContact && !contact.getPhones().isEmpty()) {
-                    cache.put(contactId, contact);
-                    if (contactsIds.length() > 0) {
-                        contactsIds.append(",");
-                    }
-                    contactsIds.append(contactId);
-                }
-            }
-            contactCursor.close();
-        } else {
-            cache = new LongSparseArray<>();
-        }
-
-        contactCursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, CONTACTS_SUMMARY_PROJECTION,
-                ContactsContract.Contacts._ID + " in (" + contactsIds.toString() + ")", null,
-                ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
-
-        if (contactCursor != null) {
-            final int indexId = contactCursor.getColumnIndex(ContactsContract.Contacts._ID);
-            final int indexKey = contactCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
-            final int indexName = contactCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
-            final int indexPhoto = contactCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_ID);
-
-            while (contactCursor.moveToNext()) {
-                long contactId = contactCursor.getLong(indexId);
-                Contact contact = cache.get(contactId);
-                if (contact == null)
-                    Log.w(TAG, "Can't find contact with ID " + contactId);
-                else {
-                    contact.setSystemContactInfo(contactId, contactCursor.getString(indexKey), contactCursor.getString(indexName), contactCursor.getLong(indexPhoto));
-                    systemContacts.put(contactId, contact);
-                }
-            }
-            contactCursor.close();
-        }
-
-        return systemContacts;
-    }
-
-    @Override
-    protected Contact findContactByIdFromSystem(Long id, String key) {
-        Contact contact = null;
-        ContentResolver contentResolver = mContext.getContentResolver();
-
-        try {
-            android.net.Uri contentUri;
-            if (key != null) {
-                contentUri = ContactsContract.Contacts.lookupContact(
-                        contentResolver,
-                        ContactsContract.Contacts.getLookupUri(id, key));
-            } else {
-                contentUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id);
-            }
-
-            Cursor result = null;
-            if (contentUri != null) {
-                result = contentResolver.query(contentUri, CONTACT_PROJECTION, null, null, null);
-            }
-
-            if (result == null) {
-                return null;
-            }
-
-            if (result.moveToFirst()) {
-                int indexId = result.getColumnIndex(ContactsContract.Data._ID);
-                int indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
-                int indexName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
-                int indexPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID);
-                int indexStared = result.getColumnIndex(ContactsContract.Contacts.STARRED);
-
-                long contactId = result.getLong(indexId);
-
-                Log.d(TAG, "Contact name: " + result.getString(indexName) + " id:" + contactId + " key:" + result.getString(indexKey));
-
-                contact = new Contact(Uri.fromString(contentUri.toString()));
-                contact.setSystemContactInfo(contactId, result.getString(indexKey), result.getString(indexName), result.getLong(indexPhoto));
-
-                if (result.getInt(indexStared) != 0) {
-                    contact.setStared();
-                }
-
-                fillContactDetails(contact);
-            }
-
-            result.close();
-        } catch (Exception e) {
-            Log.d(TAG, "findContactByIdFromSystem: Error while searching for contact id=" + id, e);
-        }
-
-        if (contact == null) {
-            Log.d(TAG, "findContactByIdFromSystem: findById " + id + " can't find contact.");
-        }
-
-        return contact;
-    }
-
-    private void fillContactDetails(@NonNull Contact contact) {
-        ContentResolver contentResolver = mContext.getContentResolver();
-
-        try {
-            Cursor cursorPhones = contentResolver.query(
-                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
-                    CONTACTS_PHONES_PROJECTION, ID_SELECTION,
-                    new String[]{String.valueOf(contact.getId())}, null);
-
-            if (cursorPhones != null) {
-                final int indexNumber = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
-                final int indexType = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE);
-                final int indexLabel = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL);
-
-                while (cursorPhones.moveToNext()) {
-                    contact.addNumber(cursorPhones.getString(indexNumber), cursorPhones.getInt(indexType), cursorPhones.getString(indexLabel), Phone.NumberType.TEL);
-                    Log.d(TAG, "Phone:" + cursorPhones.getString(cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
-                }
-
-                cursorPhones.close();
-            }
-
-            android.net.Uri baseUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.getId());
-            android.net.Uri targetUri = android.net.Uri.withAppendedPath(baseUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY);
-
-            Cursor cursorSip = contentResolver.query(
-                    targetUri,
-                    CONTACTS_SIP_PROJECTION,
-                    ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + " =?",
-                    new String[]{ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE}, null);
-
-            if (cursorSip != null) {
-                final int indexMime = cursorSip.getColumnIndex(ContactsContract.Data.MIMETYPE);
-                final int indexSip = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS);
-                final int indexType = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE);
-                final int indexLabel = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL);
-
-                while (cursorSip.moveToNext()) {
-                    String contactMime = cursorSip.getString(indexMime);
-                    String contactNumber = cursorSip.getString(indexSip);
-
-                    if (!contactMime.contentEquals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) || Uri.fromString(contactNumber).isHexId() || "ring".equalsIgnoreCase(cursorSip.getString(indexLabel))) {
-                        contact.addNumber(contactNumber, cursorSip.getInt(indexType), cursorSip.getString(indexLabel), Phone.NumberType.SIP);
-                    }
-                    Log.d(TAG, "SIP phone:" + contactNumber + " " + contactMime + " ");
-                }
-                cursorSip.close();
-            }
-        } catch (Exception e) {
-            Log.d(TAG, "fillContactDetails: Error while retrieving detail contact info", e);
-        }
-    }
-
-    public Contact findContactBySipNumberFromSystem(String number) {
-        Contact contact = null;
-        ContentResolver contentResolver = mContext.getContentResolver();
-
-        try {
-            Cursor result = contentResolver.query(ContactsContract.Data.CONTENT_URI,
-                    DATA_PROJECTION,
-                    ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS + "=?" + " AND (" + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=?)",
-                    new String[]{number, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE}, null);
-
-            if (result == null) {
-                Log.d(TAG, "findContactBySipNumberFromSystem: " + number + " can't find contact.");
-                return Contact.buildSIP(Uri.fromString(number));
-            }
-
-            int indexId = result.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID);
-            int indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
-            int indexName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
-            int indexPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID);
-            int indexStared = result.getColumnIndex(ContactsContract.Contacts.STARRED);
-
-            if (result.moveToFirst()) {
-                long contactId = result.getLong(indexId);
-                contact = new Contact(Uri.fromString(number));
-                contact.setSystemContactInfo(contactId, result.getString(indexKey), result.getString(indexName), result.getLong(indexPhoto));
-
-                if (result.getInt(indexStared) != 0) {
-                    contact.setStared();
-                }
-
-                fillContactDetails(contact);
-            }
-            result.close();
-
-            if (contact == null || contact.getPhones() == null || contact.getPhones().isEmpty()) {
-                return null;
-            }
-        } catch (Exception e) {
-            Log.d(TAG, "findContactBySipNumberFromSystem: Error while searching for contact number=" + number, e);
-        }
-
-        return contact;
-    }
-
-    public Contact findContactByNumberFromSystem(String number) {
-        Contact contact = null;
-        ContentResolver contentResolver = mContext.getContentResolver();
-
-        try {
-            android.net.Uri uri = android.net.Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, android.net.Uri.encode(number));
-            Cursor result = contentResolver.query(uri, PHONELOOKUP_PROJECTION, null, null, null);
-            if (result == null) {
-                Log.d(TAG, "findContactByNumberFromSystem: " + number + " can't find contact.");
-                return findContactBySipNumberFromSystem(number);
-            }
-            if (result.moveToFirst()) {
-                int indexId = result.getColumnIndex(ContactsContract.Contacts._ID);
-                int indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
-                int indexName = result.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
-                int indexPhoto = result.getColumnIndex(ContactsContract.Contacts.PHOTO_ID);
-                contact = new Contact(Uri.fromString(number));
-                contact.setSystemContactInfo(result.getLong(indexId), result.getString(indexKey), result.getString(indexName), result.getLong(indexPhoto));
-                fillContactDetails(contact);
-                Log.d(TAG, "findContactByNumberFromSystem: " + number + " found " + contact.getDisplayName());
-            }
-            result.close();
-        } catch (Exception e) {
-            Log.d(TAG, "findContactByNumber: Error while searching for contact number=" + number, e);
-        }
-
-        if (contact == null) {
-            Log.d(TAG, "findContactByNumberFromSystem: " + number + " can't find contact.");
-            contact = findContactBySipNumberFromSystem(number);
-        }
-
-        if (contact != null)
-            contact.setFromSystem(true);
-        return contact;
-    }
-
-    @Override
-    public Completable loadContactData(Contact contact, String accountId) {
-        if (!contact.detailsLoaded) {
-            Single<Tuple<String, Object>> profile = contact.isFromSystem() ? loadSystemContactData(contact) : loadVCardContactData(contact, accountId);
-            return profile
-                    .doOnSuccess(p -> contact.setProfile(p.first, p.second))
-                    .doOnError(e -> contact.setProfile(null, null))
-                    .ignoreElement()
-                    .onErrorComplete();
-        }
-        return Completable.complete();
-    }
-
-    @Override
-    public void saveVCardContactData(Contact contact, String accountId, VCard vcard) {
-        if (vcard != null) {
-            Tuple<String, Object> profileData = VCardServiceImpl.readData(vcard);
-            contact.setProfile(profileData.first, profileData.second);
-            String filename = contact.getPrimaryNumber() + ".vcf";
-            VCardUtils.savePeerProfileToDisk(vcard, accountId
-                    , filename, mContext.getFilesDir());
-            AvatarFactory.clearCache();
-        }
-    }
-
-    @Override
-    public Single<VCard> saveVCardContact(String accountId, String uri, String displayName, String picture)
-    {
-        return Single.fromCallable(() -> {
-            VCard vcard = VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT));
-            String filename = uri + ".vcf";
-            VCardUtils.savePeerProfileToDisk(vcard, accountId, filename, mContext.getFilesDir());
-            return vcard;
-        });
-    }
-
-    private Single<Tuple<String, Object>> loadVCardContactData(Contact contact, String accountId) {
-        String id = contact.getPrimaryNumber();
-        if (id != null) {
-            return Single.fromCallable(() -> VCardUtils.loadPeerProfileFromDisk(mContext.getFilesDir(), id + ".vcf", accountId))
-                    .map(VCardServiceImpl::readData)
-                    .subscribeOn(Schedulers.computation());
-        }
-        return Single.error(new IllegalArgumentException());
-    }
-
-    private Single<Tuple<String, Object>> loadSystemContactData(Contact contact) {
-        String contactName = contact.getDisplayName();
-        android.net.Uri photoURI = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.getId());
-        return AndroidFileUtils
-                .loadBitmap(mContext, android.net.Uri.withAppendedPath(photoURI, ContactsContract.Contacts.Photo.DISPLAY_PHOTO))
-                .map(bitmap -> new Tuple<String, Object>(contactName, bitmap))
-                .onErrorReturn(e -> new Tuple<>(contactName, null))
-                .subscribeOn(Schedulers.io());
-    }
-
-    public Single<Drawable> loadConversationAvatar(@NonNull Context context, @NonNull Conversation conversation) {
-        return getLoadedContact(conversation.getAccountId(), conversation.getContacts(), false)
-                .flatMap(contacts -> AvatarFactory.getAvatar(context, conversation, false));
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
new file mode 100644
index 000000000..d567f54b6
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt
@@ -0,0 +1,500 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.content.ContentUris
+import android.content.Context
+import android.database.Cursor
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.provider.ContactsContract
+import android.util.Base64
+import android.util.Log
+import android.util.LongSparseArray
+import cx.ring.utils.AndroidFileUtils
+import cx.ring.views.AvatarFactory
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Contact
+import net.jami.model.Conversation
+import net.jami.model.Phone
+import net.jami.services.AccountService
+import net.jami.services.ContactService
+import net.jami.services.DeviceRuntimeService
+import net.jami.services.PreferencesService
+import net.jami.utils.Tuple
+import net.jami.utils.VCardUtils
+
+class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesService,
+                         deviceRuntimeService : DeviceRuntimeService,
+                         accountService: AccountService
+) : ContactService(preferenceService, deviceRuntimeService, accountService) {
+    override fun loadContactsFromSystem(
+        loadRingContacts: Boolean,
+        loadSipContacts: Boolean
+    ): Map<Long, Contact> {
+        val systemContacts: MutableMap<Long, Contact> = HashMap()
+        val contentResolver = mContext.contentResolver
+        val contactsIds = StringBuilder()
+        val cache: LongSparseArray<Contact>
+        var contactCursor = contentResolver.query(
+            ContactsContract.Data.CONTENT_URI,
+            CONTACTS_DATA_PROJECTION,
+            ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=?",
+            arrayOf(
+                ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
+                ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
+                ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
+            ),
+            null
+        )
+        if (contactCursor != null) {
+            cache = LongSparseArray(contactCursor.count)
+            contactsIds.ensureCapacity(contactCursor.count * 4)
+            val indexId =
+                contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID)
+            val indexMime = contactCursor.getColumnIndex(ContactsContract.Data.MIMETYPE)
+            val indexNumber =
+                contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS)
+            val indexType =
+                contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE)
+            val indexLabel =
+                contactCursor.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL)
+            while (contactCursor.moveToNext()) {
+                val contactId = contactCursor.getLong(indexId)
+                val contactNumber = contactCursor.getString(indexNumber)
+                val contactType = contactCursor.getInt(indexType)
+                val contactLabel = contactCursor.getString(indexLabel)
+                val uri = net.jami.model.Uri.fromString(contactNumber)
+                var contact = cache[contactId]
+                var isNewContact = false
+                if (contact == null) {
+                    contact = Contact(uri)
+                    contact.setSystemId(contactId)
+                    isNewContact = true
+                    contact.isFromSystem = true
+                }
+                if (uri.isSingleIp || uri.isHexId && loadRingContacts || loadSipContacts) {
+                    when (contactCursor.getString(indexMime)) {
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> contact.addPhoneNumber(
+                            uri,
+                            contactType,
+                            contactLabel
+                        )
+                        ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> contact.addNumber(
+                            uri,
+                            contactType,
+                            contactLabel,
+                            Phone.NumberType.SIP
+                        )
+                        ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE -> if (uri.isHexId) {
+                            contact.addNumber(
+                                uri,
+                                contactType,
+                                contactLabel,
+                                Phone.NumberType.UNKNOWN
+                            )
+                        }
+                    }
+                }
+                if (isNewContact && contact.phones.isNotEmpty()) {
+                    cache.put(contactId, contact)
+                    if (contactsIds.isNotEmpty()) {
+                        contactsIds.append(",")
+                    }
+                    contactsIds.append(contactId)
+                }
+            }
+            contactCursor.close()
+        } else {
+            cache = LongSparseArray()
+        }
+        contactCursor = contentResolver.query(
+            ContactsContract.Contacts.CONTENT_URI, CONTACTS_SUMMARY_PROJECTION,
+            ContactsContract.Contacts._ID + " in (" + contactsIds.toString() + ")", null,
+            ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"
+        )
+        if (contactCursor != null) {
+            val indexId = contactCursor.getColumnIndex(ContactsContract.Contacts._ID)
+            val indexKey = contactCursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)
+            val indexName = contactCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
+            val indexPhoto = contactCursor.getColumnIndex(ContactsContract.Contacts.PHOTO_ID)
+            while (contactCursor.moveToNext()) {
+                val contactId = contactCursor.getLong(indexId)
+                val contact = cache[contactId]
+                if (contact == null) Log.w(TAG, "Can't find contact with ID $contactId") else {
+                    contact.setSystemContactInfo(
+                        contactId,
+                        contactCursor.getString(indexKey),
+                        contactCursor.getString(indexName),
+                        contactCursor.getLong(indexPhoto)
+                    )
+                    systemContacts[contactId] = contact
+                }
+            }
+            contactCursor.close()
+        }
+        return systemContacts
+    }
+
+    override fun findContactByIdFromSystem(id: Long, key: String): Contact? {
+        var contact: Contact? = null
+        val contentResolver = mContext.contentResolver
+        try {
+            val contentUri: Uri? = if (key != null) {
+                ContactsContract.Contacts.lookupContact(
+                    contentResolver,
+                    ContactsContract.Contacts.getLookupUri(id, key)
+                )
+            } else {
+                ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id)
+            }
+            var result: Cursor? = null
+            if (contentUri != null) {
+                result = contentResolver.query(contentUri, CONTACT_PROJECTION, null, null, null)
+            }
+            if (result == null) {
+                return null
+            }
+            if (result.moveToFirst()) {
+                val indexId = result.getColumnIndex(ContactsContract.Data._ID)
+                val indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)
+                val indexName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)
+                val indexPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID)
+                val indexStared = result.getColumnIndex(ContactsContract.Contacts.STARRED)
+                val contactId = result.getLong(indexId)
+                Log.d(
+                    TAG,
+                    "Contact name: " + result.getString(indexName) + " id:" + contactId + " key:" + result.getString(
+                        indexKey
+                    )
+                )
+                contact = Contact(net.jami.model.Uri.fromString(contentUri.toString()))
+                contact.setSystemContactInfo(
+                    contactId,
+                    result.getString(indexKey),
+                    result.getString(indexName),
+                    result.getLong(indexPhoto)
+                )
+                if (result.getInt(indexStared) != 0) {
+                    contact.setStared()
+                }
+                fillContactDetails(contact)
+            }
+            result.close()
+        } catch (e: Exception) {
+            Log.d(TAG, "findContactByIdFromSystem: Error while searching for contact id=$id", e)
+        }
+        if (contact == null) {
+            Log.d(TAG, "findContactByIdFromSystem: findById $id can't find contact.")
+        }
+        return contact!!
+    }
+
+    private fun fillContactDetails(contact: Contact) {
+        val contentResolver = mContext.contentResolver
+        try {
+            val cursorPhones = contentResolver.query(
+                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
+                CONTACTS_PHONES_PROJECTION, ID_SELECTION, arrayOf(contact.id.toString()), null
+            )
+            if (cursorPhones != null) {
+                val indexNumber =
+                    cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
+                val indexType =
+                    cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)
+                val indexLabel =
+                    cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL)
+                while (cursorPhones.moveToNext()) {
+                    contact.addNumber(
+                        cursorPhones.getString(indexNumber),
+                        cursorPhones.getInt(indexType),
+                        cursorPhones.getString(indexLabel),
+                        Phone.NumberType.TEL
+                    )
+                    Log.d(
+                        TAG,
+                        "Phone:" + cursorPhones.getString(
+                            cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
+                        )
+                    )
+                }
+                cursorPhones.close()
+            }
+            val baseUri =
+                ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id)
+            val targetUri =
+                Uri.withAppendedPath(baseUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY)
+            val cursorSip = contentResolver.query(
+                targetUri,
+                CONTACTS_SIP_PROJECTION,
+                ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + " =?",
+                arrayOf(
+                    ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
+                    ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
+                ),
+                null
+            )
+            if (cursorSip != null) {
+                val indexMime = cursorSip.getColumnIndex(ContactsContract.Data.MIMETYPE)
+                val indexSip =
+                    cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS)
+                val indexType =
+                    cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE)
+                val indexLabel =
+                    cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL)
+                while (cursorSip.moveToNext()) {
+                    val contactMime = cursorSip.getString(indexMime)
+                    val contactNumber = cursorSip.getString(indexSip)
+                    if (!contactMime.contentEquals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) || net.jami.model.Uri.fromString(
+                            contactNumber
+                        ).isHexId || "ring".equals(
+                            cursorSip.getString(indexLabel),
+                            ignoreCase = true
+                        )
+                    ) {
+                        contact.addNumber(
+                            contactNumber,
+                            cursorSip.getInt(indexType),
+                            cursorSip.getString(indexLabel),
+                            Phone.NumberType.SIP
+                        )
+                    }
+                    Log.d(TAG, "SIP phone:$contactNumber $contactMime ")
+                }
+                cursorSip.close()
+            }
+        } catch (e: Exception) {
+            Log.d(TAG, "fillContactDetails: Error while retrieving detail contact info", e)
+        }
+    }
+
+    public override fun findContactBySipNumberFromSystem(number: String): Contact? {
+        var contact: Contact? = null
+        val contentResolver = mContext.contentResolver
+        try {
+            val result = contentResolver.query(
+                ContactsContract.Data.CONTENT_URI,
+                DATA_PROJECTION,
+                ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS + "=?" + " AND (" + ContactsContract.Data.MIMETYPE + "=? OR " + ContactsContract.Data.MIMETYPE + "=?)",
+                arrayOf(
+                    number,
+                    ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE,
+                    ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
+                ),
+                null
+            )
+            if (result == null) {
+                Log.d(TAG, "findContactBySipNumberFromSystem: $number can't find contact.")
+                return Contact.buildSIP(net.jami.model.Uri.fromString(number))
+            }
+            val indexId = result.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID)
+            val indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)
+            val indexName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)
+            val indexPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID)
+            val indexStared = result.getColumnIndex(ContactsContract.Contacts.STARRED)
+            if (result.moveToFirst()) {
+                val contactId = result.getLong(indexId)
+                contact = Contact(net.jami.model.Uri.fromString(number))
+                contact.setSystemContactInfo(
+                    contactId,
+                    result.getString(indexKey),
+                    result.getString(indexName),
+                    result.getLong(indexPhoto)
+                )
+                if (result.getInt(indexStared) != 0) {
+                    contact.setStared()
+                }
+                fillContactDetails(contact)
+            }
+            result.close()
+            if (contact == null || contact.phones == null || contact.phones.isEmpty()) {
+                return null
+            }
+        } catch (e: Exception) {
+            Log.d(
+                TAG,
+                "findContactBySipNumberFromSystem: Error while searching for contact number=$number",
+                e
+            )
+        }
+        return contact!!
+    }
+
+    public override fun findContactByNumberFromSystem(number: String): Contact? {
+        var contact: Contact? = null
+        val contentResolver = mContext.contentResolver
+        try {
+            val uri = Uri.withAppendedPath(
+                ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                Uri.encode(number)
+            )
+            val result = contentResolver.query(uri, PHONELOOKUP_PROJECTION, null, null, null)
+            if (result == null) {
+                Log.d(TAG, "findContactByNumberFromSystem: $number can't find contact.")
+                return findContactBySipNumberFromSystem(number)
+            }
+            if (result.moveToFirst()) {
+                val indexId = result.getColumnIndex(ContactsContract.Contacts._ID)
+                val indexKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY)
+                val indexName = result.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
+                val indexPhoto = result.getColumnIndex(ContactsContract.Contacts.PHOTO_ID)
+                contact = Contact(net.jami.model.Uri.fromString(number))
+                contact.setSystemContactInfo(
+                    result.getLong(indexId),
+                    result.getString(indexKey),
+                    result.getString(indexName),
+                    result.getLong(indexPhoto)
+                )
+                fillContactDetails(contact)
+                Log.d(
+                    TAG,
+                    "findContactByNumberFromSystem: " + number + " found " + contact.displayName
+                )
+            }
+            result.close()
+        } catch (e: Exception) {
+            Log.d(TAG, "findContactByNumber: Error while searching for contact number=$number", e)
+        }
+        if (contact == null) {
+            Log.d(TAG, "findContactByNumberFromSystem: $number can't find contact.")
+            contact = findContactBySipNumberFromSystem(number)
+        }
+        if (contact != null) contact.isFromSystem = true
+        return contact
+    }
+
+    override fun loadContactData(contact: Contact, accountId: String): Completable {
+        if (!contact.detailsLoaded) {
+            val profile: Single<Tuple<String?, Any?>> =
+                if (contact.isFromSystem) loadSystemContactData(contact)
+                else loadVCardContactData(contact, accountId)
+            return profile
+                .doOnSuccess { p: Tuple<String?, Any?> -> contact.setProfile(p.first, p.second) }
+                .doOnError { e: Throwable? -> contact.setProfile(null, null) }
+                .ignoreElement()
+                .onErrorComplete()
+        }
+        return Completable.complete()
+    }
+
+    override fun saveVCardContactData(contact: Contact, accountId: String, vcard: VCard) {
+        if (vcard != null) {
+            val profileData = VCardServiceImpl.readData(vcard)
+            contact.setProfile(profileData.first, profileData.second)
+            val filename = contact.primaryNumber + ".vcf"
+            VCardUtils.savePeerProfileToDisk(
+                vcard, accountId, filename, mContext.filesDir
+            )
+            AvatarFactory.clearCache()
+        }
+    }
+
+    override fun saveVCardContact(accountId: String, uri: String, displayName: String, picture: String): Single<VCard> {
+        return Single.fromCallable {
+            val vcard = VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT))
+            val filename = "$uri.vcf"
+            VCardUtils.savePeerProfileToDisk(vcard, accountId, filename, mContext.filesDir)
+            vcard
+        }
+    }
+
+    private fun loadVCardContactData(contact: Contact, accountId: String): Single<Tuple<String?, Any?>> {
+        val id = contact.primaryNumber
+        return Single.fromCallable<VCard> { VCardUtils.loadPeerProfileFromDisk(mContext.filesDir, "$id.vcf", accountId) }
+            .map { vcard: VCard -> VCardServiceImpl.readData(vcard) }
+            .subscribeOn(Schedulers.computation())
+    }
+
+    private fun loadSystemContactData(contact: Contact): Single<Tuple<String?, Any?>> {
+        val contactName = contact.displayName
+        val photoURI = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id)
+        return AndroidFileUtils
+            .loadBitmap(
+                mContext,
+                Uri.withAppendedPath(photoURI, ContactsContract.Contacts.Photo.DISPLAY_PHOTO)
+            )
+            .map { bitmap: Bitmap? -> Tuple<String?, Any?>(contactName, bitmap) }
+            .onErrorReturn { e: Throwable? -> Tuple(contactName, null) }
+            .subscribeOn(Schedulers.io())
+    }
+
+    fun loadConversationAvatar(context: Context, conversation: Conversation): Single<Drawable> {
+        return getLoadedContact(conversation.accountId, conversation.contacts, false)
+            .flatMap { AvatarFactory.getAvatar(context, conversation, false) }
+    }
+
+    companion object {
+        private val TAG = ContactServiceImpl::class.java.simpleName
+        private val CONTACTS_SUMMARY_PROJECTION = arrayOf(
+            ContactsContract.Contacts._ID,
+            ContactsContract.Contacts.LOOKUP_KEY,
+            ContactsContract.Contacts.DISPLAY_NAME,
+            ContactsContract.Contacts.PHOTO_ID,
+            ContactsContract.Contacts.STARRED
+        )
+        private val CONTACTS_DATA_PROJECTION = arrayOf(
+            ContactsContract.Data.CONTACT_ID,
+            ContactsContract.Data.MIMETYPE,
+            ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS,
+            ContactsContract.CommonDataKinds.SipAddress.TYPE,
+            ContactsContract.CommonDataKinds.SipAddress.LABEL,
+            ContactsContract.CommonDataKinds.Im.PROTOCOL,
+            ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL
+        )
+        private val CONTACT_PROJECTION = arrayOf(
+            ContactsContract.Contacts._ID,
+            ContactsContract.Contacts.LOOKUP_KEY,
+            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY,
+            ContactsContract.Contacts.PHOTO_ID,
+            ContactsContract.Contacts.STARRED
+        )
+        private val CONTACTS_PHONES_PROJECTION = arrayOf(
+            ContactsContract.CommonDataKinds.Phone.NUMBER,
+            ContactsContract.CommonDataKinds.Phone.TYPE,
+            ContactsContract.CommonDataKinds.Phone.LABEL
+        )
+        private val CONTACTS_SIP_PROJECTION = arrayOf(
+            ContactsContract.Data.MIMETYPE,
+            ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS,
+            ContactsContract.CommonDataKinds.SipAddress.TYPE,
+            ContactsContract.CommonDataKinds.SipAddress.LABEL
+        )
+        private val DATA_PROJECTION = arrayOf(
+            ContactsContract.Data._ID,
+            ContactsContract.RawContacts.CONTACT_ID,
+            ContactsContract.Data.LOOKUP_KEY,
+            ContactsContract.Data.DISPLAY_NAME_PRIMARY,
+            ContactsContract.Data.PHOTO_ID,
+            ContactsContract.Data.PHOTO_THUMBNAIL_URI,
+            ContactsContract.Data.STARRED
+        )
+        private val PHONELOOKUP_PROJECTION = arrayOf(
+            ContactsContract.PhoneLookup._ID,
+            ContactsContract.PhoneLookup.LOOKUP_KEY,
+            ContactsContract.PhoneLookup.PHOTO_ID,
+            ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
+        )
+        private const val ID_SELECTION = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/DataTransferService.java b/ring-android/app/src/main/java/cx/ring/services/DataTransferService.java
index f8974fe85..148b540d8 100644
--- a/ring-android/app/src/main/java/cx/ring/services/DataTransferService.java
+++ b/ring-android/app/src/main/java/cx/ring/services/DataTransferService.java
@@ -32,13 +32,14 @@ import androidx.core.app.NotificationManagerCompat;
 
 import javax.inject.Inject;
 
-import cx.ring.application.JamiApplication;
+import dagger.hilt.android.AndroidEntryPoint;
 
 import net.jami.services.NotificationService;
 
 import java.util.HashSet;
 import java.util.Set;
 
+@AndroidEntryPoint
 public class DataTransferService extends Service {
     private final String TAG = DataTransferService.class.getSimpleName();
     public static final String ACTION_START = "startTransfer";
@@ -47,6 +48,7 @@ public class DataTransferService extends Service {
 
     @Inject
     NotificationService mNotificationService;
+
     private NotificationManagerCompat notificationManager;
     private boolean started = false;
 
@@ -101,7 +103,6 @@ public class DataTransferService extends Service {
     @Override
     public void onCreate() {
         Log.d(TAG, "OnCreate(), DataTransferService has been initialized");
-        ((JamiApplication) getApplication()).getInjectionComponent().inject(this);
         notificationManager = NotificationManagerCompat.from(this);
         super.onCreate();
     }
diff --git a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
deleted file mode 100644
index bdad5c9f8..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Build;
-import android.provider.ContactsContract;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import androidx.core.content.ContextCompat;
-
-import net.jami.daemon.IntVect;
-import net.jami.daemon.StringVect;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.utils.FileUtils;
-import net.jami.utils.StringUtils;
-
-import java.io.File;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import cx.ring.application.JamiApplication;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.NetworkUtils;
-
-public class DeviceRuntimeServiceImpl extends DeviceRuntimeService {
-
-    private static final String TAG = DeviceRuntimeServiceImpl.class.getSimpleName();
-    private static final String[] PROFILE_PROJECTION = new String[]{ContactsContract.Profile._ID,
-            ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
-            ContactsContract.Profile.PHOTO_ID};
-    @Inject
-    protected Context mContext;
-    @Inject
-    @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-
-    private void copyAssets() {
-        File pluginsPath = new File(mContext.getFilesDir(), "plugins");
-        Log.w(TAG, "Plugins: " + pluginsPath.getAbsolutePath());
-        // Overwrite existing plugins folder in order to use newer plugins
-        AndroidFileUtils.copyAssetFolder(mContext.getAssets(), "plugins", pluginsPath);
-    }
-
-    @Override
-    public void loadNativeLibrary() {
-        mExecutor.execute(() -> {
-            try {
-                System.loadLibrary("ring");
-            } catch (Exception e) {
-                Log.e(TAG, "Could not load Jami library", e);
-                android.os.Process.killProcess(android.os.Process.myPid());
-                System.exit(0);
-            }
-        });
-    }
-
-    @Override
-    public File provideFilesDir() {
-        return mContext.getFilesDir();
-    }
-
-    @Override
-    public File getFilePath(String filename) {
-        return AndroidFileUtils.getFilePath(mContext, filename);
-    }
-
-    @Override
-    public File getConversationPath(String conversationId, String name) {
-        return AndroidFileUtils.getConversationPath(mContext, conversationId, name);
-    }
-
-    @Override
-    public File getConversationPath(String accountId, String conversationId, String name) {
-        return AndroidFileUtils.getConversationPath(mContext, accountId, conversationId, name);
-    }
-
-    @Override
-    public File getTemporaryPath(String conversationId, String name) {
-        return AndroidFileUtils.getTempPath(mContext, conversationId, name);
-    }
-
-    @Override
-    public File getConversationDir(String conversationId) {
-        return AndroidFileUtils.getConversationDir(mContext, conversationId);
-    }
-
-    @Override
-    public File getCacheDir() {
-        return mContext.getCacheDir();
-    }
-
-    @Override
-    public String getPushToken() {
-        return JamiApplication.getInstance().getPushToken();
-    }
-
-    private boolean isNetworkConnectedForType(int connectivityManagerType) {
-        NetworkInfo info = NetworkUtils.getNetworkInfo(mContext);
-        return (info != null && info.isConnected() && info.getType() == connectivityManagerType);
-    }
-
-    @Override
-    public boolean isConnectedBluetooth() {
-        return isNetworkConnectedForType(ConnectivityManager.TYPE_BLUETOOTH);
-    }
-
-    @Override
-    public boolean isConnectedWifi() {
-        return isNetworkConnectedForType(ConnectivityManager.TYPE_WIFI);
-    }
-
-    @Override
-    public boolean isConnectedMobile() {
-        return isNetworkConnectedForType(ConnectivityManager.TYPE_MOBILE);
-    }
-
-    @Override
-    public boolean isConnectedEthernet() {
-        return isNetworkConnectedForType(ConnectivityManager.TYPE_ETHERNET);
-    }
-
-    @Override
-    public boolean hasVideoPermission() {
-        return checkPermission(Manifest.permission.CAMERA);
-    }
-
-    @Override
-    public boolean hasAudioPermission() {
-        return checkPermission(Manifest.permission.RECORD_AUDIO);
-    }
-
-    @Override
-    public boolean hasContactPermission() {
-        return checkPermission(Manifest.permission.READ_CONTACTS);
-    }
-
-    @Override
-    public boolean hasCallLogPermission() {
-        return checkPermission(Manifest.permission.WRITE_CALL_LOG);
-    }
-
-    @Override
-    public boolean hasWriteExternalStoragePermission() {
-        return checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-    }
-
-    @Override
-    public boolean hasGalleryPermission() {
-        return checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
-    }
-
-    @Override
-    public String getProfileName() {
-        Cursor mProfileCursor = mContext.getContentResolver().query(ContactsContract.Profile.CONTENT_URI, PROFILE_PROJECTION, null, null, null);
-        if (mProfileCursor != null) {
-            if (mProfileCursor.moveToFirst()) {
-                String profileName = mProfileCursor.getString(mProfileCursor.getColumnIndex(ContactsContract.Profile.DISPLAY_NAME_PRIMARY));
-                mProfileCursor.close();
-                return profileName;
-            }
-            mProfileCursor.close();
-        }
-        return null;
-    }
-
-    @Override
-    public boolean hardLinkOrCopy(File source, File dest) {
-        try {
-            Os.link(source.getAbsolutePath(), dest.getAbsolutePath());
-        } catch (ErrnoException e) {
-            Log.w(TAG, "Can't create hardlink, copying instead: " + e.getMessage());
-            return FileUtils.copyFile(source, dest);
-        }
-        return true;
-    }
-
-    private boolean checkPermission(String permission) {
-        return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    @Override
-    public void getHardwareAudioFormat(IntVect ret) {
-        int sampleRate = 44100;
-        int bufferSize = 64;
-        try {
-            AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-            sampleRate = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
-            bufferSize = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
-        } catch (Exception e) {
-            Log.w(getClass().getName(), "Failed to read native OpenSL config", e);
-        }
-        ret.add(sampleRate);
-        ret.add(bufferSize);
-        Log.d(TAG, "getHardwareAudioFormat: " + sampleRate + " " + bufferSize);
-    }
-
-    @Override
-    public void getAppDataPath(String name, StringVect ret) {
-        if (name == null || ret == null) {
-            return;
-        }
-
-        switch (name) {
-            case "files":
-                ret.add(mContext.getFilesDir().getAbsolutePath());
-                break;
-            case "cache":
-                ret.add(mContext.getCacheDir().getAbsolutePath());
-                break;
-            default:
-                ret.add(mContext.getDir(name, Context.MODE_PRIVATE).getAbsolutePath());
-                break;
-        }
-    }
-
-    @Override
-    public void getDeviceName(StringVect ret) {
-        String manufacturer = Build.MANUFACTURER;
-        String model = Build.MODEL;
-        if (model.startsWith(manufacturer)) {
-            ret.add(StringUtils.capitalize(model));
-        } else {
-            ret.add(StringUtils.capitalize(manufacturer) + " " + model);
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.kt
new file mode 100644
index 000000000..06bfafac0
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.kt
@@ -0,0 +1,231 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.media.AudioManager
+import android.net.ConnectivityManager
+import android.os.Build
+import android.os.Process
+import android.provider.ContactsContract
+import android.system.ErrnoException
+import android.system.Os
+import android.util.Log
+import androidx.core.content.ContextCompat
+import cx.ring.application.JamiApplication.Companion.instance
+import cx.ring.utils.AndroidFileUtils.copyAssetFolder
+import cx.ring.utils.AndroidFileUtils.getConversationDir
+import cx.ring.utils.AndroidFileUtils.getConversationPath
+import cx.ring.utils.AndroidFileUtils.getFilePath
+import cx.ring.utils.AndroidFileUtils.getTempPath
+import cx.ring.utils.NetworkUtils
+import net.jami.daemon.IntVect
+import net.jami.daemon.StringVect
+import net.jami.services.DeviceRuntimeService
+import net.jami.services.LogService
+import net.jami.utils.FileUtils
+import net.jami.utils.StringUtils
+import java.io.File
+import java.util.concurrent.ScheduledExecutorService
+import kotlin.system.exitProcess
+
+class DeviceRuntimeServiceImpl(
+    private val mContext: Context,
+    private val mExecutor: ScheduledExecutorService,
+    private val logService: LogService
+) : DeviceRuntimeService() {
+    private fun copyAssets() {
+        val pluginsPath = File(mContext.filesDir, "plugins")
+        Log.w(TAG, "Plugins: " + pluginsPath.absolutePath)
+        // Overwrite existing plugins folder in order to use newer plugins
+        copyAssetFolder(mContext.assets, "plugins", pluginsPath)
+    }
+
+    override fun loadNativeLibrary() {
+        logService.w(TAG, "loadNativeLibrary")
+        mExecutor.execute {
+            Log.w(TAG, "System.loadLibrary")
+            try {
+                System.loadLibrary("ring")
+            } catch (e: Exception) {
+                Log.e(TAG, "Could not load Jami library", e)
+                Process.killProcess(Process.myPid())
+                exitProcess(0)
+            }
+        }
+    }
+
+    override fun provideFilesDir(): File {
+        return mContext.filesDir
+    }
+
+    override fun getFilePath(filename: String): File {
+        return getFilePath(mContext, filename)
+    }
+
+    override fun getConversationPath(conversationId: String, name: String): File {
+        return getConversationPath(mContext, conversationId, name)
+    }
+
+    override fun getConversationPath(
+        accountId: String,
+        conversationId: String,
+        name: String
+    ): File {
+        return getConversationPath(mContext, accountId, conversationId, name)
+    }
+
+    override fun getTemporaryPath(conversationId: String, name: String): File {
+        return getTempPath(mContext, conversationId, name)
+    }
+
+    override fun getConversationDir(conversationId: String): File {
+        return getConversationDir(mContext, conversationId)
+    }
+
+    override fun getCacheDir(): File {
+        return mContext.cacheDir
+    }
+
+    override fun getPushToken(): String? {
+        return instance?.pushToken
+    }
+
+    private fun isNetworkConnectedForType(connectivityManagerType: Int): Boolean {
+        val info = NetworkUtils.getNetworkInfo(mContext)
+        return info != null && info.isConnected && info.type == connectivityManagerType
+    }
+
+    override fun isConnectedBluetooth(): Boolean {
+        return isNetworkConnectedForType(ConnectivityManager.TYPE_BLUETOOTH)
+    }
+
+    override fun isConnectedWifi(): Boolean {
+        return isNetworkConnectedForType(ConnectivityManager.TYPE_WIFI)
+    }
+
+    override fun isConnectedMobile(): Boolean {
+        return isNetworkConnectedForType(ConnectivityManager.TYPE_MOBILE)
+    }
+
+    override fun isConnectedEthernet(): Boolean {
+        return isNetworkConnectedForType(ConnectivityManager.TYPE_ETHERNET)
+    }
+
+    override fun hasVideoPermission(): Boolean {
+        return checkPermission(Manifest.permission.CAMERA)
+    }
+
+    override fun hasAudioPermission(): Boolean {
+        return checkPermission(Manifest.permission.RECORD_AUDIO)
+    }
+
+    override fun hasContactPermission(): Boolean {
+        return checkPermission(Manifest.permission.READ_CONTACTS)
+    }
+
+    override fun hasCallLogPermission(): Boolean {
+        return checkPermission(Manifest.permission.WRITE_CALL_LOG)
+    }
+
+    override fun hasWriteExternalStoragePermission(): Boolean {
+        return checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+    }
+
+    override fun hasGalleryPermission(): Boolean {
+        return checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
+    }
+
+    override fun getProfileName(): String? {
+        mContext.contentResolver.query(
+            ContactsContract.Profile.CONTENT_URI,
+            PROFILE_PROJECTION,
+            null,
+            null,
+            null
+        )?.use { cursor ->
+            if (cursor.moveToFirst()) {
+                return cursor.getString(cursor.getColumnIndex(ContactsContract.Profile.DISPLAY_NAME_PRIMARY))
+            }
+        }
+        return null
+    }
+
+    override fun hardLinkOrCopy(source: File, dest: File): Boolean {
+        try {
+            Os.link(source.absolutePath, dest.absolutePath)
+        } catch (e: ErrnoException) {
+            Log.w(TAG, "Can't create hardlink, copying instead: " + e.message)
+            return FileUtils.copyFile(source, dest)
+        }
+        return true
+    }
+
+    private fun checkPermission(permission: String): Boolean {
+        return ContextCompat.checkSelfPermission(
+            mContext,
+            permission
+        ) == PackageManager.PERMISSION_GRANTED
+    }
+
+    override fun getHardwareAudioFormat(ret: IntVect) {
+        var sampleRate = 44100
+        var bufferSize = 64
+        try {
+            val am = mContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+            sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE).toInt()
+            bufferSize = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER).toInt()
+        } catch (e: Exception) {
+            Log.w(javaClass.name, "Failed to read native OpenSL config", e)
+        }
+        ret.add(sampleRate)
+        ret.add(bufferSize)
+        Log.d(TAG, "getHardwareAudioFormat: $sampleRate $bufferSize")
+    }
+
+    override fun getAppDataPath(name: String, ret: StringVect) {
+        when (name) {
+            "files" -> ret.add(mContext.filesDir.absolutePath)
+            "cache" -> ret.add(mContext.cacheDir.absolutePath)
+            else -> ret.add(mContext.getDir(name, Context.MODE_PRIVATE).absolutePath)
+        }
+    }
+
+    override fun getDeviceName(ret: StringVect) {
+        val manufacturer = Build.MANUFACTURER
+        val model = Build.MODEL
+        if (model.startsWith(manufacturer)) {
+            ret.add(StringUtils.capitalize(model))
+        } else {
+            ret.add(StringUtils.capitalize(manufacturer) + " " + model)
+        }
+    }
+
+    companion object {
+        private val TAG = DeviceRuntimeServiceImpl::class.simpleName!!
+        private val PROFILE_PROJECTION = arrayOf(
+            ContactsContract.Profile._ID,
+            ContactsContract.Profile.DISPLAY_NAME_PRIMARY,
+            ContactsContract.Profile.PHOTO_ID
+        )
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
deleted file mode 100644
index e5ec57866..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
+++ /dev/null
@@ -1,788 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.bluetooth.BluetoothHeadset;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Point;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.MediaRecorder;
-import android.media.projection.MediaProjection;
-import android.os.Build;
-import android.view.SurfaceHolder;
-import android.view.TextureView;
-import android.view.WindowManager;
-
-import androidx.annotation.Nullable;
-import androidx.media.AudioAttributesCompat;
-import androidx.media.AudioFocusRequestCompat;
-import androidx.media.AudioManagerCompat;
-
-import java.io.File;
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import net.jami.daemon.IntVect;
-import net.jami.daemon.JamiService;
-import net.jami.daemon.UintVect;
-import net.jami.model.Conference;
-import net.jami.model.Call;
-import net.jami.model.Call.CallStatus;
-import cx.ring.utils.BluetoothWrapper;
-
-import net.jami.services.HardwareService;
-import net.jami.utils.Log;
-import cx.ring.utils.Ringer;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Observable;
-
-import net.jami.utils.Tuple;
-
-public class HardwareServiceImpl extends HardwareService implements AudioManager.OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
-
-    private static final Point VIDEO_SIZE_LOW = new Point(320, 240);
-    private static final Point VIDEO_SIZE_DEFAULT = new Point(720, 480);
-    private static final Point VIDEO_SIZE_HD = new Point(1280, 720);
-    private static final Point VIDEO_SIZE_FULL_HD = new Point(1920, 1080);
-    private static final Point VIDEO_SIZE_ULTRA_HD = new Point(3840, 2160);
-
-    private static final String TAG = HardwareServiceImpl.class.getSimpleName();
-    private static WeakReference<TextureView> mCameraPreviewSurface = new WeakReference<>(null);
-    private static WeakReference<Conference> mCameraPreviewCall = new WeakReference<>(null);
-
-    private static final Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<>());
-    private final Map<String, Shm> videoInputs = new HashMap<>();
-    private final Context mContext;
-    private final CameraService cameraService;
-    private final Ringer mRinger;
-    private final AudioManager mAudioManager;
-    private BluetoothWrapper mBluetoothWrapper;
-    private AudioFocusRequestCompat currentFocus = null;
-
-    private String mCapturingId = null;
-    private boolean mIsCapturing = false;
-    private boolean mIsScreenSharing = false;
-
-    private boolean mShouldCapture = false;
-    private boolean mShouldSpeakerphone = false;
-    private final boolean mHasSpeakerPhone;
-    private boolean mIsChoosePlugin = false;
-    private String mMediaHandlerId = null;
-    private String mPluginCallId = null;
-
-    public HardwareServiceImpl(Context context) {
-        mContext = context;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mHasSpeakerPhone = hasSpeakerphone();
-        mRinger = new Ringer(mContext);
-        cameraService = new CameraService(mContext);
-    }
-
-    public Completable initVideo() {
-        Log.i(TAG, "initVideo()");
-        return cameraService.init();
-    }
-
-    public Observable<Tuple<Integer, Integer>> getMaxResolutions() {
-        return cameraService.getMaxResolutions();
-    }
-
-    public boolean isVideoAvailable() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) || cameraService.hasCamera();
-    }
-
-    public boolean hasMicrophone() {
-        PackageManager pm = mContext.getPackageManager();
-        boolean hasMicrophone = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
-
-        if (!hasMicrophone) {
-            MediaRecorder recorder = new MediaRecorder();
-            File testFile = new File(mContext.getCacheDir(), "MediaUtil#micAvailTestFile");
-            try {
-                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-                recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
-                recorder.setOutputFile(testFile.getAbsolutePath());
-                recorder.prepare();
-                recorder.start();
-                hasMicrophone = true;
-            } catch (IllegalStateException e) {
-                // Microphone is already in use
-                hasMicrophone = true;
-            } catch (Exception exception) {
-                hasMicrophone = false;
-            } finally {
-                recorder.release();
-                testFile.delete();
-            }
-        }
-
-        return hasMicrophone;
-    }
-
-    @Override
-    public boolean isSpeakerPhoneOn() {
-        return mAudioManager.isSpeakerphoneOn();
-    }
-
-    private final AudioFocusRequestCompat RINGTONE_REQUEST = new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
-            .setAudioAttributes(new AudioAttributesCompat.Builder()
-                    .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
-                    .setUsage(AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE)
-                    .setLegacyStreamType(AudioManager.STREAM_RING)
-                    .build())
-            .setOnAudioFocusChangeListener(this)
-            .build();
-
-    private final AudioFocusRequestCompat CALL_REQUEST = new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
-            .setAudioAttributes(new AudioAttributesCompat.Builder()
-                    .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
-                    .setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
-                    .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
-                    .build())
-            .setOnAudioFocusChangeListener(this)
-            .build();
-
-    private void getFocus(AudioFocusRequestCompat request) {
-        if (currentFocus == request)
-            return;
-        if (currentFocus != null) {
-            AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus);
-            currentFocus = null;
-        }
-        if (request != null && AudioManagerCompat.requestAudioFocus(mAudioManager, request) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
-            currentFocus = request;
-        }
-    }
-
-    @Override
-    synchronized public void updateAudioState(final Call.CallStatus state, final boolean incomingCall, final boolean isOngoingVideo) {
-        Log.d(TAG, "updateAudioState: Call state updated to " + state + " Call is incoming: " + incomingCall + " Call is video: " + isOngoingVideo);
-        boolean callEnded = state.equals(CallStatus.HUNGUP) || state.equals(CallStatus.FAILURE) || state.equals(CallStatus.OVER);
-        try {
-            if (mBluetoothWrapper == null && !callEnded) {
-                mBluetoothWrapper = new BluetoothWrapper(mContext, this);
-            }
-            switch (state) {
-                case RINGING:
-                    if (incomingCall)
-                        startRinging();
-                    getFocus(RINGTONE_REQUEST);
-                    if (incomingCall) {
-                        // ringtone for incoming calls
-                        mAudioManager.setMode(AudioManager.MODE_RINGTONE);
-                        setAudioRouting(true);
-                        mShouldSpeakerphone = isOngoingVideo;
-                    } else
-                        setAudioRouting(isOngoingVideo);
-                    break;
-                case CURRENT:
-                    stopRinging();
-                    getFocus(CALL_REQUEST);
-                    mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
-                    setAudioRouting(isOngoingVideo);
-                    break;
-                case HOLD:
-                case UNHOLD:
-                case INACTIVE:
-                    break;
-                default:
-                    closeAudioState();
-                    break;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error updating audio state", e);
-        }
-    }
-
-    /*
-    This is required in the case where a call is incoming. If you have an incoming call, and no bluetooth device is connected, the ringer should always be played through the speaker.
-    However, this results in the call starting in a state where the speaker is always on and the UI is in an incorrect state.
-    If it is a bluetooth device, it takes priority and does not play on speaker regardless. Otherwise, it returns mShouldSpeakerphone which was updated in updateaudiostate.
-     */
-    @Override
-    public boolean shouldPlaySpeaker() {
-        if(mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected() )
-            return false;
-        else
-            return mShouldSpeakerphone;
-    }
-
-    @Override
-    synchronized public void closeAudioState() {
-        stopRinging();
-        abandonAudioFocus();
-    }
-
-    @Override
-    public void startRinging() {
-        mRinger.ring();
-    }
-
-    @Override
-    public void stopRinging() {
-        mRinger.stopRing();
-    }
-
-    @Override
-    public void onAudioFocusChange(int arg0) {
-        Log.i(TAG, "onAudioFocusChange " + arg0);
-    }
-
-    @Override
-    synchronized public void abandonAudioFocus() {
-        if (currentFocus != null) {
-            AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus);
-            currentFocus = null;
-        }
-        if (mAudioManager.isSpeakerphoneOn()) {
-            mAudioManager.setSpeakerphoneOn(false);
-        }
-        mAudioManager.setMode(AudioManager.MODE_NORMAL);
-
-        if (mBluetoothWrapper != null) {
-            mBluetoothWrapper.unregister();
-            mBluetoothWrapper.setBluetoothOn(false);
-            mBluetoothWrapper = null;
-        }
-    }
-
-    private void setAudioRouting(boolean requestSpeakerOn) {
-        mShouldSpeakerphone = requestSpeakerOn;
-        // prioritize bluetooth by checking for bluetooth device first
-        if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected()) {
-            routeToBTHeadset();
-        } else if (!mAudioManager.isWiredHeadsetOn() && mHasSpeakerPhone && mShouldSpeakerphone) {
-            routeToSpeaker();
-        } else {
-            resetAudio();
-        }
-    }
-
-    private boolean hasSpeakerphone() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-            // Check FEATURE_AUDIO_OUTPUT to guard against false positives.
-            PackageManager packageManager = mContext.getPackageManager();
-            if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-                return false;
-            }
-
-            AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-            for (AudioDeviceInfo device : devices) {
-                if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                    return true;
-                }
-            }
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Routes audio to a bluetooth headset.
-     */
-    private void routeToBTHeadset() {
-        Log.d(TAG, "routeToBTHeadset: Try to enable bluetooth");
-        int oldMode = mAudioManager.getMode();
-        mAudioManager.setMode(AudioManager.MODE_NORMAL);
-        mAudioManager.setSpeakerphoneOn(false);
-        mBluetoothWrapper.setBluetoothOn(true);
-        mAudioManager.setMode(oldMode);
-        audioStateSubject.onNext(new AudioState(AudioOutput.BLUETOOTH, mBluetoothWrapper.getDeviceName()));
-    }
-
-    /**
-     * Routes audio to the device's speaker and takes into account whether the transition is coming from bluetooth.
-     */
-    private void routeToSpeaker() {
-        // if we are returning from bluetooth mode, switch to mode normal, otherwise, we switch to mode in communication
-        if (mAudioManager.isBluetoothScoOn()) {
-            int oldMode = mAudioManager.getMode();
-            mAudioManager.setMode(AudioManager.MODE_NORMAL);
-            mBluetoothWrapper.setBluetoothOn(false);
-            mAudioManager.setMode(oldMode);
-        }
-        mAudioManager.setSpeakerphoneOn(true);
-        audioStateSubject.onNext(STATE_SPEAKERS);
-    }
-
-    /**
-     * Returns to earpiece audio
-     */
-    private void resetAudio() {
-        if (mBluetoothWrapper != null)
-            mBluetoothWrapper.setBluetoothOn(false);
-        mAudioManager.setSpeakerphoneOn(false);
-        audioStateSubject.onNext(STATE_INTERNAL);
-    }
-
-    @Override
-    synchronized public void toggleSpeakerphone(boolean checked) {
-        JamiService.setAudioPlugin(JamiService.getCurrentAudioOutputPlugin());
-        mShouldSpeakerphone = checked;
-        Log.w(TAG, "toggleSpeakerphone setSpeakerphoneOn " + checked);
-        if (mHasSpeakerPhone && checked) {
-            routeToSpeaker();
-        } else if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected()) {
-            routeToBTHeadset();
-        } else {
-            resetAudio();
-        }
-    }
-
-    @Override
-    synchronized public void onBluetoothStateChanged(int status) {
-        Log.d(TAG, "bluetoothStateChanged to: " + status);
-        BluetoothEvent event = new BluetoothEvent();
-        if (status == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-            Log.d(TAG, "BluetoothHeadset Connected");
-            event.connected = true;
-        } else if (status == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-            Log.d(TAG, "BluetoothHeadset Disconnected");
-            event.connected = false;
-            if (mShouldSpeakerphone)
-                routeToSpeaker();
-        }
-        bluetoothEvents.onNext(event);
-    }
-
-    public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
-        Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height);
-        Shm shm = new Shm();
-        shm.id = id;
-        shm.w = width;
-        shm.h = height;
-        videoInputs.put(id, shm);
-        WeakReference<SurfaceHolder> weakSurfaceHolder = videoSurfaces.get(id);
-        if (weakSurfaceHolder != null) {
-            SurfaceHolder holder = weakSurfaceHolder.get();
-            if (holder != null) {
-                shm.window = startVideo(id, holder.getSurface(), width, height);
-
-                if (shm.window == 0) {
-                    Log.i(TAG, "DJamiService.decodingStarted() no window !");
-
-                    VideoEvent event = new VideoEvent();
-                    event.start = true;
-                    event.callId = shm.id;
-                    videoEvents.onNext(event);
-                    return;
-                }
-
-                VideoEvent event = new VideoEvent();
-                event.callId = shm.id;
-                event.started = true;
-                event.w = shm.w;
-                event.h = shm.h;
-                videoEvents.onNext(event);
-            }
-        }
-    }
-
-    @Override
-    public void decodingStopped(String id, String shmPath, boolean isMixer) {
-        Log.i(TAG, "decodingStopped() " + id);
-        Shm shm = videoInputs.remove(id);
-        if (shm == null) {
-            return;
-        }
-        if (shm.window != 0) {
-            try {
-                stopVideo(shm.id, shm.window);
-            } catch (Exception e) {
-                Log.e(TAG, "decodingStopped error" + e);
-            }
-            shm.window = 0;
-        }
-    }
-
-    @Override
-    public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates) {
-        // Use a larger resolution for Android 6.0+, 64 bits devices
-        final boolean useLargerSize = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (Build.SUPPORTED_64_BIT_ABIS.length > 0 || mPreferenceService.isHardwareAccelerationEnabled());
-        //int MIN_WIDTH = useLargerSize ? (useHD ? VIDEO_WIDTH_HD : VIDEO_WIDTH) : VIDEO_WIDTH_MIN;
-        Point minVideoSize;
-        if (useLargerSize)
-            minVideoSize = parseResolution(mPreferenceService.getResolution());
-        else
-            minVideoSize = VIDEO_SIZE_LOW;
-        cameraService.getCameraInfo(camId, formats, sizes, rates, minVideoSize);
-    }
-
-    private Point parseResolution(int resolution) {
-        switch(resolution) {
-            case 480:
-                return VIDEO_SIZE_DEFAULT;
-            case 720:
-                return VIDEO_SIZE_HD;
-            case 1080:
-                return VIDEO_SIZE_FULL_HD;
-            case 2160:
-                return VIDEO_SIZE_ULTRA_HD;
-            default:
-                return VIDEO_SIZE_HD;
-        }
-    }
-
-    @Override
-    public void setParameters(String camId, int format, int width, int height, int rate) {
-        Log.d(TAG, "setParameters: " + camId + ", " + format + ", " + width + ", " + height + ", " + rate);
-        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        cameraService.setParameters(camId, format, width, height, rate, windowManager.getDefaultDisplay().getRotation());
-    }
-
-    public boolean startScreenShare(Object projection) {
-        MediaProjection mediaProjection = (MediaProjection) projection;
-        if (mIsCapturing) {
-            endCapture();
-        }
-        if (!mIsScreenSharing && mediaProjection != null) {
-            mIsScreenSharing = true;
-            mediaProjection.registerCallback(new MediaProjection.Callback(){
-                @Override
-                public void onStop() {
-                    stopScreenShare();
-                }
-            }, cameraService.getVideoHandler());
-            if (!cameraService.startScreenSharing(mediaProjection, mContext.getResources().getDisplayMetrics())) {
-                mIsScreenSharing = false;
-                mediaProjection.stop();
-                return false;
-            }
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public void stopScreenShare() {
-        if (mIsScreenSharing) {
-            cameraService.stopScreenSharing();
-            mIsScreenSharing = false;
-            if (mShouldCapture)
-                startCapture(mCapturingId);
-        }
-    }
-
-    public void startMediaHandler(String mediaHandlerId) {
-        mIsChoosePlugin = true;
-        mMediaHandlerId = mediaHandlerId;
-    }
-
-    private void toggleMediaHandler(String callId) {
-        if (mMediaHandlerId != null)
-            JamiService.toggleCallMediaHandler(mMediaHandlerId, callId, true);
-    }
-
-    public void stopMediaHandler() {
-        mIsChoosePlugin = false;
-        mMediaHandlerId = null;
-    }
-
-    @Override
-    public void startCapture(@Nullable String camId) {
-        if (mIsScreenSharing) {
-            cameraService.stopScreenSharing();
-            mIsScreenSharing = false;
-        }
-        mShouldCapture = true;
-        if (mIsCapturing && mCapturingId != null && mCapturingId.equals(camId)) {
-            return;
-        }
-        if (camId == null) {
-            camId = mCapturingId != null ? mCapturingId : cameraService.switchInput(true);
-        }
-        CameraService.VideoParams videoParams = cameraService.getParams(camId);
-        if (videoParams == null) {
-            Log.w(TAG, "startCapture: no video parameters ");
-            return;
-        }
-        final TextureView surface = mCameraPreviewSurface.get();
-        if (surface == null) {
-            Log.w(TAG, "Can't start capture: no surface registered.");
-            cameraService.setPreviewParams(videoParams);
-            VideoEvent event = new VideoEvent();
-            event.start = true;
-            videoEvents.onNext(event);
-            return;
-        }
-        final Conference conf = mCameraPreviewCall.get();
-        boolean useHardwareCodec = mPreferenceService.isHardwareAccelerationEnabled() && (conf == null || !conf.isConference()) && !mIsChoosePlugin;
-        if (conf != null && useHardwareCodec) {
-            Call call = conf.getCall();
-            if (call != null) {
-                call.setDetails(JamiService.getCallDetails(call.getDaemonIdString()).toNative());
-                videoParams.codec = call.getVideoCodec();
-            } else {
-                videoParams.codec = null;
-            }
-        }
-        Log.w(TAG, "startCapture: call " + camId + " " + videoParams.codec + " useHardwareCodec:" + useHardwareCodec + " bitrate:" + mPreferenceService.getBitrate());
-
-        mIsCapturing = true;
-        mCapturingId = videoParams.id;
-        Log.d(TAG, "startCapture: startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation);
-
-        mUiScheduler.scheduleDirect(() -> cameraService.openCamera(videoParams, surface,
-                new CameraService.CameraListener() {
-                    @Override
-                    public void onOpened() {
-                        String currentCall = conf != null ? conf.getId() : null;
-                        if (currentCall == null)
-                            return;
-                        if (mPluginCallId != null && !mPluginCallId.equals(currentCall)) {
-                            JamiService.toggleCallMediaHandler(mMediaHandlerId, currentCall, false);
-                            mIsChoosePlugin = false;
-                            mMediaHandlerId = null;
-                            mPluginCallId = null;
-                        }
-                        else if (mIsChoosePlugin && mMediaHandlerId != null) {
-                            mPluginCallId = currentCall;
-                            toggleMediaHandler(currentCall);
-                        }
-                    }
-
-                    @Override
-                    public void onError() {
-                        stopCapture();
-                    }
-                },
-                useHardwareCodec,
-                mPreferenceService.getResolution(),
-                mPreferenceService.getBitrate()));
-        cameraService.setPreviewParams(videoParams);
-        VideoEvent event = new VideoEvent();
-        event.started = true;
-        event.w = videoParams.width;
-        event.h = videoParams.height;
-        event.rot = videoParams.rotation;
-        videoEvents.onNext(event);
-    }
-
-    @Override
-    public void stopCapture() {
-        Log.d(TAG, "stopCapture: " + cameraService.isOpen());
-        mShouldCapture = false;
-        endCapture();
-        if (mIsScreenSharing) {
-            cameraService.stopScreenSharing();
-            mIsScreenSharing = false;
-        }
-    }
-
-    public void requestKeyFrame() {
-        cameraService.requestKeyFrame();
-    }
-
-    public void setBitrate(String device, int bitrate) {
-        cameraService.setBitrate(bitrate);
-    }
-
-    public void endCapture() {
-        if (cameraService.isOpen()) {
-            //final CameraService.VideoParams params = previewParams;
-            cameraService.closeCamera();
-            VideoEvent event = new VideoEvent();
-            event.started = false;
-            //event.w = params.width;
-            //event.h = params.height;
-            videoEvents.onNext(event);
-        }
-        mIsCapturing = false;
-    }
-
-    @Override
-    public void addVideoSurface(String id, Object holder) {
-        if (!(holder instanceof SurfaceHolder)) {
-            return;
-        }
-
-        Log.w(TAG, "addVideoSurface " + id);
-
-        Shm shm = videoInputs.get(id);
-        WeakReference<SurfaceHolder> surfaceHolder = new WeakReference<>((SurfaceHolder) holder);
-        videoSurfaces.put(id, surfaceHolder);
-        if (shm != null && shm.window == 0) {
-            shm.window = startVideo(shm.id, surfaceHolder.get().getSurface(), shm.w, shm.h);
-        }
-
-        if (shm == null || shm.window == 0) {
-            Log.i(TAG, "DJamiService.addVideoSurface() no window !");
-
-            VideoEvent event = new VideoEvent();
-            event.start = true;
-            videoEvents.onNext(event);
-            return;
-        }
-
-        VideoEvent event = new VideoEvent();
-        event.callId = shm.id;
-        event.started = true;
-        event.w = shm.w;
-        event.h = shm.h;
-        videoEvents.onNext(event);
-    }
-
-    @Override
-    public void updateVideoSurfaceId(String currentId, String newId)
-    {
-        Log.w(TAG, "updateVideoSurfaceId " + currentId + " " + newId);
-
-        WeakReference<SurfaceHolder> surfaceHolder = videoSurfaces.get(currentId);
-        if (surfaceHolder == null) {
-            return;
-        }
-        SurfaceHolder surface = surfaceHolder.get();
-
-        Shm shm = videoInputs.get(currentId);
-        if (shm != null && shm.window != 0) {
-            try {
-                stopVideo(shm.id, shm.window);
-            } catch (Exception e) {
-                Log.e(TAG, "removeVideoSurface error" + e);
-            }
-            shm.window = 0;
-        }
-        videoSurfaces.remove(currentId);
-        if (surface != null) {
-            addVideoSurface(newId, surface);
-        }
-    }
-
-    @Override
-    public void addPreviewVideoSurface(Object oholder, Conference conference) {
-        if (!(oholder instanceof TextureView)) {
-            return;
-        }
-        TextureView holder = (TextureView) oholder;
-        Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode() + " mCapturingId " + mCapturingId);
-        if (mCameraPreviewSurface.get() == oholder)
-            return;
-        mCameraPreviewSurface = new WeakReference<>(holder);
-        mCameraPreviewCall = new WeakReference<>(conference);
-        if (mShouldCapture && !mIsCapturing) {
-            startCapture(mCapturingId);
-        }
-    }
-
-    @Override
-    public void updatePreviewVideoSurface(Conference conference)  {
-        Conference old = mCameraPreviewCall.get();
-        mCameraPreviewCall = new WeakReference<>(conference);
-        if (old != conference && mIsCapturing) {
-            String id = mCapturingId;
-            stopCapture();
-            startCapture(id);
-        }
-    }
-
-    @Override
-    public void removeVideoSurface(String id) {
-        Log.i(TAG, "removeVideoSurface " + id);
-        videoSurfaces.remove(id);
-        Shm shm = videoInputs.get(id);
-        if (shm == null) {
-            return;
-        }
-        if (shm.window != 0) {
-            try {
-                stopVideo(shm.id, shm.window);
-            } catch (Exception e) {
-                Log.e(TAG, "removeVideoSurface error" + e);
-            }
-
-            shm.window = 0;
-        }
-
-        VideoEvent event = new VideoEvent();
-        event.callId = shm.id;
-        event.started = false;
-        videoEvents.onNext(event);
-    }
-
-    @Override
-    public void removePreviewVideoSurface() {
-        Log.w(TAG, "removePreviewVideoSurface");
-        mCameraPreviewSurface.clear();
-    }
-
-    @Override
-    public void switchInput(String id, boolean setDefaultCamera) {
-        Log.w(TAG, "switchInput " + id);
-        mCapturingId = cameraService.switchInput(setDefaultCamera);
-        switchInput(id, "camera://" + mCapturingId);
-    }
-
-    @Override
-    public void setPreviewSettings() {
-        setPreviewSettings(cameraService.getPreviewSettings());
-    }
-
-    @Override
-    public int getCameraCount() {
-        return cameraService.getCameraCount();
-    }
-
-    @Override
-    public boolean hasCamera() {
-        return cameraService.hasCamera();
-    }
-
-    @Override
-    public boolean isPreviewFromFrontCamera() {
-        return cameraService.isPreviewFromFrontCamera();
-    }
-
-    @Override
-    public void setDeviceOrientation(int rotation) {
-        cameraService.setOrientation(rotation);
-        if (mCapturingId != null) {
-            CameraService.VideoParams videoParams = cameraService.getParams(mCapturingId);
-            VideoEvent event = new VideoEvent();
-            event.started = true;
-            event.w = videoParams.width;
-            event.h = videoParams.height;
-            event.rot = videoParams.rotation;
-            videoEvents.onNext(event);
-        }
-    }
-
-    @Override
-    protected List<String> getVideoDevices() {
-        return cameraService.getCameraIds();
-    }
-
-    private static class Shm {
-        String id;
-        int w, h;
-        long window = 0;
-    }
-
-    @Override
-    public void unregisterCameraDetectionCallback() {
-        cameraService.unregisterCameraDetectionCallback();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt
new file mode 100644
index 000000000..fe7abca95
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt
@@ -0,0 +1,695 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.bluetooth.BluetoothHeadset
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.Point
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.media.AudioManager.OnAudioFocusChangeListener
+import android.media.MediaRecorder
+import android.media.projection.MediaProjection
+import android.os.Build
+import android.view.SurfaceHolder
+import android.view.TextureView
+import android.view.WindowManager
+import androidx.media.AudioAttributesCompat
+import androidx.media.AudioFocusRequestCompat
+import androidx.media.AudioManagerCompat
+import cx.ring.services.CameraService.CameraListener
+import cx.ring.utils.BluetoothWrapper
+import cx.ring.utils.BluetoothWrapper.BluetoothChangeListener
+import cx.ring.utils.Ringer
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Scheduler
+import net.jami.daemon.IntVect
+import net.jami.daemon.JamiService
+import net.jami.daemon.UintVect
+import net.jami.model.Call.CallStatus
+import net.jami.model.Conference
+import net.jami.services.HardwareService
+import net.jami.services.PreferencesService
+import net.jami.utils.Log
+import net.jami.utils.Tuple
+import java.io.File
+import java.lang.ref.WeakReference
+import java.util.*
+import java.util.concurrent.ScheduledExecutorService
+
+class HardwareServiceImpl(
+    private val mContext: Context,
+    executor: ScheduledExecutorService,
+    preferenceService: PreferencesService,
+    uiScheduler: Scheduler
+) : HardwareService(executor, preferenceService, uiScheduler), OnAudioFocusChangeListener, BluetoothChangeListener {
+    private val videoInputs: MutableMap<String?, Shm> = HashMap()
+    private val cameraService = CameraService(mContext)
+    private val mRinger = Ringer(mContext)
+    private val mAudioManager: AudioManager = mContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+    private var mBluetoothWrapper: BluetoothWrapper? = null
+    private var currentFocus: AudioFocusRequestCompat? = null
+    private var mCapturingId: String? = null
+    private var mIsCapturing = false
+    private var mIsScreenSharing = false
+    private var mShouldCapture = false
+    private var mShouldSpeakerphone = false
+    private val mHasSpeakerPhone: Boolean = hasSpeakerphone()
+    private var mIsChoosePlugin = false
+    private var mMediaHandlerId: String? = null
+    private var mPluginCallId: String? = null
+    override fun initVideo(): Completable {
+        Log.i(TAG, "initVideo()")
+        return cameraService.init()
+    }
+
+    override val maxResolutions: Observable<Tuple<Int, Int>>
+        get() = cameraService.maxResolutions
+    override val isVideoAvailable: Boolean
+        get() = mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) || cameraService.hasCamera()
+
+    override fun hasMicrophone(): Boolean {
+        val pm = mContext.packageManager
+        var hasMicrophone = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+        if (!hasMicrophone) {
+            val recorder = MediaRecorder()
+            val testFile = File(mContext.cacheDir, "MediaUtil#micAvailTestFile")
+            hasMicrophone = try {
+                recorder.setAudioSource(MediaRecorder.AudioSource.MIC)
+                recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT)
+                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
+                recorder.setOutputFile(testFile.absolutePath)
+                recorder.prepare()
+                recorder.start()
+                true
+            } catch (e: IllegalStateException) {
+                // Microphone is already in use
+                true
+            } catch (exception: Exception) {
+                false
+            } finally {
+                recorder.release()
+                testFile.delete()
+            }
+        }
+        return hasMicrophone
+    }
+
+    override val isSpeakerphoneOn: Boolean
+        get() = mAudioManager.isSpeakerphoneOn
+
+    private val RINGTONE_REQUEST = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
+        .setAudioAttributes(
+            AudioAttributesCompat.Builder()
+                .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
+                .setUsage(AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE)
+                .setLegacyStreamType(AudioManager.STREAM_RING)
+                .build()
+        )
+        .setOnAudioFocusChangeListener(this)
+        .build()
+    private val CALL_REQUEST = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
+        .setAudioAttributes(
+            AudioAttributesCompat.Builder()
+                .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
+                .setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
+                .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
+                .build()
+        )
+        .setOnAudioFocusChangeListener(this)
+        .build()
+
+    private fun getFocus(request: AudioFocusRequestCompat?) {
+        if (currentFocus === request) return
+        if (currentFocus != null) {
+            AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus!!)
+            currentFocus = null
+        }
+        if (request != null && AudioManagerCompat.requestAudioFocus(
+                mAudioManager,
+                request
+            ) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
+        ) {
+            currentFocus = request
+        }
+    }
+
+    @Synchronized
+    override fun updateAudioState(state: CallStatus?, incomingCall: Boolean, isOngoingVideo: Boolean) {
+        Log.d(TAG, "updateAudioState: Call state updated to $state Call is incoming: $incomingCall Call is video: $isOngoingVideo")
+        val callEnded = state == CallStatus.HUNGUP || state == CallStatus.FAILURE || state == CallStatus.OVER
+        try {
+            if (mBluetoothWrapper == null && !callEnded) {
+                mBluetoothWrapper = BluetoothWrapper(mContext, this)
+            }
+            when (state) {
+                CallStatus.RINGING -> {
+                    if (incomingCall) startRinging()
+                    getFocus(RINGTONE_REQUEST)
+                    if (incomingCall) {
+                        // ringtone for incoming calls
+                        mAudioManager.mode = AudioManager.MODE_RINGTONE
+                        setAudioRouting(true)
+                        mShouldSpeakerphone = isOngoingVideo
+                    } else setAudioRouting(isOngoingVideo)
+                }
+                CallStatus.CURRENT -> {
+                    stopRinging()
+                    getFocus(CALL_REQUEST)
+                    mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
+                    setAudioRouting(isOngoingVideo)
+                }
+                CallStatus.HOLD, CallStatus.UNHOLD, CallStatus.INACTIVE -> {
+                }
+                else -> closeAudioState()
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Error updating audio state", e)
+        }
+    }
+
+    /*
+    This is required in the case where a call is incoming. If you have an incoming call, and no bluetooth device is connected, the ringer should always be played through the speaker.
+    However, this results in the call starting in a state where the speaker is always on and the UI is in an incorrect state.
+    If it is a bluetooth device, it takes priority and does not play on speaker regardless. Otherwise, it returns mShouldSpeakerphone which was updated in updateaudiostate.
+     */
+    override fun shouldPlaySpeaker(): Boolean {
+        return if (mBluetoothWrapper != null && mBluetoothWrapper!!.canBluetooth() && mBluetoothWrapper!!.isBTHeadsetConnected) false else mShouldSpeakerphone
+    }
+
+    @Synchronized
+    override fun closeAudioState() {
+        stopRinging()
+        abandonAudioFocus()
+    }
+
+    override fun startRinging() {
+        mRinger.ring()
+    }
+
+    override fun stopRinging() {
+        mRinger.stopRing()
+    }
+
+    override fun onAudioFocusChange(arg0: Int) {
+        Log.i(TAG, "onAudioFocusChange $arg0")
+    }
+
+    @Synchronized
+    override fun abandonAudioFocus() {
+        if (currentFocus != null) {
+            AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus!!)
+            currentFocus = null
+        }
+        if (mAudioManager.isSpeakerphoneOn) {
+            mAudioManager.isSpeakerphoneOn = false
+        }
+        mAudioManager.mode = AudioManager.MODE_NORMAL
+        mBluetoothWrapper?.let { bluetoothWrapper ->
+            bluetoothWrapper.unregister()
+            bluetoothWrapper.setBluetoothOn(false)
+            mBluetoothWrapper = null
+        }
+    }
+
+    private fun setAudioRouting(requestSpeakerOn: Boolean) {
+        mShouldSpeakerphone = requestSpeakerOn
+        // prioritize bluetooth by checking for bluetooth device first
+        if (mBluetoothWrapper != null && mBluetoothWrapper!!.canBluetooth() && mBluetoothWrapper!!.isBTHeadsetConnected) {
+            routeToBTHeadset()
+        } else if (!mAudioManager.isWiredHeadsetOn && mHasSpeakerPhone && mShouldSpeakerphone) {
+            routeToSpeaker()
+        } else {
+            resetAudio()
+        }
+    }
+
+    private fun hasSpeakerphone(): Boolean {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            // Check FEATURE_AUDIO_OUTPUT to guard against false positives.
+            val packageManager = mContext.packageManager
+            if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+                return false
+            }
+            val devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
+            for (device in devices) {
+                if (device.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    return true
+                }
+            }
+            return false
+        }
+        return true
+    }
+
+    /**
+     * Routes audio to a bluetooth headset.
+     */
+    private fun routeToBTHeadset() {
+        Log.d(TAG, "routeToBTHeadset: Try to enable bluetooth")
+        val oldMode = mAudioManager.mode
+        mAudioManager.mode = AudioManager.MODE_NORMAL
+        mAudioManager.isSpeakerphoneOn = false
+        mBluetoothWrapper!!.setBluetoothOn(true)
+        mAudioManager.mode = oldMode
+        audioStateSubject.onNext(AudioState(AudioOutput.BLUETOOTH, mBluetoothWrapper!!.deviceName))
+    }
+
+    /**
+     * Routes audio to the device's speaker and takes into account whether the transition is coming from bluetooth.
+     */
+    private fun routeToSpeaker() {
+        // if we are returning from bluetooth mode, switch to mode normal, otherwise, we switch to mode in communication
+        if (mAudioManager.isBluetoothScoOn) {
+            val oldMode = mAudioManager.mode
+            mAudioManager.mode = AudioManager.MODE_NORMAL
+            mBluetoothWrapper!!.setBluetoothOn(false)
+            mAudioManager.mode = oldMode
+        }
+        mAudioManager.isSpeakerphoneOn = true
+        audioStateSubject.onNext(STATE_SPEAKERS)
+    }
+
+    /**
+     * Returns to earpiece audio
+     */
+    private fun resetAudio() {
+        if (mBluetoothWrapper != null) mBluetoothWrapper!!.setBluetoothOn(false)
+        mAudioManager.isSpeakerphoneOn = false
+        audioStateSubject.onNext(STATE_INTERNAL)
+    }
+
+    @Synchronized
+    override fun toggleSpeakerphone(checked: Boolean) {
+        JamiService.setAudioPlugin(JamiService.getCurrentAudioOutputPlugin())
+        mShouldSpeakerphone = checked
+        Log.w(TAG, "toggleSpeakerphone setSpeakerphoneOn $checked")
+        if (mHasSpeakerPhone && checked) {
+            routeToSpeaker()
+        } else if (mBluetoothWrapper != null && mBluetoothWrapper!!.canBluetooth() && mBluetoothWrapper!!.isBTHeadsetConnected) {
+            routeToBTHeadset()
+        } else {
+            resetAudio()
+        }
+    }
+
+    @Synchronized
+    override fun onBluetoothStateChanged(status: Int) {
+        Log.d(TAG, "bluetoothStateChanged to: $status")
+        val event = BluetoothEvent()
+        if (status == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+            Log.d(TAG, "BluetoothHeadset Connected")
+            event.connected = true
+        } else if (status == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+            Log.d(TAG, "BluetoothHeadset Disconnected")
+            event.connected = false
+            if (mShouldSpeakerphone) routeToSpeaker()
+        }
+        bluetoothEvents.onNext(event)
+    }
+
+    override fun decodingStarted(id: String, shmPath: String, width: Int, height: Int, isMixer: Boolean) {
+        Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height)
+        val shm = Shm()
+        shm.id = id
+        shm.w = width
+        shm.h = height
+        videoInputs[id] = shm
+        val weakSurfaceHolder = videoSurfaces[id]
+        if (weakSurfaceHolder != null) {
+            val holder = weakSurfaceHolder.get()
+            if (holder != null) {
+                shm.window = startVideo(id, holder.surface, width, height)
+                if (shm.window == 0L) {
+                    Log.i(TAG, "DJamiService.decodingStarted() no window !")
+                    val event = VideoEvent()
+                    event.start = true
+                    event.callId = shm.id
+                    videoEvents.onNext(event)
+                    return
+                }
+                val event = VideoEvent()
+                event.callId = shm.id
+                event.started = true
+                event.w = shm.w
+                event.h = shm.h
+                videoEvents.onNext(event)
+            }
+        }
+    }
+
+    override fun decodingStopped(id: String, shmPath: String, isMixer: Boolean) {
+        Log.i(TAG, "decodingStopped() $id")
+        val shm = videoInputs.remove(id) ?: return
+        if (shm.window != 0L) {
+            try {
+                stopVideo(shm.id, shm.window)
+            } catch (e: Exception) {
+                Log.e(TAG, "decodingStopped error$e")
+            }
+            shm.window = 0
+        }
+    }
+
+    override fun getCameraInfo(camId: String, formats: IntVect, sizes: UintVect, rates: UintVect) {
+        // Use a larger resolution for Android 6.0+, 64 bits devices
+        val useLargerSize =
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (Build.SUPPORTED_64_BIT_ABIS.isNotEmpty() || mPreferenceService.isHardwareAccelerationEnabled)
+        //int MIN_WIDTH = useLargerSize ? (useHD ? VIDEO_WIDTH_HD : VIDEO_WIDTH) : VIDEO_WIDTH_MIN;
+        val minVideoSize: Point = if (useLargerSize) parseResolution(mPreferenceService.resolution) else VIDEO_SIZE_LOW
+        cameraService.getCameraInfo(camId, formats, sizes, rates, minVideoSize)
+    }
+
+    private fun parseResolution(resolution: Int): Point {
+        return when (resolution) {
+            480 -> VIDEO_SIZE_DEFAULT
+            720 -> VIDEO_SIZE_HD
+            1080 -> VIDEO_SIZE_FULL_HD
+            2160 -> VIDEO_SIZE_ULTRA_HD
+            else -> VIDEO_SIZE_HD
+        }
+    }
+
+    override fun setParameters(camId: String, format: Int, width: Int, height: Int, rate: Int) {
+        Log.d(TAG, "setParameters: $camId, $format, $width, $height, $rate")
+        val windowManager = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+        cameraService.setParameters(camId, format, width, height, rate, windowManager.defaultDisplay.rotation)
+    }
+
+    override fun startScreenShare(mediaProjection: Any?): Boolean {
+        val projection = mediaProjection as MediaProjection?
+        if (mIsCapturing) {
+            endCapture()
+        }
+        return if (!mIsScreenSharing && projection != null) {
+            mIsScreenSharing = true
+            projection.registerCallback(object : MediaProjection.Callback() {
+                override fun onStop() {
+                    stopScreenShare()
+                }
+            }, cameraService.videoHandler)
+            if (!cameraService.startScreenSharing(projection, mContext.resources.displayMetrics)) {
+                mIsScreenSharing = false
+                projection.stop()
+                return false
+            }
+            true
+        } else {
+            false
+        }
+    }
+
+    override fun stopScreenShare() {
+        if (mIsScreenSharing) {
+            cameraService.stopScreenSharing()
+            mIsScreenSharing = false
+            if (mShouldCapture) startCapture(mCapturingId)
+        }
+    }
+
+    override fun startMediaHandler(mediaHandlerId: String?) {
+        mIsChoosePlugin = true
+        mMediaHandlerId = mediaHandlerId
+    }
+
+    private fun toggleMediaHandler(callId: String) {
+        if (mMediaHandlerId != null) JamiService.toggleCallMediaHandler(mMediaHandlerId, callId, true)
+    }
+
+    override fun stopMediaHandler() {
+        mIsChoosePlugin = false
+        mMediaHandlerId = null
+    }
+
+    override fun startCapture(camId: String?) {
+        if (mIsScreenSharing) {
+            cameraService.stopScreenSharing()
+            mIsScreenSharing = false
+        }
+        mShouldCapture = true
+        if (mIsCapturing && mCapturingId != null && mCapturingId == camId) {
+            return
+        }
+        val cam = camId ?: if (mCapturingId != null) mCapturingId else cameraService.switchInput(true)
+        val videoParams = cameraService.getParams(cam)
+        if (videoParams == null) {
+            Log.w(TAG, "startCapture: no video parameters ")
+            return
+        }
+        val surface = mCameraPreviewSurface.get()
+        if (surface == null) {
+            Log.w(TAG, "Can't start capture: no surface registered.")
+            cameraService.setPreviewParams(videoParams)
+            val event = VideoEvent()
+            event.start = true
+            videoEvents.onNext(event)
+            return
+        }
+        val conf = mCameraPreviewCall.get()
+        val useHardwareCodec =
+            mPreferenceService.isHardwareAccelerationEnabled && (conf == null || !conf.isConference) && !mIsChoosePlugin
+        if (conf != null && useHardwareCodec) {
+            val call = conf.call
+            if (call != null) {
+                call.setDetails(JamiService.getCallDetails(call.daemonIdString).toNative())
+                videoParams.codec = call.videoCodec
+            } else {
+                videoParams.codec = null
+            }
+        }
+        Log.w(TAG, "startCapture: call " + cam + " " + videoParams.codec + " useHardwareCodec:" + useHardwareCodec + " bitrate:" + mPreferenceService.bitrate)
+        mIsCapturing = true
+        mCapturingId = videoParams.id
+        Log.d(TAG, "startCapture: startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation)
+        mUiScheduler.scheduleDirect {
+            cameraService.openCamera(videoParams, surface,
+                object : CameraListener {
+                    override fun onOpened() {
+                        val currentCall = conf?.id ?: return
+                        if (mPluginCallId != null && mPluginCallId != currentCall) {
+                            JamiService.toggleCallMediaHandler(mMediaHandlerId, currentCall, false)
+                            mIsChoosePlugin = false
+                            mMediaHandlerId = null
+                            mPluginCallId = null
+                        } else if (mIsChoosePlugin && mMediaHandlerId != null) {
+                            mPluginCallId = currentCall
+                            toggleMediaHandler(currentCall)
+                        }
+                    }
+
+                    override fun onError() {
+                        stopCapture()
+                    }
+                },
+                useHardwareCodec,
+                mPreferenceService.resolution,
+                mPreferenceService.bitrate
+            )
+        }
+        cameraService.setPreviewParams(videoParams)
+        val event = VideoEvent()
+        event.started = true
+        event.w = videoParams.width
+        event.h = videoParams.height
+        event.rot = videoParams.rotation
+        videoEvents.onNext(event)
+    }
+
+    override fun stopCapture() {
+        Log.d(TAG, "stopCapture: " + cameraService.isOpen)
+        mShouldCapture = false
+        endCapture()
+        if (mIsScreenSharing) {
+            cameraService.stopScreenSharing()
+            mIsScreenSharing = false
+        }
+    }
+
+    override fun requestKeyFrame() {
+        cameraService.requestKeyFrame()
+    }
+
+    override fun setBitrate(device: String?, bitrate: Int) {
+        cameraService.setBitrate(bitrate)
+    }
+
+    override fun endCapture() {
+        if (cameraService.isOpen) {
+            //final CameraService.VideoParams params = previewParams;
+            cameraService.closeCamera()
+            val event = VideoEvent()
+            event.started = false
+            //event.w = params.width;
+            //event.h = params.height;
+            videoEvents.onNext(event)
+        }
+        mIsCapturing = false
+    }
+
+    override fun addVideoSurface(id: String?, holder: Any?) {
+        if (holder !is SurfaceHolder) {
+            return
+        }
+        Log.w(TAG, "addVideoSurface $id")
+        val shm = videoInputs[id]
+        val surfaceHolder = WeakReference(holder)
+        videoSurfaces[id] = surfaceHolder
+        if (shm != null && shm.window == 0L) {
+            shm.window = startVideo(shm.id, surfaceHolder.get()!!.surface, shm.w, shm.h)
+        }
+        if (shm == null || shm.window == 0L) {
+            Log.i(TAG, "DJamiService.addVideoSurface() no window !")
+            val event = VideoEvent()
+            event.start = true
+            videoEvents.onNext(event)
+            return
+        }
+        val event = VideoEvent()
+        event.callId = shm.id
+        event.started = true
+        event.w = shm.w
+        event.h = shm.h
+        videoEvents.onNext(event)
+    }
+
+    override fun updateVideoSurfaceId(currentId: String?, newId: String?) {
+        Log.w(TAG, "updateVideoSurfaceId $currentId $newId")
+        val surfaceHolder = videoSurfaces[currentId] ?: return
+        val surface = surfaceHolder.get()
+        val shm = videoInputs[currentId]
+        if (shm != null && shm.window != 0L) {
+            try {
+                stopVideo(shm.id, shm.window)
+            } catch (e: Exception) {
+                Log.e(TAG, "removeVideoSurface error$e")
+            }
+            shm.window = 0
+        }
+        videoSurfaces.remove(currentId)
+        surface?.let { addVideoSurface(newId, it) }
+    }
+
+    override fun addPreviewVideoSurface(holder: Any?, conference: Conference?) {
+        if (holder !is TextureView)
+            return
+        Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode() + " mCapturingId " + mCapturingId)
+        if (mCameraPreviewSurface.get() === holder) return
+        mCameraPreviewSurface = WeakReference(holder)
+        mCameraPreviewCall = WeakReference(conference)
+        if (mShouldCapture && !mIsCapturing) {
+            startCapture(mCapturingId)
+        }
+    }
+
+    override fun updatePreviewVideoSurface(conference: Conference?) {
+        val old = mCameraPreviewCall.get()
+        mCameraPreviewCall = WeakReference(conference)
+        if (old !== conference && mIsCapturing) {
+            val id = mCapturingId
+            stopCapture()
+            startCapture(id)
+        }
+    }
+
+    override fun removeVideoSurface(id: String?) {
+        Log.i(TAG, "removeVideoSurface $id")
+        videoSurfaces.remove(id)
+        val shm = videoInputs[id] ?: return
+        if (shm.window != 0L) {
+            try {
+                stopVideo(shm.id, shm.window)
+            } catch (e: Exception) {
+                Log.e(TAG, "removeVideoSurface error$e")
+            }
+            shm.window = 0
+        }
+        val event = VideoEvent()
+        event.callId = shm.id
+        event.started = false
+        videoEvents.onNext(event)
+    }
+
+    override fun removePreviewVideoSurface() {
+        Log.w(TAG, "removePreviewVideoSurface")
+        mCameraPreviewSurface.clear()
+    }
+
+    override fun switchInput(id: String?, setDefaultCamera: Boolean) {
+        Log.w(TAG, "switchInput $id")
+        mCapturingId = cameraService.switchInput(setDefaultCamera)
+        switchInput(id, "camera://$mCapturingId")
+    }
+
+    override fun setPreviewSettings() {
+        setPreviewSettings(cameraService.previewSettings)
+    }
+
+    override val cameraCount: Int
+        get() = cameraService.cameraCount
+
+    override fun hasCamera(): Boolean {
+        return cameraService.hasCamera()
+    }
+
+    override val isPreviewFromFrontCamera: Boolean
+        get() = cameraService.isPreviewFromFrontCamera
+
+    override fun setDeviceOrientation(rotation: Int) {
+        cameraService.setOrientation(rotation)
+        if (mCapturingId != null) {
+            val videoParams = cameraService.getParams(mCapturingId)
+            val event = VideoEvent()
+            event.started = true
+            event.w = videoParams.width
+            event.h = videoParams.height
+            event.rot = videoParams.rotation
+            videoEvents.onNext(event)
+        }
+    }
+
+    override val videoDevices: List<String>
+        get() = cameraService.cameraIds
+
+    private class Shm {
+        var id: String? = null
+        var w = 0
+        var h = 0
+        var window: Long = 0
+    }
+
+    override fun unregisterCameraDetectionCallback() {
+        cameraService.unregisterCameraDetectionCallback()
+    }
+
+    companion object {
+        private val VIDEO_SIZE_LOW = Point(320, 240)
+        private val VIDEO_SIZE_DEFAULT = Point(720, 480)
+        private val VIDEO_SIZE_HD = Point(1280, 720)
+        private val VIDEO_SIZE_FULL_HD = Point(1920, 1080)
+        private val VIDEO_SIZE_ULTRA_HD = Point(3840, 2160)
+        private val TAG = HardwareServiceImpl::class.simpleName!!
+        private var mCameraPreviewSurface = WeakReference<TextureView?>(null)
+        private var mCameraPreviewCall = WeakReference<Conference?>(null)
+        private val videoSurfaces = Collections.synchronizedMap(HashMap<String?, WeakReference<SurfaceHolder>>())
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.java
deleted file mode 100644
index 4b54c7d85..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import com.j256.ormlite.dao.Dao;
-import com.j256.ormlite.support.ConnectionSource;
-
-import java.io.File;
-import java.sql.SQLException;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.inject.Inject;
-
-import cx.ring.history.DatabaseHelper;
-
-import net.jami.model.ConversationHistory;
-import net.jami.model.Interaction;
-import net.jami.model.Uri;
-import net.jami.services.HistoryService;
-
-import static cx.ring.fragments.ConversationFragment.KEY_PREFERENCE_CONVERSATION_LAST_READ;
-
-/**
- * Implements the necessary Android related methods for the {@link HistoryService}
- */
-public class HistoryServiceImpl extends HistoryService {
-    private static final String TAG = HistoryServiceImpl.class.getSimpleName();
-    private final static String DATABASE_NAME = "history.db";
-    private final static String LEGACY_DATABASE_KEY = "legacy";
-
-    private final ConcurrentHashMap<String, DatabaseHelper> databaseHelpers = new ConcurrentHashMap<>();
-
-    @Inject
-    protected Context mContext;
-
-    public HistoryServiceImpl() {
-    }
-
-    @Override
-    protected ConnectionSource getConnectionSource(String dbName) {
-        return getHelper(dbName).getConnectionSource();
-    }
-
-    @Override
-    protected Dao<Interaction, Integer> getInteractionDataDao(String dbName) {
-        try {
-            return getHelper(dbName).getInteractionDataDao();
-        } catch (SQLException e) {
-            Log.e(TAG, "Unable to get a interactionDataDao");
-            return null;
-        }
-    }
-
-    @Override
-    protected Dao<ConversationHistory, Integer> getConversationDataDao(String dbName) {
-        try {
-            return getHelper(dbName).getConversationDataDao();
-        } catch (SQLException e) {
-            Log.e(TAG, "Unable to get a conversationDataDao");
-            return null;
-        }
-    }
-
-    /**
-     * Creates an instance of our database's helper.
-     * Stores it in a hash map for easy retrieval in the future.
-     *
-     * @param accountId represents the file where the database is stored
-     * @return the database helper
-     */
-    private DatabaseHelper initHelper(String accountId) {
-        File db = new File(new File(mContext.getFilesDir(), accountId), DATABASE_NAME);
-        DatabaseHelper helper = new DatabaseHelper(mContext, db.getAbsolutePath());
-        databaseHelpers.put(accountId, helper);
-        return helper;
-    }
-
-    /**
-     * Retrieve helper for our DB. Creates a new instance if it does not exist through the initHelper method.
-     *
-     * @param accountId represents the file where the database is stored
-     * @return the database helper
-     * @see #initHelper(String) initHelper
-     */
-    @SuppressWarnings("JavadocReference")
-    @Override
-    protected DatabaseHelper getHelper(String accountId) {
-        DatabaseHelper helper = databaseHelpers.get(accountId);
-        return helper == null ? initHelper(accountId) : helper;
-    }
-
-    /**
-     * Deletes the user's account file and all its children
-     *
-     * @param accountId the file name
-     * @see #deleteFolder(File) deleteFolder
-     */
-    @Override
-    protected void deleteAccountHistory(String accountId) {
-        File accountDir = new File(mContext.getFilesDir(), accountId);
-        if (accountDir.exists())
-            deleteFolder(accountDir);
-    }
-
-    @Override
-    public void setMessageRead(String accountId, Uri conversationUri, String lastId) {
-        SharedPreferences preferences = mContext.getSharedPreferences(accountId + "_" + conversationUri.getUri(), Context.MODE_PRIVATE);
-        preferences.edit().putString(KEY_PREFERENCE_CONVERSATION_LAST_READ, lastId).apply();
-    }
-
-    @Override
-    public String getLastMessageRead(String accountId, Uri conversationUri) {
-        SharedPreferences preferences = mContext.getSharedPreferences(accountId + "_" + conversationUri.getUri(), Context.MODE_PRIVATE);
-        return preferences.getString(KEY_PREFERENCE_CONVERSATION_LAST_READ, null);
-    }
-
-    /**
-     * Deletes a file and all its children recursively
-     *
-     * @param file the file to delete
-     */
-    private void deleteFolder(File file) {
-        if (file.isDirectory()) {
-            File[] children = file.listFiles();
-            if (children != null) {
-                for (File child : children)
-                    deleteFolder(child);
-            }
-        }
-        file.delete();
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.kt
new file mode 100644
index 000000000..bb6b1599e
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/HistoryServiceImpl.kt
@@ -0,0 +1,128 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.content.Context
+import com.j256.ormlite.dao.Dao
+import com.j256.ormlite.support.ConnectionSource
+import cx.ring.fragments.ConversationFragment
+import cx.ring.history.DatabaseHelper
+import net.jami.model.ConversationHistory
+import net.jami.model.Interaction
+import net.jami.model.Uri
+import net.jami.services.HistoryService
+import java.io.File
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * Implements the necessary Android related methods for the [HistoryService]
+ */
+class HistoryServiceImpl(private val mContext: Context) : HistoryService() {
+    private val databaseHelpers = ConcurrentHashMap<String, DatabaseHelper>()
+    override fun getConnectionSource(dbName: String): ConnectionSource {
+        return getHelper(dbName).connectionSource
+    }
+
+    override fun getInteractionDataDao(dbName: String): Dao<Interaction, Int> {
+        return getHelper(dbName).interactionDataDao
+    }
+
+    override fun getConversationDataDao(dbName: String): Dao<ConversationHistory, Int> {
+        return getHelper(dbName).conversationDataDao
+    }
+
+    /**
+     * Creates an instance of our database's helper.
+     * Stores it in a hash map for easy retrieval in the future.
+     *
+     * @param accountId represents the file where the database is stored
+     * @return the database helper
+     */
+    private fun initHelper(accountId: String): DatabaseHelper {
+        val db = File(File(mContext.filesDir, accountId), Companion.DATABASE_NAME)
+        val helper = DatabaseHelper(mContext, db.absolutePath)
+        databaseHelpers[accountId] = helper
+        return helper
+    }
+
+    /**
+     * Retrieve helper for our DB. Creates a new instance if it does not exist through the initHelper method.
+     *
+     * @param accountId represents the file where the database is stored
+     * @return the database helper
+     * @see .initHelper
+     */
+    override fun getHelper(accountId: String): DatabaseHelper {
+        val helper = databaseHelpers[accountId]
+        return helper ?: initHelper(accountId)
+    }
+
+    /**
+     * Deletes the user's account file and all its children
+     *
+     * @param accountId the file name
+     * @see .deleteFolder
+     */
+    override fun deleteAccountHistory(accountId: String) {
+        val accountDir = File(mContext.filesDir, accountId)
+        if (accountDir.exists()) deleteFolder(accountDir)
+    }
+
+    override fun setMessageRead(accountId: String, conversationUri: Uri, lastId: String) {
+        val preferences = mContext.getSharedPreferences(
+            accountId + "_" + conversationUri.uri,
+            Context.MODE_PRIVATE
+        )
+        preferences.edit()
+            .putString(ConversationFragment.KEY_PREFERENCE_CONVERSATION_LAST_READ, lastId).apply()
+    }
+
+    override fun getLastMessageRead(accountId: String, conversationUri: Uri): String? {
+        val preferences = mContext.getSharedPreferences(
+            accountId + "_" + conversationUri.uri,
+            Context.MODE_PRIVATE
+        )
+        return preferences.getString(
+            ConversationFragment.KEY_PREFERENCE_CONVERSATION_LAST_READ,
+            null
+        )
+    }
+
+    /**
+     * Deletes a file and all its children recursively
+     *
+     * @param file the file to delete
+     */
+    private fun deleteFolder(file: File) {
+        if (file.isDirectory) {
+            val children = file.listFiles()
+            if (children != null) {
+                for (child in children) deleteFolder(child)
+            }
+        }
+        file.delete()
+    }
+
+    companion object {
+        private const val DATABASE_NAME = "history.db"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java
index 042db2428..169e6f490 100644
--- a/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java
@@ -56,6 +56,4 @@ public class LogServiceImpl implements LogService {
     public void i(String tag, String message, Throwable e) {
         Log.i(tag, message, e);
     }
-
-
 }
diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
deleted file mode 100644
index 649ca69c5..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java
+++ /dev/null
@@ -1,990 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.annotation.SuppressLint;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.media.AudioAttributes;
-import android.media.RingtoneManager;
-import android.os.Build;
-import android.text.TextUtils;
-import android.text.format.Formatter;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationCompat.CarExtender.UnreadConversation;
-import androidx.core.app.NotificationManagerCompat;
-import androidx.core.app.Person;
-import androidx.core.app.RemoteInput;
-import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.core.util.Pair;
-
-import com.bumptech.glide.Glide;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.client.ConversationActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.contactrequests.ContactRequestsFragment;
-import cx.ring.views.AvatarFactory;
-import cx.ring.fragments.ConversationFragment;
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conference;
-import net.jami.model.Conversation;
-import net.jami.model.Interaction;
-import net.jami.model.Interaction.InteractionStatus;
-import net.jami.model.DataTransfer;
-import net.jami.model.Call;
-import net.jami.model.TextMessage;
-import net.jami.model.Uri;
-import cx.ring.service.CallNotificationService;
-import cx.ring.service.DRingService;
-import cx.ring.settings.SettingsFragment;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.utils.ConversationPath;
-import cx.ring.utils.DeviceUtils;
-import cx.ring.utils.ResourceMapper;
-
-import net.jami.services.AccountService;
-import net.jami.services.ContactService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HistoryService;
-import net.jami.services.NotificationService;
-import net.jami.services.PreferencesService;
-import net.jami.utils.Tuple;
-
-public class NotificationServiceImpl implements NotificationService {
-
-    public static final String EXTRA_BUBBLE = "bubble";
-
-    private static final String TAG = NotificationServiceImpl.class.getSimpleName();
-
-    private static final String NOTIF_MSG = "MESSAGE";
-    private static final String NOTIF_TRUST_REQUEST = "TRUST REQUEST";
-    private static final String NOTIF_FILE_TRANSFER = "FILE_TRANSFER";
-    private static final String NOTIF_MISSED_CALL = "MISSED_CALL";
-
-    private static final String NOTIF_CHANNEL_CALL_IN_PROGRESS = "current_call";
-    private static final String NOTIF_CHANNEL_MISSED_CALL = "missed_calls";
-    private static final String NOTIF_CHANNEL_INCOMING_CALL = "incoming_call";
-
-    private static final String NOTIF_CHANNEL_MESSAGE = "messages";
-    private static final String NOTIF_CHANNEL_REQUEST = "requests";
-    private static final String NOTIF_CHANNEL_FILE_TRANSFER = "file_transfer";
-    public static final String NOTIF_CHANNEL_SYNC = "sync";
-    private static final String NOTIF_CHANNEL_SERVICE = "service";
-
-    private static final String NOTIF_CALL_GROUP = "calls";
-
-    public static final int NOTIF_CALL_ID = 1001;
-
-    private final SparseArray<NotificationCompat.Builder> mNotificationBuilders = new SparseArray<>();
-    @Inject
-    protected Context mContext;
-    @Inject
-    protected AccountService mAccountService;
-    @Inject
-    protected ContactService mContactService;
-    @Inject
-    protected PreferencesService mPreferencesService;
-    @Inject
-    protected HistoryService mHistoryService;
-    @Inject
-    protected DeviceRuntimeService mDeviceRuntimeService;
-
-    private NotificationManagerCompat notificationManager;
-    private final Random random = new Random();
-    private int avatarSize;
-    private final LinkedHashMap<String, Conference> currentCalls = new LinkedHashMap<>();
-    private final ConcurrentHashMap<Integer, Notification> callNotifications = new ConcurrentHashMap<>();
-    private final ConcurrentHashMap<Integer, Notification> dataTransferNotifications = new ConcurrentHashMap<>();
-
-    @SuppressLint("CheckResult")
-    public void initHelper() {
-        if (notificationManager == null) {
-            notificationManager = NotificationManagerCompat.from(mContext);
-        }
-        avatarSize = (int) (mContext.getResources().getDisplayMetrics().density * AvatarFactory.SIZE_NOTIF);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            registerNotificationChannels(mContext);
-        }
-    }
-
-    @RequiresApi(api = Build.VERSION_CODES.O)
-    public static void registerNotificationChannels(Context context) {
-        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager == null)
-            return;
-
-        // Setting up groups
-        notificationManager.createNotificationChannelGroup(new NotificationChannelGroup(NOTIF_CALL_GROUP, context.getString(R.string.notif_group_calls)));
-
-        // Missed calls channel
-        NotificationChannel missedCallsChannel = new NotificationChannel(NOTIF_CHANNEL_MISSED_CALL, context.getString(R.string.notif_channel_missed_calls), NotificationManager.IMPORTANCE_DEFAULT);
-        missedCallsChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        missedCallsChannel.setSound(null, null);
-        missedCallsChannel.enableVibration(false);
-        missedCallsChannel.setGroup(NOTIF_CALL_GROUP);
-        notificationManager.createNotificationChannel(missedCallsChannel);
-
-        // Incoming call channel
-        NotificationChannel incomingCallChannel = new NotificationChannel(NOTIF_CHANNEL_INCOMING_CALL, context.getString(R.string.notif_channel_incoming_calls), NotificationManager.IMPORTANCE_HIGH);
-        incomingCallChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-        incomingCallChannel.setGroup(NOTIF_CALL_GROUP);
-        incomingCallChannel.setSound(null, null);
-        incomingCallChannel.enableVibration(false);
-        notificationManager.createNotificationChannel(incomingCallChannel);
-
-        // Call in progress channel
-        NotificationChannel callInProgressChannel = new NotificationChannel(NOTIF_CHANNEL_CALL_IN_PROGRESS, context.getString(R.string.notif_channel_call_in_progress), NotificationManager.IMPORTANCE_DEFAULT);
-        callInProgressChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-        callInProgressChannel.setSound(null, null);
-        callInProgressChannel.enableVibration(false);
-        callInProgressChannel.setGroup(NOTIF_CALL_GROUP);
-        notificationManager.createNotificationChannel(callInProgressChannel);
-
-        // Text messages channel
-        AudioAttributes soundAttributes = new AudioAttributes.Builder()
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
-                .build();
-
-        NotificationChannel messageChannel = new NotificationChannel(NOTIF_CHANNEL_MESSAGE, context.getString(R.string.notif_channel_messages), NotificationManager.IMPORTANCE_HIGH);
-        messageChannel.enableVibration(true);
-        messageChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        messageChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
-        notificationManager.createNotificationChannel(messageChannel);
-
-        // Contact requests
-        NotificationChannel requestsChannel = new NotificationChannel(NOTIF_CHANNEL_REQUEST, context.getString(R.string.notif_channel_requests), NotificationManager.IMPORTANCE_DEFAULT);
-        requestsChannel.enableVibration(true);
-        requestsChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        requestsChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
-        notificationManager.createNotificationChannel(requestsChannel);
-
-        // File transfer requests
-        NotificationChannel fileTransferChannel = new NotificationChannel(NOTIF_CHANNEL_FILE_TRANSFER, context.getString(R.string.notif_channel_file_transfer), NotificationManager.IMPORTANCE_DEFAULT);
-        fileTransferChannel.enableVibration(true);
-        fileTransferChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        fileTransferChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), soundAttributes);
-        notificationManager.createNotificationChannel(fileTransferChannel);
-
-        // File transfer requests
-        NotificationChannel syncChannel = new NotificationChannel(NOTIF_CHANNEL_SYNC, context.getString(R.string.notif_channel_sync), NotificationManager.IMPORTANCE_DEFAULT);
-        syncChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        syncChannel.enableLights(false);
-        syncChannel.enableVibration(false);
-        syncChannel.setShowBadge(false);
-        syncChannel.setSound(null, null);
-        notificationManager.createNotificationChannel(syncChannel);
-
-        // Background service channel
-        NotificationChannel backgroundChannel = new NotificationChannel(NOTIF_CHANNEL_SERVICE, context.getString(R.string.notif_channel_background_service), NotificationManager.IMPORTANCE_LOW);
-        backgroundChannel.setDescription(context.getString(R.string.notif_channel_background_service_descr));
-        backgroundChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        backgroundChannel.enableLights(false);
-        backgroundChannel.enableVibration(false);
-        backgroundChannel.setShowBadge(false);
-        notificationManager.createNotificationChannel(backgroundChannel);
-    }
-
-    /**
-     * Starts the call activity directly for Android TV
-     *
-     * @param callId the call ID
-     */
-    private void startCallActivity(String callId) {
-        mContext.startActivity(new Intent(Intent.ACTION_VIEW)
-                .putExtra(KEY_CALL_ID, callId)
-                .setClass(mContext.getApplicationContext(), TVCallActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
-    }
-
-    private Notification buildCallNotification(@NonNull Conference conference) {
-        String ongoingCallId = null;
-        for (Conference conf : currentCalls.values()) {
-            if (conf != conference && conf.getState() == Call.CallStatus.CURRENT)
-                ongoingCallId = conf.getParticipants().get(0).getDaemonIdString();
-        }
-
-        Call call = conference.getParticipants().get(0);
-        PendingIntent gotoIntent = PendingIntent.getService(mContext,
-                random.nextInt(),
-                new Intent(DRingService.ACTION_CALL_VIEW)
-                        .setClass(mContext, DRingService.class)
-                        .putExtra(KEY_CALL_ID, call.getDaemonIdString()), 0);
-
-        Contact contact = call.getContact();
-        NotificationCompat.Builder messageNotificationBuilder;
-        if (conference.isOnGoing()) {
-            messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS);
-            messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_current_call_title, contact.getDisplayName()))
-                    .setContentText(mContext.getText(R.string.notif_current_call))
-                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
-                    .setContentIntent(gotoIntent)
-                    .setSound(null)
-                    .setVibrate(null)
-                    .setColorized(true)
-                    .setUsesChronometer(true)
-                    .setWhen(conference.getTimestampStart())
-                    .setColor(mContext.getResources().getColor(R.color.color_primary_light))
-                    .addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_hangup),
-                            PendingIntent.getService(mContext, random.nextInt(),
-                                    new Intent(DRingService.ACTION_CALL_END)
-                                            .setClass(mContext, DRingService.class)
-                                            .putExtra(KEY_CALL_ID, call.getDaemonIdString()),
-                                    PendingIntent.FLAG_ONE_SHOT));
-        } else if (conference.isRinging()) {
-            if (conference.isIncoming()) {
-                messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_INCOMING_CALL);
-                messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_incoming_call_title, contact.getDisplayName()))
-                        .setPriority(NotificationCompat.PRIORITY_MAX)
-                        .setContentText(mContext.getText(R.string.notif_incoming_call))
-                        .setContentIntent(gotoIntent)
-                        .setSound(null)
-                        .setVibrate(null)
-                        .setFullScreenIntent(gotoIntent, true)
-                        .addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_decline),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(DRingService.ACTION_CALL_REFUSE)
-                                                .setClass(mContext, DRingService.class)
-                                                .putExtra(KEY_CALL_ID, call.getDaemonIdString()),
-                                        PendingIntent.FLAG_ONE_SHOT))
-                        .addAction(R.drawable.baseline_call_24, ongoingCallId == null ?
-                                        mContext.getText(R.string.action_call_accept) : mContext.getText(R.string.action_call_end_accept),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(ongoingCallId == null ? DRingService.ACTION_CALL_ACCEPT : DRingService.ACTION_CALL_END_ACCEPT)
-                                                .setClass(mContext, DRingService.class)
-                                                .putExtra(KEY_END_ID, ongoingCallId)
-                                                .putExtra(KEY_CALL_ID, call.getDaemonIdString()),
-                                        PendingIntent.FLAG_ONE_SHOT));
-                if (ongoingCallId != null) {
-                    messageNotificationBuilder.addAction(R.drawable.baseline_call_24, mContext.getText(R.string.action_call_hold_accept),
-                            PendingIntent.getService(mContext, random.nextInt(),
-                                    new Intent(DRingService.ACTION_CALL_HOLD_ACCEPT)
-                                            .setClass(mContext, DRingService.class)
-                                            .putExtra(KEY_HOLD_ID, ongoingCallId)
-                                            .putExtra(KEY_CALL_ID, call.getDaemonIdString()),
-                                    PendingIntent.FLAG_ONE_SHOT));
-                }
-            } else {
-                messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS);
-                messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_outgoing_call_title, contact.getDisplayName()))
-                        .setContentText(mContext.getText(R.string.notif_outgoing_call))
-                        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
-                        .setContentIntent(gotoIntent)
-                        .setSound(null)
-                        .setVibrate(null)
-                        .setColorized(true)
-                        .setColor(mContext.getResources().getColor(R.color.color_primary_light))
-                        .addAction(R.drawable.baseline_call_end_24, mContext.getText(R.string.action_call_hangup),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(DRingService.ACTION_CALL_END)
-                                                .setClass(mContext, DRingService.class)
-                                                .putExtra(KEY_CALL_ID, call.getDaemonIdString()),
-                                        PendingIntent.FLAG_ONE_SHOT));
-            }
-        } else {
-            return null;
-        }
-
-        messageNotificationBuilder.setOngoing(true)
-                .setCategory(NotificationCompat.CATEGORY_CALL)
-                .setSmallIcon(R.drawable.ic_ring_logo_white);
-
-        setContactPicture(contact, messageNotificationBuilder);
-
-        return messageNotificationBuilder.build();
-    }
-
-    @Override
-    public Object showCallNotification(int notifId) {
-        return callNotifications.remove(notifId);
-    }
-
-    @Override
-    public void showLocationNotification(Account first, Contact contact) {
-        android.net.Uri path = ConversationPath.toUri(first.getAccountID(), contact.getUri());
-
-        Intent intentConversation = new Intent(Intent.ACTION_VIEW, path, mContext, ConversationActivity.class)
-                .putExtra(ConversationFragment.EXTRA_SHOW_MAP, true);
-
-        NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MESSAGE)
-                .setCategory(NotificationCompat.CATEGORY_MESSAGE)
-                .setPriority(NotificationCompat.PRIORITY_HIGH)
-                .setDefaults(NotificationCompat.DEFAULT_ALL)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setSmallIcon(R.drawable.ic_ring_logo_white)
-                .setLargeIcon(getContactPicture(contact))
-                .setContentText(mContext.getString(R.string.location_share_contact, contact.getDisplayName()))
-                .setContentIntent(PendingIntent.getActivity(mContext, random.nextInt(), intentConversation, 0))
-                .setAutoCancel(false)
-                .setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
-        notificationManager.notify(Objects.hash( "Location", path), messageNotificationBuilder.build());
-    }
-
-    @Override
-    public void cancelLocationNotification(Account first, Contact contact) {
-        notificationManager.cancel(Objects.hash( "Location", ConversationPath.toUri(first.getAccountID(), contact.getUri())));
-    }
-
-    /**
-     * Updates a notification
-     *
-     * @param notification   a built notification object
-     * @param notificationId the notification's id
-     */
-    @Override
-    public void updateNotification(Object notification, int notificationId) {
-        if(notification != null)
-            notificationManager.notify(notificationId, (Notification) notification);
-    }
-
-    /**
-     * Starts a service (data transfer or call)
-     *
-     * @param id            the notification id
-     */
-    private void startForegroundService(int id, Class serviceClass) {
-        ContextCompat.startForegroundService(mContext, new Intent(mContext, serviceClass)
-                .putExtra(KEY_NOTIFICATION_ID, id));
-    }
-
-    /**
-     * Handles the creation and destruction of services associated with calls as well as displaying notifications.
-     *
-     * @param conference the conference object for the notification
-     * @param remove     true if it should be removed from current calls
-     */
-    @Override
-    public void handleCallNotification(Conference conference, boolean remove) {
-        if (DeviceUtils.isTv(mContext)) {
-            if (!remove)
-                startCallActivity(conference.getId());
-            return;
-        }
-
-        Notification notification = null;
-
-        // Build notification
-        String id = conference.getId();
-        currentCalls.remove(id);
-        if (!remove) {
-            currentCalls.put(id, conference);
-            notification = buildCallNotification(conference);
-        }
-        if (notification == null && !currentCalls.isEmpty()) {
-            // Build notification for other calls if any remains
-            for (Conference c : currentCalls.values())
-                conference = c;
-            notification = buildCallNotification(conference);
-        }
-
-        // Send notification to the  Service
-        if (notification != null) {
-            int nid = random.nextInt();
-            callNotifications.put(nid, notification);
-            ContextCompat.startForegroundService(mContext, new Intent(CallNotificationService.ACTION_START, null, mContext, CallNotificationService.class)
-                    .putExtra(KEY_NOTIFICATION_ID, nid));
-        } else {
-            try {
-                mContext.startService(new Intent(CallNotificationService.ACTION_STOP, null, mContext, CallNotificationService.class));
-            } catch (Exception e) {
-                Log.w(TAG, "Error stopping service", e);
-            }
-        }
-    }
-
-    @Override
-    public void onConnectionUpdate(Boolean b) {
-        /*Log.i(TAG, "onConnectionUpdate " + b);
-        if (b) {
-            Intent serviceIntent = new Intent(SyncService.ACTION_START).setClass(mContext, SyncService.class);
-            try {
-                ContextCompat.startForegroundService(mContext, serviceIntent);
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Error starting service", e);
-            }
-        } else {
-            try {
-                mContext.startService(new Intent(SyncService.ACTION_STOP).setClass(mContext, SyncService.class));
-            } catch (IllegalStateException ignored) {
-            }
-        }*/
-    }
-
-    /**
-     * Handles the creation and destruction of services associated with transfers as well as displaying notifications.
-     *
-     * @param transfer the data transfer object
-     * @param conversation  the contact to whom the data transfer is being sent
-     * @param remove   true if it should be removed from current calls
-     */
-    @Override
-    public void handleDataTransferNotification(DataTransfer transfer, Conversation conversation, boolean remove) {
-        Log.d(TAG, "handleDataTransferNotification, a data transfer event is in progress " + remove);
-        if (DeviceUtils.isTv(mContext)) {
-            return;
-        }
-        if (!remove) {
-            showFileTransferNotification(conversation, transfer);
-        } else {
-            removeTransferNotification(ConversationPath.toUri(conversation), transfer.getFileId());
-        }
-    }
-
-    @Override
-    public void removeTransferNotification(String accountId, Uri conversationUri, String transferId) {
-        removeTransferNotification(ConversationPath.toUri(accountId, conversationUri), transferId);
-    }
-
-    /**
-     * Cancels a data transfer notification and removes it from the list of notifications
-     *
-     * @param transferId the transfer id which is required to generate the notification id
-     */
-    public void removeTransferNotification(android.net.Uri path, String transferId) {
-        int id = getFileTransferNotificationId(path, transferId);
-        dataTransferNotifications.remove(id);
-        cancelFileNotification(id, false);
-        if (dataTransferNotifications.isEmpty()) {
-            mContext.startService(new Intent(DataTransferService.ACTION_STOP, path, mContext, DataTransferService.class)
-            .putExtra(KEY_NOTIFICATION_ID, id));
-        } else {
-            ContextCompat.startForegroundService(mContext, new Intent(DataTransferService.ACTION_STOP, path, mContext, DataTransferService.class)
-                    .putExtra(KEY_NOTIFICATION_ID, id));
-        }
-    }
-
-    /**
-     * @param notificationId the notification id
-     * @return the notification object for a data transfer notification
-     */
-    @Override
-    public Notification getDataTransferNotification(int notificationId) {
-        return dataTransferNotifications.get(notificationId);
-    }
-
-    @Override
-    public void showTextNotification(String accountId, Conversation conversation) {
-        TreeMap<Long, TextMessage> texts = conversation.getUnreadTextMessages();
-
-        //Log.w(TAG, "showTextNotification start " + accountId + " " + conversation.getUri() + " " + texts.size());
-
-        //TODO handle groups
-        if (texts.isEmpty() || conversation.isVisible()) {
-            cancelTextNotification(conversation.getAccountId(), conversation.getUri());
-            return;
-        }
-        if (texts.lastEntry().getValue().isNotified()) {
-            return;
-        }
-
-        Log.w(TAG, "showTextNotification " + accountId + " " + conversation.getUri());
-        mContactService.getLoadedContact(accountId, conversation.getContacts(), false)
-                .subscribe(c -> textNotification(accountId, texts, conversation),
-                        e -> Log.w(TAG, "Can't load contact", e));
-    }
-
-    private void textNotification(String accountId, TreeMap<Long, TextMessage> texts, Conversation conversation) {
-        ConversationPath cpath = new ConversationPath(conversation);
-        android.net.Uri path = cpath.toUri();
-        Pair<Bitmap, String> conversationProfile = getProfile(conversation);
-
-        int notificationVisibility = mPreferencesService.getSettings().getNotificationVisibility();
-        switch (notificationVisibility){
-            case SettingsFragment.NOTIFICATION_PUBLIC:
-                notificationVisibility = Notification.VISIBILITY_PUBLIC;
-                break;
-            case SettingsFragment.NOTIFICATION_SECRET:
-                notificationVisibility = Notification.VISIBILITY_SECRET;
-                break;
-            case SettingsFragment.NOTIFICATION_PRIVATE:
-            default:
-                notificationVisibility = Notification.VISIBILITY_PRIVATE;
-        }
-
-        TextMessage last = texts.lastEntry().getValue();
-        Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
-        Intent intentDelete = new Intent(DRingService.ACTION_CONV_DISMISS, path, mContext, DRingService.class);
-
-        NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MESSAGE)
-                .setCategory(NotificationCompat.CATEGORY_MESSAGE)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setDefaults(NotificationCompat.DEFAULT_ALL)
-                .setVisibility(notificationVisibility)
-                .setSmallIcon(R.drawable.ic_ring_logo_white)
-                .setContentTitle(conversationProfile.second)
-                .setContentText(last.getBody())
-                .setWhen(last.getTimestamp())
-                .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
-                .setDeleteIntent(PendingIntent.getService(mContext, random.nextInt(), intentDelete, 0))
-                .setAutoCancel(true)
-                .setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
-
-        String key = cpath.toKey();
-
-        Person conversationPerson = new Person.Builder()
-                .setKey(key)
-                .setName(conversationProfile.second)
-                .setIcon(conversationProfile.first == null ? null : IconCompat.createWithBitmap(conversationProfile.first))
-                .build();
-
-        if (conversationProfile.first != null) {
-            messageNotificationBuilder.setLargeIcon(conversationProfile.first);
-            Intent intentBubble = new Intent(Intent.ACTION_VIEW, path, mContext, ConversationActivity.class);
-            intentBubble.putExtra(EXTRA_BUBBLE, true);
-            messageNotificationBuilder.setBubbleMetadata(new NotificationCompat.BubbleMetadata.Builder()
-                    .setDesiredHeight(600)
-                    .setIcon(IconCompat.createWithAdaptiveBitmap(conversationProfile.first))
-                    .setIntent(PendingIntent.getActivity(mContext, 0, intentBubble,
-                            PendingIntent.FLAG_UPDATE_CURRENT))
-                    .build())
-                    .setShortcutId(key);
-        }
-
-        UnreadConversation.Builder unreadConvBuilder = new UnreadConversation.Builder(conversationProfile.second)
-                .setLatestTimestamp(last.getTimestamp());
-
-        if (texts.size() == 1) {
-            last.setNotified(true);
-            messageNotificationBuilder.setStyle(null);
-            unreadConvBuilder.addMessage(last.getBody());
-        } else {
-            Account account = mAccountService.getAccount(accountId);
-            Tuple<String, Object> profile = account == null ? null : VCardServiceImpl.loadProfile(mContext, account).blockingGet();
-            Bitmap myPic = account == null ? null : getContactPicture(account);
-            Person userPerson = new Person.Builder()
-                    .setKey(accountId)
-                    .setName(profile == null || TextUtils.isEmpty(profile.first) ? "You" : profile.first)
-                    .setIcon(myPic == null ? null : IconCompat.createWithBitmap(myPic))
-                    .build();
-
-            NotificationCompat.MessagingStyle history = new NotificationCompat.MessagingStyle(userPerson);
-            for (TextMessage textMessage : texts.values()) {
-                Contact contact = textMessage.getContact();
-                Bitmap contactPicture = getContactPicture(contact);
-                Person contactPerson = new Person.Builder()
-                        .setKey(ConversationPath.toKey(cpath.getAccountId(), contact.getUri().getUri()))
-                        .setName(contact.getDisplayName())
-                        .setIcon(contactPicture == null ? null : IconCompat.createWithBitmap(contactPicture))
-                        .build();
-                history.addMessage(new NotificationCompat.MessagingStyle.Message(
-                        textMessage.getBody(),
-                        textMessage.getTimestamp(),
-                        textMessage.isIncoming() ? contactPerson : null));
-                unreadConvBuilder.addMessage(textMessage.getBody());
-            }
-            messageNotificationBuilder.setStyle(history);
-        }
-
-        int notificationId = getTextNotificationId(conversation.getAccountId(), conversation.getUri());
-        int replyId = notificationId + 1;
-        int markAsReadId = notificationId + 2;
-
-        CharSequence replyLabel = mContext.getText(R.string.notif_reply);
-        RemoteInput remoteInput = new RemoteInput.Builder(DRingService.KEY_TEXT_REPLY)
-                .setLabel(replyLabel)
-                .build();
-
-        PendingIntent replyPendingIntent = PendingIntent.getService(mContext, replyId,
-                new Intent(DRingService.ACTION_CONV_REPLY_INLINE, path, mContext, DRingService.class),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-
-        PendingIntent readPendingIntent = PendingIntent.getService(mContext, markAsReadId,
-                new Intent(DRingService.ACTION_CONV_READ, path, mContext, DRingService.class), 0);
-
-        messageNotificationBuilder
-                .addAction(new NotificationCompat.Action.Builder(R.drawable.baseline_reply_24, replyLabel, replyPendingIntent)
-                        .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
-                        .addRemoteInput(remoteInput)
-                        .extend(new NotificationCompat.Action.WearableExtender()
-                                .setHintDisplayActionInline(true))
-                        .build())
-                .addAction(new NotificationCompat.Action.Builder(0,
-                        mContext.getString(R.string.notif_mark_as_read),
-                        readPendingIntent)
-                        .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
-                        .setShowsUserInterface(false)
-                        .build())
-                .extend(new NotificationCompat.CarExtender()
-                        .setUnreadConversation(unreadConvBuilder
-                                .setReadPendingIntent(readPendingIntent)
-                                .setReplyAction(replyPendingIntent, remoteInput)
-                                .build()));
-
-        notificationManager.notify(notificationId, messageNotificationBuilder.build());
-        mNotificationBuilders.put(notificationId, messageNotificationBuilder);
-    }
-
-    private NotificationCompat.Builder getRequestNotificationBuilder(String accountId) {
-        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_REQUEST)
-                .setDefaults(NotificationCompat.DEFAULT_ALL)
-                .setPriority(NotificationCompat.PRIORITY_HIGH)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setAutoCancel(true)
-                .setSmallIcon(R.drawable.ic_ring_logo_white)
-                .setCategory(NotificationCompat.CATEGORY_SOCIAL)
-                .setContentTitle(mContext.getString(R.string.contact_request_title));
-        Intent intentOpenTrustRequestFragment = new Intent(HomeActivity.ACTION_PRESENT_TRUST_REQUEST_FRAGMENT)
-                .setClass(mContext, HomeActivity.class)
-                .putExtra(ContactRequestsFragment.ACCOUNT_ID, accountId);
-        builder.setContentIntent(PendingIntent.getActivity(mContext,
-                random.nextInt(), intentOpenTrustRequestFragment, PendingIntent.FLAG_ONE_SHOT));
-        builder.setColor(ResourcesCompat.getColor(mContext.getResources(),
-                R.color.color_primary_dark, null));
-        return builder;
-    }
-
-    @Override
-    public void showIncomingTrustRequestNotification(final Account account) {
-        int notificationId = getIncomingTrustNotificationId(account.getAccountID());
-        Set<String> notifiedRequests = mPreferencesService.loadRequestsPreferences(account.getAccountID());
-
-        Collection<Conversation> requests = account.getPending();
-        if (requests.isEmpty()) {
-            notificationManager.cancel(notificationId);
-            return;
-        }
-        if (requests.size() == 1) {
-            Conversation request = requests.iterator().next();
-            String contactKey = request.getUri().getRawUriString();
-            if (notifiedRequests.contains(contactKey)) {
-                return;
-            }
-            mContactService.getLoadedContact(account.getAccountID(), request.getContacts(), false).subscribe(c -> {
-                NotificationCompat.Builder builder = getRequestNotificationBuilder(account.getAccountID());
-                mPreferencesService.saveRequestPreferences(account.getAccountID(), contactKey);
-                android.net.Uri info = ConversationPath.toUri(account.getAccountID(), request.getUri());
-                builder.setContentText(request.getUriTitle())
-                        .addAction(R.drawable.baseline_person_add_24, mContext.getText(R.string.accept),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(DRingService.ACTION_TRUST_REQUEST_ACCEPT, info, mContext, DRingService.class),
-                                        PendingIntent.FLAG_ONE_SHOT))
-                        .addAction(R.drawable.baseline_delete_24, mContext.getText(R.string.refuse),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(DRingService.ACTION_TRUST_REQUEST_REFUSE, info, mContext, DRingService.class),
-                                        PendingIntent.FLAG_ONE_SHOT))
-                        .addAction(R.drawable.baseline_block_24, mContext.getText(R.string.block),
-                                PendingIntent.getService(mContext, random.nextInt(),
-                                        new Intent(DRingService.ACTION_TRUST_REQUEST_BLOCK, info, mContext, DRingService.class),
-                                        PendingIntent.FLAG_ONE_SHOT));
-
-                Bitmap pic = getContactPicture(request);
-                if (pic != null)
-                    builder.setLargeIcon(pic);
-                notificationManager.notify(notificationId, builder.build());
-            }, e -> Log.w(TAG, "error showing notification", e));
-        } else {
-            NotificationCompat.Builder builder = getRequestNotificationBuilder(account.getAccountID());
-            boolean newRequest = false;
-            for (Conversation request : requests) {
-                Contact contact = request.getContact();
-                if (contact != null) {
-                    String contactKey = contact.getUri().getRawRingId();
-                    if (!notifiedRequests.contains(contactKey)) {
-                        newRequest = true;
-                        mPreferencesService.saveRequestPreferences(account.getAccountID(), contactKey);
-                    }
-                }
-            }
-            if (!newRequest)
-                return;
-            builder.setContentText(String.format(mContext.getString(R.string.contact_request_msg), requests.size()));
-            builder.setLargeIcon(null);
-            notificationManager.notify(notificationId, builder.build());
-        }
-    }
-
-    @Override
-    public void showFileTransferNotification(Conversation conversation, DataTransfer info) {
-        if (info == null) {
-            return;
-        }
-        InteractionStatus event = info.getStatus();
-        if (event == null || event == InteractionStatus.FILE_AVAILABLE) {
-            return;
-        }
-        android.net.Uri path = ConversationPath.toUri(conversation);
-        Log.d(TAG, "showFileTransferNotification " + path);
-        String dataTransferId = info.getFileId();
-        int notificationId = getFileTransferNotificationId(path, dataTransferId);
-
-        Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
-
-        if (event.isOver()) {
-            removeTransferNotification(path, dataTransferId);
-            if (info.isOutgoing() || info.isError()) {
-                return;
-            }
-
-            NotificationCompat.Builder notif = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER)
-                    .setSmallIcon(R.drawable.ic_ring_logo_white)
-                    .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
-                    .setAutoCancel(true);
-
-            if (info.showPicture()) {
-                File filePath = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), info.getStoragePath());
-                Bitmap img;
-                try {
-                    BitmapDrawable d = (BitmapDrawable) Glide.with(mContext)
-                            .load(filePath)
-                            .submit()
-                            .get();
-                    img = d.getBitmap();
-                    notif.setContentTitle(mContext.getString(R.string.notif_incoming_picture, conversation.getTitle()));
-                    notif.setStyle(new NotificationCompat.BigPictureStyle()
-                            .bigPicture(img));
-                } catch (Exception e) {
-                    Log.w(TAG, "Can't load image for notification", e);
-                    return;
-                }
-            } else {
-                notif.setContentTitle(mContext.getString(R.string.notif_incoming_file_transfer_title, conversation.getTitle()));
-                notif.setStyle(null);
-            }
-            Bitmap picture = getContactPicture(conversation);
-            if (picture != null)
-                notif.setLargeIcon(picture);
-            notificationManager.notify(random.nextInt(), notif.build());
-            return;
-        }
-        NotificationCompat.Builder messageNotificationBuilder = mNotificationBuilders.get(notificationId);
-        if (messageNotificationBuilder == null) {
-            messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER);
-        }
-
-        boolean ongoing = event == InteractionStatus.TRANSFER_ONGOING || event == InteractionStatus.TRANSFER_ACCEPTED;
-        String titleMessage = mContext.getString(info.isOutgoing() ? R.string.notif_outgoing_file_transfer_title : R.string.notif_incoming_file_transfer_title, conversation.getTitle());
-        messageNotificationBuilder.setContentTitle(titleMessage)
-                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setAutoCancel(false)
-                .setOngoing(ongoing)
-                .setSmallIcon(R.drawable.ic_ring_logo_white)
-                .setCategory(NotificationCompat.CATEGORY_PROGRESS)
-                .setOnlyAlertOnce(true)
-                .setContentText(event == Interaction.InteractionStatus.TRANSFER_ONGOING ?
-                        Formatter.formatFileSize(mContext, info.getBytesProgress()) + " / " + Formatter.formatFileSize(mContext, info.getTotalSize()) :
-                        info.getDisplayName() + ": " + ResourceMapper.getReadableFileTransferStatus(mContext, event))
-                .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
-                .setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
-        Bitmap picture = getContactPicture(conversation);
-        if (picture != null)
-            messageNotificationBuilder.setLargeIcon(picture);
-        if (event.isOver()) {
-            messageNotificationBuilder.setProgress(0, 0, false);
-        } else if (ongoing) {
-            messageNotificationBuilder.setProgress((int) info.getTotalSize(), (int) info.getBytesProgress(), false);
-        } else {
-            messageNotificationBuilder.setProgress(0, 0, true);
-        }
-        if (event == Interaction.InteractionStatus.TRANSFER_CREATED) {
-            messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_VIBRATE);
-            mNotificationBuilders.put(notificationId, messageNotificationBuilder);
-            // updateNotification(messageNotificationBuilder.build(), notificationId);
-            return;
-        } else {
-            messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_LIGHTS);
-        }
-        messageNotificationBuilder.mActions.clear();
-        if (event == Interaction.InteractionStatus.TRANSFER_AWAITING_HOST) {
-            messageNotificationBuilder
-                    .addAction(R.drawable.baseline_call_received_24, mContext.getText(R.string.accept),
-                            PendingIntent.getService(mContext, random.nextInt(),
-                                    new Intent(DRingService.ACTION_FILE_ACCEPT, path, mContext, DRingService.class)
-                                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
-                                    PendingIntent.FLAG_ONE_SHOT))
-                    .addAction(R.drawable.baseline_cancel_24, mContext.getText(R.string.refuse),
-                            PendingIntent.getService(mContext, random.nextInt(),
-                                    new Intent(DRingService.ACTION_FILE_CANCEL, path, mContext, DRingService.class)
-                                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
-                                    PendingIntent.FLAG_ONE_SHOT));
-            mNotificationBuilders.put(notificationId, messageNotificationBuilder);
-            updateNotification(messageNotificationBuilder.build(), notificationId);
-            return;
-        } else if (!event.isOver()) {
-            messageNotificationBuilder
-                    .addAction(R.drawable.baseline_cancel_24, mContext.getText(android.R.string.cancel),
-                            PendingIntent.getService(mContext, random.nextInt(),
-                                    new Intent(DRingService.ACTION_FILE_CANCEL, path, mContext, DRingService.class)
-                                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId),
-                                    PendingIntent.FLAG_ONE_SHOT));
-        }
-        mNotificationBuilders.put(notificationId, messageNotificationBuilder);
-        dataTransferNotifications.put(notificationId, messageNotificationBuilder.build());
-        ContextCompat.startForegroundService(mContext, new Intent(DataTransferService.ACTION_START, path, mContext, DataTransferService.class)
-                .putExtra(KEY_NOTIFICATION_ID, notificationId));
-        //startForegroundService(notificationId, DataTransferService.class);
-    }
-
-    @Override
-    public void showMissedCallNotification(Call call) {
-        final int notificationId = call.getDaemonIdString().hashCode();
-        NotificationCompat.Builder messageNotificationBuilder = mNotificationBuilders.get(notificationId);
-        if (messageNotificationBuilder == null) {
-            messageNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MISSED_CALL);
-        }
-
-        android.net.Uri path = ConversationPath.toUri(call);
-
-        Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class);
-
-        messageNotificationBuilder.setContentTitle(mContext.getText(R.string.notif_missed_incoming_call))
-                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
-                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
-                .setSmallIcon(R.drawable.baseline_call_missed_24)
-                .setCategory(NotificationCompat.CATEGORY_CALL)
-                .setOnlyAlertOnce(true)
-                .setAutoCancel(true)
-                .setContentText(call.getContact().getDisplayName())
-                .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
-                .setColor(ResourcesCompat.getColor(mContext.getResources(), R.color.color_primary_dark, null));
-
-        setContactPicture(call.getContact(), messageNotificationBuilder);
-        notificationManager.notify(notificationId, messageNotificationBuilder.build());
-    }
-
-    @Override
-    public Object getServiceNotification() {
-        Intent intentHome = new Intent(Intent.ACTION_VIEW)
-                .setClass(mContext, HomeActivity.class)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        PendingIntent pendIntent = PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT);
-        NotificationCompat.Builder messageNotificationBuilder = new NotificationCompat.Builder(mContext, NotificationServiceImpl.NOTIF_CHANNEL_SERVICE);
-        messageNotificationBuilder
-                .setContentTitle(mContext.getText(R.string.app_name))
-                .setContentText(mContext.getText(R.string.notif_background_service))
-                .setSmallIcon(R.drawable.ic_ring_logo_white)
-                .setContentIntent(pendIntent)
-                .setVisibility(NotificationCompat.VISIBILITY_SECRET)
-                .setPriority(NotificationCompat.PRIORITY_MIN)
-                .setOngoing(true)
-                .setCategory(NotificationCompat.CATEGORY_SERVICE);
-        return messageNotificationBuilder.build();
-    }
-
-    @Override
-    public void cancelTextNotification(String accountId, Uri contact) {
-        int notificationId = getTextNotificationId(accountId, contact);
-        notificationManager.cancel(notificationId);
-        mNotificationBuilders.remove(notificationId);
-    }
-
-    @Override
-    public void cancelTrustRequestNotification(String accountID) {
-        if (accountID == null) {
-            return;
-        }
-        int notificationId = getIncomingTrustNotificationId(accountID);
-        notificationManager.cancel(notificationId);
-    }
-
-    @Override
-    public void cancelCallNotification() {
-        notificationManager.cancel(NOTIF_CALL_ID);
-        mNotificationBuilders.remove(NOTIF_CALL_ID);
-        callNotifications.clear();
-    }
-
-    /**\
-     * Cancels a notification
-     * @param notificationId the notification ID
-     * @param isMigratingToService true if the notification is being updated to be a part of the foreground service
-     */
-    @Override
-    public void cancelFileNotification(int notificationId, boolean isMigratingToService) {
-        notificationManager.cancel(notificationId);
-        if(!isMigratingToService)
-            mNotificationBuilders.remove(notificationId);
-    }
-
-    @Override
-    public void cancelAll() {
-        notificationManager.cancelAll();
-        mNotificationBuilders.clear();
-    }
-
-    private int getIncomingTrustNotificationId(String accountId) {
-        return (NOTIF_TRUST_REQUEST + accountId).hashCode();
-    }
-
-    private int getTextNotificationId(String accountId, Uri contact) {
-        return (NOTIF_MSG + accountId + contact.toString()).hashCode();
-    }
-
-    private int getFileTransferNotificationId(android.net.Uri path, String dataTransferId) {
-        return (NOTIF_FILE_TRANSFER + path.toString() + dataTransferId).hashCode();
-    }
-
-    private Bitmap getContactPicture(Contact contact) {
-        try {
-            return AvatarFactory.getBitmapAvatar(mContext, contact, avatarSize, false).blockingGet();
-        } catch (Exception e) {
-            return null;
-        }
-    }
-    private Bitmap getContactPicture(Conversation conversation) {
-        try {
-            return AvatarFactory.getBitmapAvatar(mContext, conversation, avatarSize, false).blockingGet();
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    private Bitmap getContactPicture(Account account) {
-        return AvatarFactory.getBitmapAvatar(mContext, account, avatarSize).blockingGet();
-    }
-
-    private Pair<Bitmap, String> getProfile(Conversation conversation) {
-        return Pair.create(getContactPicture(conversation), conversation.getTitle());
-    }
-
-    private void setContactPicture(Contact contact, NotificationCompat.Builder messageNotificationBuilder) {
-        Bitmap pic = getContactPicture(contact);
-        if (pic != null)
-            messageNotificationBuilder.setLargeIcon(pic);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt
new file mode 100644
index 000000000..670963168
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.annotation.SuppressLint
+import android.app.*
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.media.AudioAttributes
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Build
+import android.text.TextUtils
+import android.text.format.Formatter
+import android.util.Log
+import android.util.SparseArray
+import androidx.annotation.RequiresApi
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import androidx.core.app.Person
+import androidx.core.app.RemoteInput
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.IconCompat
+import androidx.core.util.Pair
+import com.bumptech.glide.Glide
+import cx.ring.R
+import cx.ring.client.ConversationActivity
+import cx.ring.client.HomeActivity
+import cx.ring.contactrequests.ContactRequestsFragment
+import cx.ring.fragments.ConversationFragment
+import cx.ring.service.CallNotificationService
+import cx.ring.service.DRingService
+import cx.ring.settings.SettingsFragment
+import cx.ring.tv.call.TVCallActivity
+import cx.ring.utils.ConversationPath
+import cx.ring.utils.DeviceUtils
+import cx.ring.utils.ResourceMapper
+import cx.ring.views.AvatarFactory
+import net.jami.model.*
+import net.jami.model.Interaction.InteractionStatus
+import net.jami.services.*
+import java.util.*
+import java.util.concurrent.ConcurrentHashMap
+
+class NotificationServiceImpl(
+    val mContext: Context,
+    var mAccountService: AccountService,
+    var mContactService: ContactService,
+    var mPreferencesService: PreferencesService,
+    var mDeviceRuntimeService: DeviceRuntimeService) : NotificationService {
+    private val mNotificationBuilders = SparseArray<NotificationCompat.Builder>()
+
+    private var notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(mContext)
+    private val random = Random()
+    private var avatarSize = (mContext.resources.displayMetrics.density * AvatarFactory.SIZE_NOTIF).toInt()
+    private val currentCalls = LinkedHashMap<String, Conference>()
+    private val callNotifications = ConcurrentHashMap<Int, Notification>()
+    private val dataTransferNotifications = ConcurrentHashMap<Int, Notification>()
+    @SuppressLint("CheckResult")
+    fun initHelper() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            registerNotificationChannels(mContext)
+        }
+    }
+
+    /**
+     * Starts the call activity directly for Android TV
+     *
+     * @param callId the call ID
+     */
+    private fun startCallActivity(callId: String) {
+        mContext.startActivity(
+            Intent(Intent.ACTION_VIEW)
+                .putExtra(NotificationService.KEY_CALL_ID, callId)
+                .setClass(mContext.applicationContext, TVCallActivity::class.java)
+                .setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+        )
+    }
+
+    private fun buildCallNotification(conference: Conference): Notification? {
+        var ongoingCallId: String? = null
+        for (conf in currentCalls.values) {
+            if (conf !== conference && conf.state == Call.CallStatus.CURRENT) ongoingCallId =
+                conf.participants[0].daemonIdString
+        }
+        val call = conference.participants[0]
+        val gotoIntent = PendingIntent.getService(mContext, random.nextInt(),
+            Intent(DRingService.ACTION_CALL_VIEW)
+                .setClass(mContext, DRingService::class.java)
+                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString), 0)
+        val contact = call.contact!!
+        val messageNotificationBuilder: NotificationCompat.Builder
+        if (conference.isOnGoing) {
+            messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS)
+            messageNotificationBuilder.setContentTitle(mContext.getString(R.string.notif_current_call_title, contact.displayName))
+                .setContentText(mContext.getText(R.string.notif_current_call))
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                .setContentIntent(gotoIntent)
+                .setSound(null)
+                .setVibrate(null)
+                .setColorized(true)
+                .setUsesChronometer(true)
+                .setWhen(conference.timestampStart)
+                .setColor(ContextCompat.getColor(mContext, R.color.color_primary_light))
+                .addAction(
+                    R.drawable.baseline_call_end_24,
+                    mContext.getText(R.string.action_call_hangup),
+                    PendingIntent.getService(
+                        mContext, random.nextInt(),
+                        Intent(DRingService.ACTION_CALL_END)
+                            .setClass(mContext, DRingService::class.java)
+                            .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString),
+                        PendingIntent.FLAG_ONE_SHOT
+                    )
+                )
+        } else if (conference.isRinging) {
+            if (conference.isIncoming) {
+                messageNotificationBuilder =
+                    NotificationCompat.Builder(mContext, NOTIF_CHANNEL_INCOMING_CALL)
+                messageNotificationBuilder.setContentTitle(
+                    mContext.getString(
+                        R.string.notif_incoming_call_title,
+                        contact.displayName
+                    )
+                )
+                    .setPriority(NotificationCompat.PRIORITY_MAX)
+                    .setContentText(mContext.getText(R.string.notif_incoming_call))
+                    .setContentIntent(gotoIntent)
+                    .setSound(null)
+                    .setVibrate(null)
+                    .setFullScreenIntent(gotoIntent, true)
+                    .addAction(
+                        R.drawable.baseline_call_end_24,
+                        mContext.getText(R.string.action_call_decline),
+                        PendingIntent.getService(
+                            mContext, random.nextInt(),
+                            Intent(DRingService.ACTION_CALL_REFUSE)
+                                .setClass(mContext, DRingService::class.java)
+                                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString),
+                            PendingIntent.FLAG_ONE_SHOT
+                        )
+                    )
+                    .addAction(
+                        R.drawable.baseline_call_24,
+                        if (ongoingCallId == null)
+                            mContext.getText(R.string.action_call_accept)
+                        else
+                            mContext.getText(R.string.action_call_end_accept),
+                        PendingIntent.getService(
+                            mContext, random.nextInt(),
+                            Intent(if (ongoingCallId == null) DRingService.ACTION_CALL_ACCEPT else DRingService.ACTION_CALL_END_ACCEPT)
+                                .setClass(mContext, DRingService::class.java)
+                                .putExtra(NotificationService.KEY_END_ID, ongoingCallId)
+                                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString),
+                            PendingIntent.FLAG_ONE_SHOT
+                        )
+                    )
+                if (ongoingCallId != null) {
+                    messageNotificationBuilder.addAction(
+                        R.drawable.baseline_call_24,
+                        mContext.getText(R.string.action_call_hold_accept),
+                        PendingIntent.getService(
+                            mContext, random.nextInt(),
+                            Intent(DRingService.ACTION_CALL_HOLD_ACCEPT)
+                                .setClass(mContext, DRingService::class.java)
+                                .putExtra(NotificationService.KEY_HOLD_ID, ongoingCallId)
+                                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString),
+                            PendingIntent.FLAG_ONE_SHOT
+                        )
+                    )
+                }
+            } else {
+                messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_CALL_IN_PROGRESS)
+                    .setContentTitle(mContext.getString(R.string.notif_outgoing_call_title, contact.displayName))
+                    .setContentText(mContext.getText(R.string.notif_outgoing_call))
+                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                    .setContentIntent(gotoIntent)
+                    .setSound(null)
+                    .setVibrate(null)
+                    .setColorized(true)
+                    .setColor(ContextCompat.getColor(mContext, R.color.color_primary_light))
+                    .addAction(
+                        R.drawable.baseline_call_end_24,
+                        mContext.getText(R.string.action_call_hangup),
+                        PendingIntent.getService(
+                            mContext, random.nextInt(),
+                            Intent(DRingService.ACTION_CALL_END)
+                                .setClass(mContext, DRingService::class.java)
+                                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString),
+                            PendingIntent.FLAG_ONE_SHOT
+                        )
+                    )
+            }
+        } else {
+            return null
+        }
+        messageNotificationBuilder.setOngoing(true)
+            .setCategory(NotificationCompat.CATEGORY_CALL)
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+        setContactPicture(contact, messageNotificationBuilder)
+        return messageNotificationBuilder.build()
+    }
+
+    override fun showCallNotification(notifId: Int): Any {
+        return callNotifications.remove(notifId)!!
+    }
+
+    override fun showLocationNotification(first: Account, contact: Contact) {
+        val path = ConversationPath.toUri(first.accountID, contact.uri)
+        val intentConversation =
+            Intent(Intent.ACTION_VIEW, path, mContext, ConversationActivity::class.java)
+                .putExtra(ConversationFragment.EXTRA_SHOW_MAP, true)
+        val messageNotificationBuilder = NotificationCompat.Builder(
+            mContext, NOTIF_CHANNEL_MESSAGE
+        )
+            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
+            .setPriority(NotificationCompat.PRIORITY_HIGH)
+            .setDefaults(NotificationCompat.DEFAULT_ALL)
+            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+            .setLargeIcon(getContactPicture(contact))
+            .setContentText(
+                mContext.getString(
+                    R.string.location_share_contact,
+                    contact.displayName
+                )
+            )
+            .setContentIntent(
+                PendingIntent.getActivity(
+                    mContext,
+                    random.nextInt(),
+                    intentConversation,
+                    0
+                )
+            )
+            .setAutoCancel(false)
+            .setColor(ResourcesCompat.getColor(
+                    mContext.resources,
+                    R.color.color_primary_dark,
+                    null
+                ))
+        notificationManager.notify(
+            Objects.hash("Location", path),
+            messageNotificationBuilder.build()
+        )
+    }
+
+    override fun cancelLocationNotification(first: Account, contact: Contact) {
+        notificationManager.cancel(
+            Objects.hash(
+                "Location",
+                ConversationPath.toUri(first.accountID, contact.uri)
+            )
+        )
+    }
+
+    /**
+     * Updates a notification
+     *
+     * @param notification   a built notification object
+     * @param notificationId the notification's id
+     */
+    private fun updateNotification(notification: Notification, notificationId: Int) {
+        notificationManager.notify(notificationId, notification)
+    }
+
+    /**
+     * Starts a service (data transfer or call)
+     *
+     * @param id            the notification id
+     */
+    private fun startForegroundService(id: Int, serviceClass: Class<*>) {
+        ContextCompat.startForegroundService(
+            mContext, Intent(mContext, serviceClass)
+                .putExtra(NotificationService.KEY_NOTIFICATION_ID, id)
+        )
+    }
+
+    /**
+     * Handles the creation and destruction of services associated with calls as well as displaying notifications.
+     *
+     * @param conference the conference object for the notification
+     * @param remove     true if it should be removed from current calls
+     */
+    override fun handleCallNotification(conference: Conference, remove: Boolean) {
+        if (DeviceUtils.isTv(mContext)) {
+            if (!remove) startCallActivity(conference.id)
+            return
+        }
+        var notification: Notification? = null
+
+        // Build notification
+        val id = conference.id
+        currentCalls.remove(id)
+        if (!remove) {
+            currentCalls[id] = conference
+            notification = buildCallNotification(conference)
+        }
+        if (notification == null && currentCalls.isNotEmpty()) {
+            // Build notification for other calls if any remains
+            //for (c in currentCalls.values) conference = c
+            notification = buildCallNotification(currentCalls.values.last())
+        }
+
+        // Send notification to the  Service
+        if (notification != null) {
+            val nid = random.nextInt()
+            callNotifications[nid] = notification
+            ContextCompat.startForegroundService(mContext,
+                Intent(CallNotificationService.ACTION_START, null, mContext, CallNotificationService::class.java)
+                    .putExtra(NotificationService.KEY_NOTIFICATION_ID, nid))
+        } else {
+            try {
+                mContext.startService(Intent(CallNotificationService.ACTION_STOP, null, mContext, CallNotificationService::class.java))
+            } catch (e: Exception) {
+                Log.w(TAG, "Error stopping service", e)
+            }
+        }
+    }
+
+    override fun onConnectionUpdate(b: Boolean) {
+        /*Log.i(TAG, "onConnectionUpdate " + b);
+        if (b) {
+            Intent serviceIntent = new Intent(SyncService.ACTION_START).setClass(mContext, SyncService.class);
+            try {
+                ContextCompat.startForegroundService(mContext, serviceIntent);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Error starting service", e);
+            }
+        } else {
+            try {
+                mContext.startService(new Intent(SyncService.ACTION_STOP).setClass(mContext, SyncService.class));
+            } catch (IllegalStateException ignored) {
+            }
+        }*/
+    }
+
+    /**
+     * Handles the creation and destruction of services associated with transfers as well as displaying notifications.
+     *
+     * @param transfer the data transfer object
+     * @param conversation  the contact to whom the data transfer is being sent
+     * @param remove   true if it should be removed from current calls
+     */
+    override fun handleDataTransferNotification(transfer: DataTransfer, conversation: Conversation, remove: Boolean) {
+        Log.d(TAG, "handleDataTransferNotification, a data transfer event is in progress $remove")
+        if (DeviceUtils.isTv(mContext)) {
+            return
+        }
+        if (!remove) {
+            showFileTransferNotification(conversation, transfer)
+        } else {
+            removeTransferNotification(ConversationPath.toUri(conversation), transfer.fileId ?: transfer.id.toString())
+        }
+    }
+
+    override fun removeTransferNotification(accountId: String, conversationUri: net.jami.model.Uri, transferId: String) {
+        removeTransferNotification(ConversationPath.toUri(accountId, conversationUri), transferId)
+    }
+
+    /**
+     * Cancels a data transfer notification and removes it from the list of notifications
+     *
+     * @param transferId the transfer id which is required to generate the notification id
+     */
+    fun removeTransferNotification(path: Uri, transferId: String) {
+        val id = getFileTransferNotificationId(path, transferId)
+        dataTransferNotifications.remove(id)
+        cancelFileNotification(id, false)
+        if (dataTransferNotifications.isEmpty()) {
+            mContext.startService(
+                Intent(
+                    DataTransferService.ACTION_STOP,
+                    path,
+                    mContext,
+                    DataTransferService::class.java
+                )
+                    .putExtra(NotificationService.KEY_NOTIFICATION_ID, id)
+            )
+        } else {
+            ContextCompat.startForegroundService(
+                mContext,
+                Intent(
+                    DataTransferService.ACTION_STOP,
+                    path,
+                    mContext,
+                    DataTransferService::class.java
+                )
+                    .putExtra(NotificationService.KEY_NOTIFICATION_ID, id)
+            )
+        }
+    }
+
+    /**
+     * @param notificationId the notification id
+     * @return the notification object for a data transfer notification
+     */
+    override fun getDataTransferNotification(notificationId: Int): Notification {
+        return dataTransferNotifications[notificationId]!!
+    }
+
+    override fun showTextNotification(accountId: String, conversation: Conversation) {
+        val texts = conversation.unreadTextMessages
+
+        //Log.w(TAG, "showTextNotification start " + accountId + " " + conversation.getUri() + " " + texts.size());
+
+        //TODO handle groups
+        if (texts.isEmpty() || conversation.isVisible) {
+            cancelTextNotification(conversation.accountId, conversation.uri)
+            return
+        }
+        if (texts.lastEntry().value.isNotified) {
+            return
+        }
+        Log.w(TAG, "showTextNotification " + accountId + " " + conversation.uri)
+        mContactService.getLoadedContact(accountId, conversation.contacts, false)
+            .subscribe({ textNotification(accountId, texts, conversation) })
+            { e: Throwable -> Log.w(TAG, "Can't load contact", e) }
+    }
+
+    private fun textNotification(
+        accountId: String,
+        texts: TreeMap<Long, TextMessage>,
+        conversation: Conversation
+    ) {
+        val cpath = ConversationPath(conversation)
+        val path = cpath.toUri()
+        val conversationProfile = getProfile(conversation)
+        var notificationVisibility = mPreferencesService.settings.notificationVisibility
+        notificationVisibility = when (notificationVisibility) {
+            SettingsFragment.NOTIFICATION_PUBLIC -> Notification.VISIBILITY_PUBLIC
+            SettingsFragment.NOTIFICATION_SECRET -> Notification.VISIBILITY_SECRET
+            SettingsFragment.NOTIFICATION_PRIVATE -> Notification.VISIBILITY_PRIVATE
+            else -> Notification.VISIBILITY_PRIVATE
+        }
+        val last = texts.lastEntry()?.value
+        val intentConversation = Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService::class.java)
+        val intentDelete = Intent(DRingService.ACTION_CONV_DISMISS, path, mContext, DRingService::class.java)
+        val messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MESSAGE)
+            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
+            .setPriority(Notification.PRIORITY_HIGH)
+            .setDefaults(NotificationCompat.DEFAULT_ALL)
+            .setVisibility(notificationVisibility)
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+            .setContentTitle(conversationProfile.second)
+            .setContentText(last?.body)
+            .setWhen(last?.timestamp ?: 0)
+            .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
+            .setDeleteIntent(PendingIntent.getService(mContext, random.nextInt(), intentDelete, 0))
+            .setAutoCancel(true)
+            .setColor(
+                ResourcesCompat.getColor(
+                    mContext.resources,
+                    R.color.color_primary_dark,
+                    null
+                )
+            )
+        val key = cpath.toKey()
+        val conversationPerson = Person.Builder()
+            .setKey(key)
+            .setName(conversationProfile.second)
+            .setIcon(
+                if (conversationProfile.first == null) null else IconCompat.createWithBitmap(conversationProfile.first)
+            )
+            .build()
+        if (conversationProfile.first != null) {
+            messageNotificationBuilder.setLargeIcon(conversationProfile.first)
+            val intentBubble = Intent(Intent.ACTION_VIEW, path, mContext, ConversationActivity::class.java)
+            intentBubble.putExtra(EXTRA_BUBBLE, true)
+            messageNotificationBuilder
+                .setBubbleMetadata(NotificationCompat.BubbleMetadata.Builder(PendingIntent.getActivity(
+                    mContext, 0, intentBubble,
+                    PendingIntent.FLAG_UPDATE_CURRENT
+                ), IconCompat.createWithAdaptiveBitmap(conversationProfile.first))
+                    .setDesiredHeight(600)
+                    .build())
+                .addPerson(conversationPerson)
+                .setShortcutId(key)
+        }
+        if (texts.size == 1) {
+            last!!.isNotified = true
+            messageNotificationBuilder.setStyle(null)
+        } else {
+            val account = mAccountService.getAccount(accountId)
+            val profile = if (account == null) null else VCardServiceImpl.loadProfile(
+                mContext, account
+            ).blockingFirst()
+            val myPic = account?.let { getContactPicture(it) }
+            val userPerson = Person.Builder()
+                .setKey(accountId)
+                .setName(if (profile == null || TextUtils.isEmpty(profile.first)) "You" else profile.first)
+                .setIcon(if (myPic == null) null else IconCompat.createWithBitmap(myPic))
+                .build()
+            val history = NotificationCompat.MessagingStyle(userPerson)
+            for (textMessage in texts.values) {
+                val contact = textMessage.contact!!
+                val contactPicture = getContactPicture(contact)
+                val contactPerson = Person.Builder()
+                    .setKey(ConversationPath.toKey(cpath.accountId, contact.uri.uri))
+                    .setName(contact.displayName)
+                    .setIcon(if (contactPicture == null) null else IconCompat.createWithBitmap(contactPicture))
+                    .build()
+                history.addMessage(
+                    NotificationCompat.MessagingStyle.Message(
+                        textMessage.body,
+                        textMessage.timestamp,
+                        if (textMessage.isIncoming) contactPerson else null
+                    )
+                )
+            }
+            messageNotificationBuilder.setStyle(history)
+        }
+        val notificationId = getTextNotificationId(conversation.accountId, conversation.uri)
+        val replyId = notificationId + 1
+        val markAsReadId = notificationId + 2
+        val replyLabel = mContext.getText(R.string.notif_reply)
+        val remoteInput = RemoteInput.Builder(DRingService.KEY_TEXT_REPLY)
+            .setLabel(replyLabel)
+            .build()
+        val replyPendingIntent = PendingIntent.getService(
+            mContext, replyId,
+            Intent(DRingService.ACTION_CONV_REPLY_INLINE, path, mContext, DRingService::class.java),
+            PendingIntent.FLAG_UPDATE_CURRENT
+        )
+        val readPendingIntent = PendingIntent.getService(
+            mContext, markAsReadId,
+            Intent(DRingService.ACTION_CONV_READ, path, mContext, DRingService::class.java), 0
+        )
+        messageNotificationBuilder
+            .addAction(
+                NotificationCompat.Action.Builder(
+                    R.drawable.baseline_reply_24,
+                    replyLabel,
+                    replyPendingIntent
+                )
+                    .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
+                    .addRemoteInput(remoteInput)
+                    .extend(
+                        NotificationCompat.Action.WearableExtender()
+                            .setHintDisplayActionInline(true)
+                    )
+                    .build()
+            )
+            .addAction(
+                NotificationCompat.Action.Builder(
+                    0,
+                    mContext.getString(R.string.notif_mark_as_read),
+                    readPendingIntent
+                )
+                    .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
+                    .setShowsUserInterface(false)
+                    .build()
+            )
+        notificationManager.notify(notificationId, messageNotificationBuilder.build())
+        mNotificationBuilders.put(notificationId, messageNotificationBuilder)
+    }
+
+    private fun getRequestNotificationBuilder(accountId: String): NotificationCompat.Builder {
+        val builder = NotificationCompat.Builder(
+            mContext, NOTIF_CHANNEL_REQUEST
+        )
+            .setDefaults(NotificationCompat.DEFAULT_ALL)
+            .setPriority(NotificationCompat.PRIORITY_HIGH)
+            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+            .setAutoCancel(true)
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+            .setCategory(NotificationCompat.CATEGORY_SOCIAL)
+            .setContentTitle(mContext.getString(R.string.contact_request_title))
+        val intentOpenTrustRequestFragment =
+            Intent(HomeActivity.ACTION_PRESENT_TRUST_REQUEST_FRAGMENT)
+                .setClass(mContext, HomeActivity::class.java)
+                .putExtra(ContactRequestsFragment.ACCOUNT_ID, accountId)
+        builder.setContentIntent(
+            PendingIntent.getActivity(
+                mContext,
+                random.nextInt(), intentOpenTrustRequestFragment, PendingIntent.FLAG_ONE_SHOT
+            )
+        )
+        builder.color = ResourcesCompat.getColor(
+            mContext.resources,
+            R.color.color_primary_dark, null
+        )
+        return builder
+    }
+
+    override fun showIncomingTrustRequestNotification(account: Account) {
+        val notificationId = getIncomingTrustNotificationId(account.accountID)
+        val notifiedRequests = mPreferencesService.loadRequestsPreferences(account.accountID)
+        val requests = account.getPending()
+        if (requests.isEmpty()) {
+            notificationManager.cancel(notificationId)
+            return
+        }
+        if (requests.size == 1) {
+            val request = requests.iterator().next()
+            val contactKey = request.uri.rawUriString
+            if (notifiedRequests.contains(contactKey)) {
+                return
+            }
+            mContactService.getLoadedContact(account.accountID, request.contacts, false)
+                .subscribe(
+                    {
+                        val builder = getRequestNotificationBuilder(account.accountID)
+                        mPreferencesService.saveRequestPreferences(account.accountID, contactKey)
+                        val info = ConversationPath.toUri(account.accountID, request.uri)
+                        builder.setContentText(request.uriTitle)
+                            .addAction(
+                                R.drawable.baseline_person_add_24,
+                                mContext.getText(R.string.accept),
+                                PendingIntent.getService(
+                                    mContext, random.nextInt(),
+                                    Intent(
+                                        DRingService.ACTION_TRUST_REQUEST_ACCEPT,
+                                        info,
+                                        mContext,
+                                        DRingService::class.java
+                                    ),
+                                    PendingIntent.FLAG_ONE_SHOT
+                                )
+                            )
+                            .addAction(
+                                R.drawable.baseline_delete_24, mContext.getText(R.string.refuse),
+                                PendingIntent.getService(
+                                    mContext, random.nextInt(),
+                                    Intent(
+                                        DRingService.ACTION_TRUST_REQUEST_REFUSE,
+                                        info,
+                                        mContext,
+                                        DRingService::class.java
+                                    ),
+                                    PendingIntent.FLAG_ONE_SHOT
+                                )
+                            )
+                            .addAction(
+                                R.drawable.baseline_block_24, mContext.getText(R.string.block),
+                                PendingIntent.getService(
+                                    mContext, random.nextInt(),
+                                    Intent(
+                                        DRingService.ACTION_TRUST_REQUEST_BLOCK,
+                                        info,
+                                        mContext,
+                                        DRingService::class.java
+                                    ),
+                                    PendingIntent.FLAG_ONE_SHOT
+                                )
+                            )
+                        val pic = getContactPicture(request)
+                        if (pic != null) builder.setLargeIcon(pic)
+                        notificationManager.notify(notificationId, builder.build())
+                    }) { e: Throwable? -> Log.w(TAG, "error showing notification", e) }
+        } else {
+            val builder = getRequestNotificationBuilder(account.accountID)
+            var newRequest = false
+            for (request in requests) {
+                val contact = request.contact
+                if (contact != null) {
+                    val contactKey = contact.uri.rawRingId
+                    if (!notifiedRequests.contains(contactKey)) {
+                        newRequest = true
+                        mPreferencesService.saveRequestPreferences(account.accountID, contactKey)
+                    }
+                }
+            }
+            if (!newRequest) return
+            builder.setContentText(
+                String.format(
+                    mContext.getString(R.string.contact_request_msg),
+                    requests.size
+                )
+            )
+            builder.setLargeIcon(null)
+            notificationManager.notify(notificationId, builder.build())
+        }
+    }
+
+    override fun showFileTransferNotification(conversation: Conversation, info: DataTransfer) {
+        val event = info.status ?: return
+        if (event == InteractionStatus.FILE_AVAILABLE)
+            return
+        val path = ConversationPath.toUri(conversation)
+        Log.d(TAG, "showFileTransferNotification $path")
+        val dataTransferId = info.fileId ?: info.id.toString()
+        val notificationId = getFileTransferNotificationId(path, dataTransferId)
+        val intentConversation =
+            Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService::class.java)
+        if (event.isOver) {
+            removeTransferNotification(path, dataTransferId)
+            if (info.isOutgoing || info.isError) {
+                return
+            }
+            val notif = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER)
+                .setSmallIcon(R.drawable.ic_ring_logo_white)
+                .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
+                .setAutoCancel(true)
+            if (info.showPicture()) {
+                val filePath = mDeviceRuntimeService.getConversationPath(
+                    conversation.uri.rawRingId,
+                    info.storagePath
+                )
+                val img: Bitmap
+                try {
+                    val d = Glide.with(mContext)
+                        .load(filePath)
+                        .submit()
+                        .get() as BitmapDrawable
+                    img = d.bitmap
+                    notif.setContentTitle(mContext.getString(R.string.notif_incoming_picture,conversation.title))
+                    notif.setStyle(NotificationCompat.BigPictureStyle().bigPicture(img))
+                } catch (e: Exception) {
+                    Log.w(TAG, "Can't load image for notification", e)
+                    return
+                }
+            } else {
+                notif.setContentTitle(mContext.getString(R.string.notif_incoming_file_transfer_title, conversation.title))
+                notif.setStyle(null)
+            }
+            val picture = getContactPicture(conversation)
+            if (picture != null) notif.setLargeIcon(picture)
+            notificationManager.notify(random.nextInt(), notif.build())
+            return
+        }
+        var messageNotificationBuilder = mNotificationBuilders[notificationId]
+        if (messageNotificationBuilder == null) {
+            messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_FILE_TRANSFER)
+        }
+        val ongoing = event == InteractionStatus.TRANSFER_ONGOING || event == InteractionStatus.TRANSFER_ACCEPTED
+        val titleMessage = mContext.getString(
+            if (info.isOutgoing) R.string.notif_outgoing_file_transfer_title else R.string.notif_incoming_file_transfer_title,
+            conversation.title
+        )
+        messageNotificationBuilder.setContentTitle(titleMessage)
+            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+            .setAutoCancel(false)
+            .setOngoing(ongoing)
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+            .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+            .setOnlyAlertOnce(true)
+            .setContentText(
+                if (event == InteractionStatus.TRANSFER_ONGOING)
+                    Formatter.formatFileSize(mContext, info.bytesProgress) + " / " + Formatter.formatFileSize(mContext, info.totalSize)
+                else
+                    info.displayName + ": " + ResourceMapper.getReadableFileTransferStatus(mContext, event)
+            )
+            .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
+            .color = ResourcesCompat.getColor(mContext.resources, R.color.color_primary_dark, null)
+        val picture = getContactPicture(conversation)
+        if (picture != null) messageNotificationBuilder.setLargeIcon(picture)
+        when {
+            event.isOver -> messageNotificationBuilder.setProgress(0, 0, false)
+            ongoing -> messageNotificationBuilder.setProgress(info.totalSize.toInt(), info.bytesProgress.toInt(), false)
+            else -> messageNotificationBuilder.setProgress(0, 0, true)
+        }
+        if (event == InteractionStatus.TRANSFER_CREATED) {
+            messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_VIBRATE)
+            mNotificationBuilders.put(notificationId, messageNotificationBuilder)
+            // updateNotification(messageNotificationBuilder.build(), notificationId);
+            return
+        } else {
+            messageNotificationBuilder.setDefaults(NotificationCompat.DEFAULT_LIGHTS)
+        }
+        messageNotificationBuilder.mActions.clear()
+        if (event == InteractionStatus.TRANSFER_AWAITING_HOST) {
+            messageNotificationBuilder
+                .addAction(R.drawable.baseline_call_received_24, mContext.getText(R.string.accept),
+                    PendingIntent.getService(mContext, random.nextInt(),
+                        Intent(DRingService.ACTION_FILE_ACCEPT, path, mContext, DRingService::class.java)
+                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId), PendingIntent.FLAG_ONE_SHOT))
+                .addAction(R.drawable.baseline_cancel_24, mContext.getText(R.string.refuse),
+                    PendingIntent.getService(mContext, random.nextInt(),
+                        Intent(DRingService.ACTION_FILE_CANCEL, path, mContext, DRingService::class.java)
+                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId), PendingIntent.FLAG_ONE_SHOT))
+            mNotificationBuilders.put(notificationId, messageNotificationBuilder)
+            updateNotification(messageNotificationBuilder.build(), notificationId)
+            return
+        } else if (!event.isOver) {
+            messageNotificationBuilder
+                .addAction(R.drawable.baseline_cancel_24, mContext.getText(android.R.string.cancel),
+                    PendingIntent.getService(mContext, random.nextInt(),
+                        Intent(DRingService.ACTION_FILE_CANCEL, path, mContext, DRingService::class.java)
+                            .putExtra(DRingService.KEY_TRANSFER_ID, dataTransferId), PendingIntent.FLAG_ONE_SHOT))
+        }
+        mNotificationBuilders.put(notificationId, messageNotificationBuilder)
+        dataTransferNotifications[notificationId] = messageNotificationBuilder.build()
+        ContextCompat.startForegroundService(mContext, Intent(DataTransferService.ACTION_START, path, mContext, DataTransferService::class.java)
+                .putExtra(NotificationService.KEY_NOTIFICATION_ID, notificationId))
+        //startForegroundService(notificationId, DataTransferService.class);
+    }
+
+    override fun showMissedCallNotification(call: Call) {
+        val notificationId = call.daemonIdString.hashCode()
+        var messageNotificationBuilder = mNotificationBuilders[notificationId]
+        if (messageNotificationBuilder == null) {
+            messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_MISSED_CALL)
+        }
+        val path = ConversationPath.toUri(call)
+        val intentConversation = Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService::class.java)
+        val contact = call.contact!!
+        messageNotificationBuilder.setContentTitle(mContext.getText(R.string.notif_missed_incoming_call))
+            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+            .setSmallIcon(R.drawable.baseline_call_missed_24)
+            .setCategory(NotificationCompat.CATEGORY_CALL)
+            .setOnlyAlertOnce(true)
+            .setAutoCancel(true)
+            .setContentText(contact.displayName)
+            .setContentIntent(PendingIntent.getService(mContext, random.nextInt(), intentConversation, 0))
+            .color = ResourcesCompat.getColor(mContext.resources, R.color.color_primary_dark, null)
+        setContactPicture(contact, messageNotificationBuilder)
+        notificationManager.notify(notificationId, messageNotificationBuilder.build())
+    }
+
+    override fun getServiceNotification(): Any {
+        val intentHome = Intent(Intent.ACTION_VIEW)
+            .setClass(mContext, HomeActivity::class.java)
+            .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        val pendIntent = PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT)
+        val messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_SERVICE)
+        messageNotificationBuilder
+            .setContentTitle(mContext.getText(R.string.app_name))
+            .setContentText(mContext.getText(R.string.notif_background_service))
+            .setSmallIcon(R.drawable.ic_ring_logo_white)
+            .setContentIntent(pendIntent)
+            .setVisibility(NotificationCompat.VISIBILITY_SECRET)
+            .setPriority(NotificationCompat.PRIORITY_MIN)
+            .setOngoing(true)
+            .setCategory(NotificationCompat.CATEGORY_SERVICE)
+        return messageNotificationBuilder.build()
+    }
+
+    override fun cancelTextNotification(accountId: String, contact: net.jami.model.Uri) {
+        val notificationId = getTextNotificationId(accountId, contact)
+        notificationManager.cancel(notificationId)
+        mNotificationBuilders.remove(notificationId)
+    }
+
+    override fun cancelTrustRequestNotification(accountID: String) {
+        val notificationId = getIncomingTrustNotificationId(accountID)
+        notificationManager.cancel(notificationId)
+    }
+
+    override fun cancelCallNotification() {
+        notificationManager.cancel(NOTIF_CALL_ID)
+        mNotificationBuilders.remove(NOTIF_CALL_ID)
+        callNotifications.clear()
+    }
+
+    /**\
+     * Cancels a notification
+     * @param notificationId the notification ID
+     * @param isMigratingToService true if the notification is being updated to be a part of the foreground service
+     */
+    override fun cancelFileNotification(notificationId: Int, isMigratingToService: Boolean) {
+        notificationManager.cancel(notificationId)
+        if (!isMigratingToService) mNotificationBuilders.remove(notificationId)
+    }
+
+    override fun cancelAll() {
+        notificationManager.cancelAll()
+        mNotificationBuilders.clear()
+    }
+
+    private fun getIncomingTrustNotificationId(accountId: String): Int {
+        return (NOTIF_TRUST_REQUEST + accountId).hashCode()
+    }
+
+    private fun getTextNotificationId(accountId: String, contact: net.jami.model.Uri): Int {
+        return (NOTIF_MSG + accountId + contact.toString()).hashCode()
+    }
+
+    private fun getFileTransferNotificationId(path: Uri, dataTransferId: String): Int {
+        return (NOTIF_FILE_TRANSFER + path.toString() + dataTransferId).hashCode()
+    }
+
+    private fun getContactPicture(contact: Contact): Bitmap? {
+        return try {
+            AvatarFactory.getBitmapAvatar(mContext, contact, avatarSize, false).blockingGet()
+        } catch (e: Exception) {
+            null
+        }
+    }
+
+    private fun getContactPicture(conversation: Conversation): Bitmap? {
+        return try {
+            AvatarFactory.getBitmapAvatar(mContext, conversation, avatarSize, false).blockingGet()
+        } catch (e: Exception) {
+            null
+        }
+    }
+
+    private fun getContactPicture(account: Account): Bitmap {
+        return AvatarFactory.getBitmapAvatar(mContext, account, avatarSize).blockingGet()
+    }
+
+    private fun getProfile(conversation: Conversation): Pair<Bitmap?, String> {
+        return Pair.create(getContactPicture(conversation), conversation.title)
+    }
+
+    private fun setContactPicture(
+        contact: Contact,
+        messageNotificationBuilder: NotificationCompat.Builder
+    ) {
+        val pic = getContactPicture(contact)
+        if (pic != null) messageNotificationBuilder.setLargeIcon(pic)
+    }
+
+    companion object {
+        const val EXTRA_BUBBLE = "bubble"
+        private val TAG = NotificationServiceImpl::class.java.simpleName
+        private const val NOTIF_MSG = "MESSAGE"
+        private const val NOTIF_TRUST_REQUEST = "TRUST REQUEST"
+        private const val NOTIF_FILE_TRANSFER = "FILE_TRANSFER"
+        private const val NOTIF_MISSED_CALL = "MISSED_CALL"
+        private const val NOTIF_CHANNEL_CALL_IN_PROGRESS = "current_call"
+        private const val NOTIF_CHANNEL_MISSED_CALL = "missed_calls"
+        private const val NOTIF_CHANNEL_INCOMING_CALL = "incoming_call"
+        private const val NOTIF_CHANNEL_MESSAGE = "messages"
+        private const val NOTIF_CHANNEL_REQUEST = "requests"
+        private const val NOTIF_CHANNEL_FILE_TRANSFER = "file_transfer"
+        const val NOTIF_CHANNEL_SYNC = "sync"
+        private const val NOTIF_CHANNEL_SERVICE = "service"
+        private const val NOTIF_CALL_GROUP = "calls"
+        const val NOTIF_CALL_ID = 1001
+
+        @RequiresApi(api = Build.VERSION_CODES.O)
+        fun registerNotificationChannels(context: Context) {
+            val notificationManager =
+                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+            // Setting up groups
+            notificationManager.createNotificationChannelGroup(
+                NotificationChannelGroup(
+                    NOTIF_CALL_GROUP, context.getString(R.string.notif_group_calls)
+                )
+            )
+
+            // Missed calls channel
+            val missedCallsChannel = NotificationChannel(
+                NOTIF_CHANNEL_MISSED_CALL,
+                context.getString(R.string.notif_channel_missed_calls),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            missedCallsChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            missedCallsChannel.setSound(null, null)
+            missedCallsChannel.enableVibration(false)
+            missedCallsChannel.group = NOTIF_CALL_GROUP
+            notificationManager.createNotificationChannel(missedCallsChannel)
+
+            // Incoming call channel
+            val incomingCallChannel = NotificationChannel(
+                NOTIF_CHANNEL_INCOMING_CALL,
+                context.getString(R.string.notif_channel_incoming_calls),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+            incomingCallChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+            incomingCallChannel.group = NOTIF_CALL_GROUP
+            incomingCallChannel.setSound(null, null)
+            incomingCallChannel.enableVibration(false)
+            notificationManager.createNotificationChannel(incomingCallChannel)
+
+            // Call in progress channel
+            val callInProgressChannel = NotificationChannel(
+                NOTIF_CHANNEL_CALL_IN_PROGRESS,
+                context.getString(R.string.notif_channel_call_in_progress),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            callInProgressChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
+            callInProgressChannel.setSound(null, null)
+            callInProgressChannel.enableVibration(false)
+            callInProgressChannel.group = NOTIF_CALL_GROUP
+            notificationManager.createNotificationChannel(callInProgressChannel)
+
+            // Text messages channel
+            val soundAttributes = AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
+                .build()
+            val messageChannel = NotificationChannel(
+                NOTIF_CHANNEL_MESSAGE,
+                context.getString(R.string.notif_channel_messages),
+                NotificationManager.IMPORTANCE_HIGH
+            )
+            messageChannel.enableVibration(true)
+            messageChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            messageChannel.setSound(
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+                soundAttributes
+            )
+            notificationManager.createNotificationChannel(messageChannel)
+
+            // Contact requests
+            val requestsChannel = NotificationChannel(
+                NOTIF_CHANNEL_REQUEST,
+                context.getString(R.string.notif_channel_requests),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            requestsChannel.enableVibration(true)
+            requestsChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            requestsChannel.setSound(
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+                soundAttributes
+            )
+            notificationManager.createNotificationChannel(requestsChannel)
+
+            // File transfer requests
+            val fileTransferChannel = NotificationChannel(
+                NOTIF_CHANNEL_FILE_TRANSFER,
+                context.getString(R.string.notif_channel_file_transfer),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            fileTransferChannel.enableVibration(true)
+            fileTransferChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            fileTransferChannel.setSound(
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
+                soundAttributes
+            )
+            notificationManager.createNotificationChannel(fileTransferChannel)
+
+            // File transfer requests
+            val syncChannel = NotificationChannel(
+                NOTIF_CHANNEL_SYNC,
+                context.getString(R.string.notif_channel_sync),
+                NotificationManager.IMPORTANCE_DEFAULT
+            )
+            syncChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            syncChannel.enableLights(false)
+            syncChannel.enableVibration(false)
+            syncChannel.setShowBadge(false)
+            syncChannel.setSound(null, null)
+            notificationManager.createNotificationChannel(syncChannel)
+
+            // Background service channel
+            val backgroundChannel = NotificationChannel(
+                NOTIF_CHANNEL_SERVICE,
+                context.getString(R.string.notif_channel_background_service),
+                NotificationManager.IMPORTANCE_LOW
+            )
+            backgroundChannel.description =
+                context.getString(R.string.notif_channel_background_service_descr)
+            backgroundChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
+            backgroundChannel.enableLights(false)
+            backgroundChannel.enableVibration(false)
+            backgroundChannel.setShowBadge(false)
+            notificationManager.createNotificationChannel(backgroundChannel)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
deleted file mode 100644
index 355717553..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatDelegate;
-import androidx.preference.PreferenceManager;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Settings;
-import net.jami.model.Uri;
-import net.jami.services.PreferencesService;
-
-import cx.ring.utils.DeviceUtils;
-import cx.ring.utils.NetworkUtils;
-
-public class SharedPreferencesServiceImpl extends PreferencesService {
-
-    public static final String PREFS_SETTINGS = "ring_settings";
-    private static final String PREFS_REQUESTS = "ring_requests";
-    public static final String PREFS_THEME = "theme";
-    public static final String PREFS_VIDEO = "videoPrefs";
-    public static final String PREFS_ACCOUNT = "account_";
-
-    private static final String PREF_PUSH_NOTIFICATIONS = "push_notifs";
-    private static final String PREF_PERSISTENT_NOTIFICATION = "persistent_notif";
-    private static final String PREF_SHOW_TYPING = "persistent_typing";
-    private static final String PREF_SHOW_READ = "persistent_read";
-    private static final String PREF_BLOCK_RECORD = "persistent_block_record";
-    private static final String PREF_NOTIFICATION_VISIBILITY = "persistent_notification";
-    private static final String PREF_HW_ENCODING = "video_hwenc";
-    public static final String PREF_BITRATE = "video_bitrate";
-    public static final String PREF_RESOLUTION = "video_resolution";
-    private static final String PREF_SYSTEM_CONTACTS = "system_contacts";
-    private static final String PREF_PLACE_CALLS = "place_calls";
-    private static final String PREF_ON_STARTUP = "on_startup";
-    public static final String PREF_DARK_MODE= "darkMode";
-    private  static final String PREF_ACCEPT_IN_MAX_SIZE = "acceptIncomingFilesMaxSize";
-    public static final String PREF_PLUGINS = "plugins";
-    private final Map<String, Set<String>> mNotifiedRequests = new HashMap<>();
-
-    @Inject
-    protected Context mContext;
-
-    @Override
-    protected void saveSettings(Settings settings) {
-        SharedPreferences appPrefs = getPreferences();
-        SharedPreferences.Editor edit = appPrefs.edit();
-        edit.clear();
-        edit.putBoolean(PREF_SYSTEM_CONTACTS, settings.isAllowSystemContacts());
-        edit.putBoolean(PREF_PLACE_CALLS, settings.isAllowPlaceSystemCalls());
-        edit.putBoolean(PREF_ON_STARTUP, settings.isAllowOnStartup());
-        edit.putBoolean(PREF_PUSH_NOTIFICATIONS, settings.isAllowPushNotifications());
-        edit.putBoolean(PREF_PERSISTENT_NOTIFICATION, settings.isAllowPersistentNotification());
-        edit.putBoolean(PREF_SHOW_TYPING, settings.isAllowTypingIndicator());
-        edit.putBoolean(PREF_SHOW_READ, settings.isAllowReadIndicator());
-        edit.putBoolean(PREF_BLOCK_RECORD, settings.isRecordingBlocked());
-        edit.putInt(PREF_NOTIFICATION_VISIBILITY, settings.getNotificationVisibility());
-        edit.apply();
-    }
-
-    @Override
-    protected Settings loadSettings() {
-        SharedPreferences appPrefs = getPreferences();
-        Settings settings = getUserSettings();
-        if (settings == null) {
-            settings = new Settings();
-        }
-        settings.setAllowSystemContacts(appPrefs.getBoolean(PREF_SYSTEM_CONTACTS, false));
-        settings.setAllowPlaceSystemCalls(appPrefs.getBoolean(PREF_PLACE_CALLS, false));
-        settings.setAllowRingOnStartup(appPrefs.getBoolean(PREF_ON_STARTUP, true));
-        settings.setAllowPushNotifications(appPrefs.getBoolean(PREF_PUSH_NOTIFICATIONS, false));
-        settings.setAllowPersistentNotification(appPrefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, false));
-        settings.setAllowTypingIndicator(appPrefs.getBoolean(PREF_SHOW_TYPING, true));
-        settings.setAllowReadIndicator(appPrefs.getBoolean(PREF_SHOW_READ, true));
-        settings.setBlockRecordIndicator(appPrefs.getBoolean(PREF_BLOCK_RECORD, false));
-        settings.setNotificationVisibility(appPrefs.getInt(PREF_NOTIFICATION_VISIBILITY, 0));
-        return settings;
-    }
-
-    private void saveRequests(String accountId, Set<String> requests) {
-        SharedPreferences preferences = mContext.getSharedPreferences(PREFS_REQUESTS, Context.MODE_PRIVATE);
-        SharedPreferences.Editor edit = preferences.edit();
-        edit.putStringSet(accountId, requests);
-        edit.apply();
-    }
-
-    @Override
-    public void saveRequestPreferences(String accountId, String contactId) {
-        Set<String> requests = loadRequestsPreferences(accountId);
-        requests.add(contactId);
-        saveRequests(accountId, requests);
-    }
-
-    @Override
-    @NonNull
-    public Set<String> loadRequestsPreferences(@NonNull String accountId) {
-        Set<String> requests = mNotifiedRequests.get(accountId);
-        if (requests == null) {
-            SharedPreferences preferences = mContext.getSharedPreferences(PREFS_REQUESTS, Context.MODE_PRIVATE);
-            requests = new HashSet<>(preferences.getStringSet(accountId, new HashSet<>()));
-            mNotifiedRequests.put(accountId, requests);
-        }
-        return requests;
-    }
-
-    @Override
-    public void removeRequestPreferences(String accountId, String contactId) {
-        Set<String> requests = loadRequestsPreferences(accountId);
-        requests.remove(contactId);
-        saveRequests(accountId, requests);
-    }
-
-    @Override
-    public boolean hasNetworkConnected() {
-        return NetworkUtils.isConnectivityAllowed(mContext);
-    }
-
-    @Override
-    public boolean isPushAllowed() {
-        String token = JamiApplication.getInstance().getPushToken();
-        return getSettings().isAllowPushNotifications() && !TextUtils.isEmpty(token) /*&& NetworkUtils.isPushAllowed(mContext, getSettings().isAllowMobileData())*/;
-    }
-
-    @Override
-    public int getResolution() {
-        return Integer.parseInt(getVideoPreferences().getString(PREF_RESOLUTION,
-                DeviceUtils.isTv(mContext)
-                        ? mContext.getString(R.string.video_resolution_default_tv)
-                        : mContext.getString(R.string.video_resolution_default)));
-    }
-
-    @Override
-    public int getBitrate() {
-        return Integer.parseInt(getVideoPreferences().getString(PREF_BITRATE, mContext.getString(R.string.video_bitrate_default)));
-    }
-
-    @Override
-    public boolean isHardwareAccelerationEnabled() {
-        return getVideoPreferences().getBoolean(PREF_HW_ENCODING, true);
-    }
-
-    @Override
-    public void setDarkMode(boolean enabled) {
-        SharedPreferences.Editor edit = getThemePreferences().edit();
-        edit.putBoolean(PREF_DARK_MODE, enabled)
-                .apply();
-        applyDarkMode(enabled);
-    }
-
-    @Override
-    public boolean getDarkMode() {
-        return getThemePreferences().getBoolean(PREF_DARK_MODE, false);
-    }
-
-    @Override
-    public void loadDarkMode() {
-        applyDarkMode(getDarkMode());
-    }
-
-    @Override
-    public int getMaxFileAutoAccept(String accountId) {
-        return mContext.getSharedPreferences(PREFS_ACCOUNT+accountId, Context.MODE_PRIVATE)
-                .getInt(PREF_ACCEPT_IN_MAX_SIZE, 30) * 1024 * 1024;
-	}
-
-	public static SharedPreferences getConversationPreferences(@NonNull Context context, String accountId, Uri conversationUri) {
-        return context.getSharedPreferences(accountId + "_" + conversationUri.getUri(), Context.MODE_PRIVATE);
-    }
-
-    private void applyDarkMode(boolean enabled) {
-        AppCompatDelegate.setDefaultNightMode(
-                enabled ? AppCompatDelegate.MODE_NIGHT_YES
-                        : Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
-                            ? AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
-                            : AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
-    }
-
-    private SharedPreferences getPreferences() {
-        return mContext.getSharedPreferences(PREFS_SETTINGS, Context.MODE_PRIVATE);
-    }
-
-    private SharedPreferences getVideoPreferences() {
-        return mContext.getSharedPreferences(PREFS_VIDEO, Context.MODE_PRIVATE);
-    }
-
-    private SharedPreferences getThemePreferences() {
-        return PreferenceManager.getDefaultSharedPreferences(mContext);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt
new file mode 100644
index 000000000..225893201
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt
@@ -0,0 +1,204 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.content.Context
+import net.jami.services.PreferencesService
+import java.util.HashMap
+import android.content.SharedPreferences
+import java.util.HashSet
+import cx.ring.utils.NetworkUtils
+import cx.ring.application.JamiApplication
+import android.text.TextUtils
+import cx.ring.utils.DeviceUtils
+import cx.ring.R
+import androidx.appcompat.app.AppCompatDelegate
+import android.os.Build
+import androidx.preference.PreferenceManager
+import net.jami.model.Settings
+import net.jami.model.Uri
+import net.jami.services.AccountService
+import net.jami.services.DeviceRuntimeService
+
+class SharedPreferencesServiceImpl(val mContext: Context, accountService: AccountService, deviceService: DeviceRuntimeService) : PreferencesService(accountService, deviceService) {
+    private val mNotifiedRequests: MutableMap<String, MutableSet<String>> = HashMap()
+
+    override fun saveSettings(settings: Settings) {
+        val appPrefs = preferences
+        val edit = appPrefs.edit()
+        edit.clear()
+        edit.putBoolean(PREF_SYSTEM_CONTACTS, settings.isAllowSystemContacts)
+        edit.putBoolean(PREF_PLACE_CALLS, settings.isAllowPlaceSystemCalls)
+        edit.putBoolean(PREF_ON_STARTUP, settings.isAllowOnStartup)
+        edit.putBoolean(PREF_PUSH_NOTIFICATIONS, settings.isAllowPushNotifications)
+        edit.putBoolean(PREF_PERSISTENT_NOTIFICATION, settings.isAllowPersistentNotification)
+        edit.putBoolean(PREF_SHOW_TYPING, settings.isAllowTypingIndicator)
+        edit.putBoolean(PREF_SHOW_READ, settings.isAllowReadIndicator)
+        edit.putBoolean(PREF_BLOCK_RECORD, settings.isRecordingBlocked)
+        edit.putInt(PREF_NOTIFICATION_VISIBILITY, settings.notificationVisibility)
+        edit.apply()
+    }
+
+    override fun loadSettings(): Settings {
+        val appPrefs = preferences
+        val settings = userSettings ?: Settings()
+        settings.isAllowSystemContacts =
+            appPrefs.getBoolean(PREF_SYSTEM_CONTACTS, false)
+        settings.isAllowPlaceSystemCalls =
+            appPrefs.getBoolean(PREF_PLACE_CALLS, false)
+        settings.setAllowRingOnStartup(appPrefs.getBoolean(PREF_ON_STARTUP, true))
+        settings.isAllowPushNotifications =
+            appPrefs.getBoolean(PREF_PUSH_NOTIFICATIONS, false)
+        settings.isAllowPersistentNotification = appPrefs.getBoolean(
+            PREF_PERSISTENT_NOTIFICATION,
+            false
+        )
+        settings.isAllowTypingIndicator =
+            appPrefs.getBoolean(PREF_SHOW_TYPING, true)
+        settings.isAllowReadIndicator =
+            appPrefs.getBoolean(PREF_SHOW_READ, true)
+        settings.setBlockRecordIndicator(appPrefs.getBoolean(PREF_BLOCK_RECORD, false))
+        settings.notificationVisibility =
+            appPrefs.getInt(PREF_NOTIFICATION_VISIBILITY, 0)
+        return settings
+    }
+
+    private fun saveRequests(accountId: String, requests: Set<String>) {
+        val preferences = mContext.getSharedPreferences(PREFS_REQUESTS, Context.MODE_PRIVATE)
+        val edit = preferences.edit()
+        edit.putStringSet(accountId, requests)
+        edit.apply()
+    }
+
+    override fun saveRequestPreferences(accountId: String, contactId: String) {
+        val requests = loadRequestsPreferences(accountId)
+        requests.add(contactId)
+        saveRequests(accountId, requests)
+    }
+
+    override fun loadRequestsPreferences(accountId: String): MutableSet<String> {
+        var requests = mNotifiedRequests[accountId]
+        if (requests == null) {
+            val preferences = mContext.getSharedPreferences(PREFS_REQUESTS, Context.MODE_PRIVATE)
+            requests = HashSet(preferences.getStringSet(accountId, null) ?: HashSet())
+            mNotifiedRequests[accountId] = requests
+        }
+        return requests
+    }
+
+    override fun removeRequestPreferences(accountId: String, contactId: String) {
+        val requests = loadRequestsPreferences(accountId)
+        requests.remove(contactId)
+        saveRequests(accountId, requests)
+    }
+
+    override fun hasNetworkConnected(): Boolean {
+        return NetworkUtils.isConnectivityAllowed(mContext)
+    }
+
+    override fun isPushAllowed(): Boolean {
+        val token = JamiApplication.instance?.pushToken
+        return settings.isAllowPushNotifications && !TextUtils.isEmpty(token) /*&& NetworkUtils.isPushAllowed(mContext, getSettings().isAllowMobileData())*/
+    }
+
+    override fun getResolution(): Int {
+        return videoPreferences.getString(
+            PREF_RESOLUTION,
+            if (DeviceUtils.isTv(mContext)) mContext.getString(R.string.video_resolution_default_tv)
+            else mContext.getString(R.string.video_resolution_default)
+        )!!.toInt()
+    }
+
+    override fun getBitrate(): Int {
+        return videoPreferences.getString(
+            PREF_BITRATE,
+            mContext.getString(R.string.video_bitrate_default)
+        )!!.toInt()
+    }
+
+    override fun isHardwareAccelerationEnabled(): Boolean {
+        return videoPreferences.getBoolean(PREF_HW_ENCODING, true)
+    }
+
+    override fun setDarkMode(enabled: Boolean) {
+        val edit = themePreferences.edit()
+        edit.putBoolean(PREF_DARK_MODE, enabled)
+            .apply()
+        applyDarkMode(enabled)
+    }
+
+    override fun getDarkMode(): Boolean {
+        return themePreferences.getBoolean(PREF_DARK_MODE, false)
+    }
+
+    override fun loadDarkMode() {
+        applyDarkMode(darkMode)
+    }
+
+    override fun getMaxFileAutoAccept(accountId: String): Int {
+        return mContext.getSharedPreferences(PREFS_ACCOUNT + accountId, Context.MODE_PRIVATE)
+            .getInt(PREF_ACCEPT_IN_MAX_SIZE, 30) * 1024 * 1024
+    }
+
+    private fun applyDarkMode(enabled: Boolean) {
+        AppCompatDelegate.setDefaultNightMode(
+            if (enabled) AppCompatDelegate.MODE_NIGHT_YES else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM else AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
+        )
+    }
+
+    private val preferences: SharedPreferences = mContext.getSharedPreferences(PREFS_SETTINGS, Context.MODE_PRIVATE)
+    private val videoPreferences: SharedPreferences = mContext.getSharedPreferences(PREFS_VIDEO, Context.MODE_PRIVATE)
+    private val themePreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext)
+
+    companion object {
+        const val PREFS_SETTINGS = "ring_settings"
+        private const val PREFS_REQUESTS = "ring_requests"
+        const val PREFS_THEME = "theme"
+        const val PREFS_VIDEO = "videoPrefs"
+        const val PREFS_ACCOUNT = "account_"
+        private const val PREF_PUSH_NOTIFICATIONS = "push_notifs"
+        private const val PREF_PERSISTENT_NOTIFICATION = "persistent_notif"
+        private const val PREF_SHOW_TYPING = "persistent_typing"
+        private const val PREF_SHOW_READ = "persistent_read"
+        private const val PREF_BLOCK_RECORD = "persistent_block_record"
+        private const val PREF_NOTIFICATION_VISIBILITY = "persistent_notification"
+        private const val PREF_HW_ENCODING = "video_hwenc"
+        const val PREF_BITRATE = "video_bitrate"
+        const val PREF_RESOLUTION = "video_resolution"
+        private const val PREF_SYSTEM_CONTACTS = "system_contacts"
+        private const val PREF_PLACE_CALLS = "place_calls"
+        private const val PREF_ON_STARTUP = "on_startup"
+        const val PREF_DARK_MODE = "darkMode"
+        private const val PREF_ACCEPT_IN_MAX_SIZE = "acceptIncomingFilesMaxSize"
+        const val PREF_PLUGINS = "plugins"
+
+        @JvmStatic fun getConversationPreferences(
+            context: Context,
+            accountId: String,
+            conversationUri: Uri
+        ): SharedPreferences {
+            return context.getSharedPreferences(
+                accountId + "_" + conversationUri.uri,
+                Context.MODE_PRIVATE
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.java
deleted file mode 100644
index f7e860f4d..000000000
--- a/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.services;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.Base64;
-
-import androidx.annotation.NonNull;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-
-import net.jami.model.Account;
-import cx.ring.utils.BitmapUtils;
-
-import net.jami.services.VCardService;
-import net.jami.utils.Tuple;
-import net.jami.utils.VCardUtils;
-import ezvcard.VCard;
-import ezvcard.parameter.ImageType;
-import ezvcard.property.Photo;
-import ezvcard.property.RawProperty;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class VCardServiceImpl extends VCardService {
-
-    private final Context mContext;
-
-    public VCardServiceImpl(Context context) {
-        this.mContext = context;
-    }
-
-    public static Single<Tuple<String, Object>> loadProfile(@NonNull Context context, @NonNull Account account) {
-        synchronized (account) {
-            Single<Tuple<String, Object>> ret = account.getLoadedProfile();
-            if (ret == null) {
-                ret = VCardUtils.loadLocalProfileFromDiskWithDefault(context.getFilesDir(), account.getAccountID())
-                        .map(VCardServiceImpl::readData)
-                        .subscribeOn(Schedulers.computation())
-                        .cache();
-                account.setLoadedProfile(ret);
-            }
-            return ret;
-        }
-    }
-
-    @Override
-    public Single<Tuple<String, Object>> loadProfile(@NonNull Account account) {
-        return loadProfile(mContext, account);
-    }
-
-    @Override
-    public Maybe<VCard> loadSmallVCard(String accountId, int maxSize) {
-        return VCardUtils.loadLocalProfileFromDisk(mContext.getFilesDir(), accountId)
-                .filter(vcard -> !VCardUtils.isEmpty(vcard))
-                .map(vcard -> {
-                    if (!vcard.getPhotos().isEmpty()) {
-                        // Reduce photo to fit in maxSize, assuming JPEG compress with ratio of at least 8
-                        byte[] data = vcard.getPhotos().get(0).getData();
-                        Bitmap photo = BitmapUtils.bytesToBitmap(data, maxSize * 8);
-                        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-                        photo.compress(Bitmap.CompressFormat.JPEG, 88, stream);
-                        vcard.removeProperties(Photo.class);
-                        vcard.addPhoto(new Photo(stream.toByteArray(), ImageType.JPEG));
-                    }
-                    vcard.removeProperties(RawProperty.class);
-                    return vcard;
-                });
-    }
-
-    @Override
-    public Single<VCard> saveVCardProfile(String accountId, String uri, String displayName, String picture)
-    {
-        return Single.fromCallable(() -> VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT)))
-                .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, mContext.getFilesDir()));
-    }
-
-    @Override
-    public Single<Tuple<String, Object>> loadVCardProfile(VCard vcard) {
-        return Single.fromCallable(() -> readData(vcard));
-    }
-
-    @Override
-    public Single<Tuple<String, Object>> peerProfileReceived(String accountId, String peerId, File vcard)
-    {
-        return VCardUtils.peerProfileReceived(mContext.getFilesDir(), accountId, peerId, vcard)
-                .map(VCardServiceImpl::readData);
-    }
-
-    public static Tuple<String, Object> readData(VCard vcard) {
-        return readData(VCardUtils.readData(vcard));
-    }
-
-    public static Tuple<String, Object> readData(Tuple<String, byte[]> profile) {
-        return new Tuple<>(profile.first, BitmapUtils.bytesToBitmap(profile.second));
-    }
-
-    @Override
-    public Object base64ToBitmap(String base64) {
-        return BitmapUtils.base64ToBitmap(base64);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
new file mode 100644
index 000000000..9ee35c4ff
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt
@@ -0,0 +1,105 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.services
+
+import android.content.Context
+import net.jami.services.VCardService
+import ezvcard.VCard
+import net.jami.utils.VCardUtils
+import android.graphics.Bitmap
+import android.util.Base64
+import cx.ring.utils.BitmapUtils
+import ezvcard.parameter.ImageType
+import ezvcard.property.Photo
+import java.io.ByteArrayOutputStream
+import ezvcard.property.RawProperty
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Account
+import net.jami.utils.Tuple
+import java.io.File
+
+class VCardServiceImpl(private val mContext: Context) : VCardService() {
+    override fun loadProfile(account: Account): Observable<Tuple<String?, Any?>> {
+        return loadProfile(mContext, account)
+    }
+
+    override fun loadSmallVCard(accountId: String, maxSize: Int): Maybe<VCard> {
+        return VCardUtils.loadLocalProfileFromDisk(mContext.filesDir, accountId)
+            .filter { vcard: VCard -> !VCardUtils.isEmpty(vcard) }
+            .map { vcard: VCard ->
+                if (vcard.photos.isNotEmpty()) {
+                    // Reduce photo to fit in maxSize, assuming JPEG compress with ratio of at least 8
+                    val data = vcard.photos[0].data
+                    val photo = BitmapUtils.bytesToBitmap(data, maxSize * 8)
+                    val stream = ByteArrayOutputStream()
+                    photo.compress(Bitmap.CompressFormat.JPEG, 88, stream)
+                    vcard.removeProperties(Photo::class.java)
+                    vcard.addPhoto(Photo(stream.toByteArray(), ImageType.JPEG))
+                }
+                vcard.removeProperties(RawProperty::class.java)
+                vcard
+            }
+    }
+
+    override fun saveVCardProfile(accountId: String, uri: String, displayName: String, picture: String): Single<VCard> {
+        return Single.fromCallable { VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT)) }
+            .flatMap { vcard: VCard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, mContext.filesDir) }
+    }
+
+    override fun loadVCardProfile(vcard: VCard): Single<Tuple<String?, Any?>> {
+        return Single.fromCallable { readData(vcard) }
+    }
+
+    override fun peerProfileReceived(accountId: String, peerId: String, vcardFile: File): Single<Tuple<String?, Any?>> {
+        return VCardUtils.peerProfileReceived(mContext.filesDir, accountId, peerId, vcardFile)
+            .map { vcard -> readData(vcard) }
+    }
+
+    override fun base64ToBitmap(base64: String): Any? {
+        return BitmapUtils.base64ToBitmap(base64)
+    }
+
+    companion object {
+        fun loadProfile(context: Context, account: Account): Observable<Tuple<String?, Any?>> {
+            synchronized(account) {
+                var ret = account.loadedProfile
+                if (ret == null) {
+                    ret = VCardUtils.loadLocalProfileFromDiskWithDefault(context.filesDir, account.accountID)
+                        .map { vcard: VCard -> readData(vcard) }
+                        .subscribeOn(Schedulers.computation())
+                        .cache()
+                    account.loadedProfile = ret
+                }
+                return account.loadedProfileObservable
+            }
+        }
+
+        fun readData(vcard: VCard?): Tuple<String?, Any?> {
+            return readData(VCardUtils.readData(vcard))
+        }
+
+        fun readData(profile: Tuple<String?, ByteArray?>): Tuple<String?, Any?> {
+            return Tuple(profile.first, BitmapUtils.bytesToBitmap(profile.second))
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java b/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java
index b712255c6..100c966ef 100644
--- a/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java
@@ -40,13 +40,15 @@ import cx.ring.account.JamiAccountSummaryFragment;
 import cx.ring.application.JamiApplication;
 import cx.ring.client.HomeActivity;
 import cx.ring.databinding.FragAccountBinding;
+import dagger.hilt.android.AndroidEntryPoint;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
 
 import net.jami.services.AccountService;
 
+@AndroidEntryPoint
 public class AccountFragment extends Fragment implements ViewTreeObserver.OnScrollChangedListener {
-
+    public static final String TAG = AccountFragment.class.getSimpleName();
     private static final int SCROLL_DIRECTION_UP = -1;
 
     public static AccountFragment newInstance(@NonNull String accountId) {
@@ -67,7 +69,6 @@ public class AccountFragment extends Fragment implements ViewTreeObserver.OnScro
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         mBinding = FragAccountBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return mBinding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
index df685ca7a..eeacca004 100644
--- a/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/settings/SettingsFragment.java
@@ -48,13 +48,16 @@ import cx.ring.databinding.FragSettingsBinding;
 import net.jami.daemon.JamiService;
 import net.jami.model.Settings;
 import cx.ring.mvp.BaseSupportFragment;
+import dagger.hilt.android.AndroidEntryPoint;
+
 import net.jami.mvp.GenericView;
 import net.jami.settings.SettingsPresenter;
 
 /**
  * TODO: improvements : handle multiples permissions for feature.
  */
-public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> implements GenericView<Settings>, ViewTreeObserver.OnScrollChangedListener {
+@AndroidEntryPoint
+public class SettingsFragment extends BaseSupportFragment<SettingsPresenter, GenericView<Settings>> implements GenericView<Settings>, ViewTreeObserver.OnScrollChangedListener {
 
     private static final int SCROLL_DIRECTION_UP = -1;
 
@@ -72,7 +75,6 @@ public class SettingsFragment extends BaseSupportFragment<SettingsPresenter> imp
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         binding = FragSettingsBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java
index 7da0c7b77..8f724edfb 100644
--- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java
+++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java
@@ -37,23 +37,20 @@ import cx.ring.utils.AndroidFileUtils;
 
 public class PathListAdapter extends RecyclerView.Adapter<PathListAdapter.PathViewHolder> {
     private List<String> mList;
-    private PathListItemListener listener;
-    private Drawable icon;
+    private PathListItemListener mListener;
     public static final String TAG = PathListAdapter.class.getSimpleName();
 
     PathListAdapter(List<String> pathList, PathListItemListener listener) {
-        this.mList = pathList;
-        this.listener = listener;
+        mList = pathList;
+        mListener = listener;
     }
 
-
     @NonNull
     @Override
     public PathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
         View view = LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.frag_path_list_item, parent, false);
-
-        return new PathViewHolder(view, listener);
+        return new PathViewHolder(view, mListener);
     }
 
     @Override
@@ -93,7 +90,7 @@ public class PathListAdapter extends RecyclerView.Adapter<PathListAdapter.PathVi
             if (file.exists()) {
                 if (AndroidFileUtils.isImage(s)) {
                     pathTextView.setVisibility(View.GONE);
-                    icon = Drawable.createFromPath(s);
+                    Drawable icon = Drawable.createFromPath(s);
                     if (icon != null) {
                         pathIcon.setImageDrawable(icon);
                     }
diff --git a/ring-android/app/src/main/java/cx/ring/share/ScanFragment.java b/ring-android/app/src/main/java/cx/ring/share/ScanFragment.java
deleted file mode 100644
index c629964f5..000000000
--- a/ring-android/app/src/main/java/cx/ring/share/ScanFragment.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *           Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.share;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.google.zxing.BarcodeFormat;
-import com.google.zxing.ResultPoint;
-import com.journeyapps.barcodescanner.BarcodeCallback;
-import com.journeyapps.barcodescanner.BarcodeResult;
-import com.journeyapps.barcodescanner.DecoratedBarcodeView;
-import com.journeyapps.barcodescanner.DefaultDecoderFactory;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.HomeActivity;
-import cx.ring.fragments.ConversationFragment;
-import cx.ring.fragments.QRCodeFragment;
-import cx.ring.mvp.BaseSupportFragment;
-
-public class ScanFragment extends BaseSupportFragment {
-    public static final String TAG = ScanFragment.class.getSimpleName();
-
-    private DecoratedBarcodeView barcodeView;
-    private TextView mErrorMessageTextView;
-
-    private boolean hasCameraPermission() {
-        return ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        View rootView = inflater.inflate(R.layout.frag_scan, container, false);
-
-        barcodeView = rootView.findViewById(R.id.barcode_scanner);
-        mErrorMessageTextView = rootView.findViewById(R.id.error_msg_txt);
-
-        if (hasCameraPermission()) {
-            hideErrorPanel();
-            initializeBarcode();
-        }
-
-        return rootView;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (checkPermission() && barcodeView != null) {
-            barcodeView.resume();
-        }
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        if (hasCameraPermission() && barcodeView != null) {
-            barcodeView.pause();
-        }
-    }
-
-    private void showErrorPanel(final int textResId) {
-        if (mErrorMessageTextView != null) {
-            mErrorMessageTextView.setText(textResId);
-            mErrorMessageTextView.setVisibility(View.VISIBLE);
-        }
-        if (barcodeView != null) {
-            barcodeView.setVisibility(View.GONE);
-        }
-    }
-
-    private void hideErrorPanel() {
-        if (mErrorMessageTextView != null) {
-            mErrorMessageTextView.setVisibility(View.GONE);
-        }
-        if (barcodeView != null) {
-            barcodeView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void displayNoPermissionsError() {
-        showErrorPanel(R.string.error_scan_no_camera_permissions);
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        for (int i = 0, n = permissions.length; i < n; i++) {
-            switch (permissions[i]) {
-                case Manifest.permission.CAMERA:
-                    boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
-                    if (granted) {
-                        hideErrorPanel();
-                        initializeBarcode();
-                    } else {
-                        displayNoPermissionsError();
-                    }
-                    return;
-                default:
-                    break;
-            }
-        }
-    }
-    private void initializeBarcode() {
-        if (barcodeView != null) {
-            barcodeView.getBarcodeView().setDecoderFactory(new DefaultDecoderFactory(Collections.singletonList(BarcodeFormat.QR_CODE)));
-            //barcodeView.initializeFromIntent(getActivity().getIntent());
-            barcodeView.decodeContinuous(callback);
-        }
-    }
-
-    private final BarcodeCallback callback = new BarcodeCallback() {
-        @Override
-        public void barcodeResult(@NonNull BarcodeResult result) {
-            if (result.getText() != null) {
-                String contactUri = result.getText();
-                if (contactUri != null) {
-                    QRCodeFragment parent = (QRCodeFragment) getParentFragment();
-                    if (parent != null) {
-                        parent.dismiss();
-                    }
-                    goToConversation(contactUri);
-                }
-            }
-        }
-
-        @Override
-        public void possibleResultPoints(List<ResultPoint> resultPoints) {
-        }
-    };
-
-    private void goToConversation(String conversationUri) {
-        ((HomeActivity) requireActivity()).startConversation(conversationUri);
-    }
-
-    private boolean checkPermission() {
-        if (!hasCameraPermission()) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                requestPermissions(new String[]{Manifest.permission.CAMERA}, JamiApplication.PERMISSIONS_REQUEST);
-            } else {
-                displayNoPermissionsError();
-            }
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/share/ScanFragment.kt b/ring-android/app/src/main/java/cx/ring/share/ScanFragment.kt
new file mode 100644
index 000000000..afd944ee2
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/share/ScanFragment.kt
@@ -0,0 +1,180 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *           Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.share
+
+import android.Manifest
+import com.journeyapps.barcodescanner.DecoratedBarcodeView
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import android.content.pm.PackageManager
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.os.Bundle
+import cx.ring.R
+import androidx.annotation.StringRes
+import com.journeyapps.barcodescanner.DefaultDecoderFactory
+import com.google.zxing.BarcodeFormat
+import com.journeyapps.barcodescanner.BarcodeCallback
+import com.journeyapps.barcodescanner.BarcodeResult
+import cx.ring.fragments.QRCodeFragment
+import com.google.zxing.ResultPoint
+import android.os.Build
+import android.view.View
+import androidx.fragment.app.Fragment
+import cx.ring.application.JamiApplication
+import cx.ring.client.HomeActivity
+
+class ScanFragment : Fragment() {
+    private var barcodeView: DecoratedBarcodeView? = null
+    private var mErrorMessageTextView: TextView? = null
+    private fun hasCameraPermission(): Boolean {
+        return ContextCompat.checkSelfPermission(
+            requireContext(),
+            Manifest.permission.CAMERA
+        ) == PackageManager.PERMISSION_GRANTED
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        val rootView = inflater.inflate(R.layout.frag_scan, container, false)
+        barcodeView = rootView.findViewById(R.id.barcode_scanner)
+        mErrorMessageTextView = rootView.findViewById(R.id.error_msg_txt)
+        if (hasCameraPermission()) {
+            hideErrorPanel()
+            initializeBarcode()
+        }
+        return rootView
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (checkPermission() && barcodeView != null) {
+            barcodeView!!.resume()
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        if (hasCameraPermission() && barcodeView != null) {
+            barcodeView!!.pause()
+        }
+    }
+
+    private fun showErrorPanel(@StringRes textResId: Int) {
+        if (mErrorMessageTextView != null) {
+            mErrorMessageTextView!!.setText(textResId)
+            mErrorMessageTextView!!.visibility = View.VISIBLE
+        }
+        if (barcodeView != null) {
+            barcodeView!!.visibility = View.GONE
+        }
+    }
+
+    private fun hideErrorPanel() {
+        if (mErrorMessageTextView != null) {
+            mErrorMessageTextView!!.visibility = View.GONE
+        }
+        if (barcodeView != null) {
+            barcodeView!!.visibility = View.VISIBLE
+        }
+    }
+
+    private fun displayNoPermissionsError() {
+        showErrorPanel(R.string.error_scan_no_camera_permissions)
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        var i = 0
+        val n = permissions.size
+        while (i < n) {
+            when (permissions[i]) {
+                Manifest.permission.CAMERA -> {
+                    val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+                    if (granted) {
+                        hideErrorPanel()
+                        initializeBarcode()
+                    } else {
+                        displayNoPermissionsError()
+                    }
+                    return
+                }
+                else -> {
+                }
+            }
+            i++
+        }
+    }
+
+    private fun initializeBarcode() {
+        if (barcodeView != null) {
+            barcodeView!!.barcodeView.decoderFactory =
+                DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
+            //barcodeView.initializeFromIntent(getActivity().getIntent());
+            barcodeView!!.decodeContinuous(callback)
+        }
+    }
+
+    private val callback: BarcodeCallback = object : BarcodeCallback {
+        override fun barcodeResult(result: BarcodeResult) {
+            if (result.text != null) {
+                val contactUri = result.text
+                if (contactUri != null) {
+                    val parent = parentFragment as QRCodeFragment?
+                    parent?.dismiss()
+                    goToConversation(contactUri)
+                }
+            }
+        }
+
+        override fun possibleResultPoints(resultPoints: List<ResultPoint>) {}
+    }
+
+    private fun goToConversation(conversationUri: String) {
+        (requireActivity() as HomeActivity).startConversation(conversationUri)
+    }
+
+    private fun checkPermission(): Boolean {
+        if (!hasCameraPermission()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                requestPermissions(
+                    arrayOf(Manifest.permission.CAMERA),
+                    JamiApplication.PERMISSIONS_REQUEST
+                )
+            } else {
+                displayNoPermissionsError()
+            }
+            return false
+        }
+        return true
+    }
+
+    companion object {
+        val TAG = ScanFragment::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java b/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java
index 8749cd56a..997bb7dec 100644
--- a/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java
@@ -36,11 +36,12 @@ import net.jami.share.ShareViewModel;
 import net.jami.utils.QRCodeUtils;
 
 import cx.ring.R;
-import cx.ring.application.JamiApplication;
 import cx.ring.databinding.FragShareBinding;
 import cx.ring.mvp.BaseSupportFragment;
+import dagger.hilt.android.AndroidEntryPoint;
 
-public class ShareFragment extends BaseSupportFragment<SharePresenter> implements GenericView<ShareViewModel> {
+@AndroidEntryPoint
+public class ShareFragment extends BaseSupportFragment<SharePresenter, GenericView<ShareViewModel>> implements GenericView<ShareViewModel> {
 
     private String mUriToShow;
     private boolean isShareLocked = false;
@@ -50,7 +51,6 @@ public class ShareFragment extends BaseSupportFragment<SharePresenter> implement
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         binding = FragShareBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         return binding.getRoot();
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/about/AboutActivity.java b/ring-android/app/src/main/java/cx/ring/tv/about/AboutActivity.java
deleted file mode 100644
index d41337d1a..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/about/AboutActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.about;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import cx.ring.R;
-
-public class AboutActivity extends Activity {
-
-    public static final String TAG = AboutActivity.class.getSimpleName();
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.tv_activity_about);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java b/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java
index a4710e3e1..7207eabf5 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java
@@ -17,11 +17,12 @@
  */
 package cx.ring.tv.about;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.BitmapFactory;
 import android.os.Bundle;
-import androidx.leanback.app.DetailsFragment;
-import androidx.leanback.app.DetailsFragmentBackgroundController;
+import androidx.leanback.app.DetailsSupportFragment;
+import androidx.leanback.app.DetailsSupportFragmentBackgroundController;
 import androidx.leanback.widget.ArrayObjectAdapter;
 import androidx.leanback.widget.ClassPresenterSelector;
 import androidx.leanback.widget.DetailsOverviewRow;
@@ -38,10 +39,10 @@ import cx.ring.tv.cards.iconcards.IconCard;
 import cx.ring.tv.cards.iconcards.IconCardHelper;
 import net.jami.utils.Log;
 
-public class AboutDetailsFragment extends DetailsFragment {
+public class AboutDetailsFragment extends DetailsSupportFragment {
     private static final String TAG = "AboutDetailsFragment";
-    private final DetailsFragmentBackgroundController mDetailsBackground =
-            new DetailsFragmentBackgroundController(this);
+    private final DetailsSupportFragmentBackgroundController mDetailsBackground =
+            new DetailsSupportFragmentBackgroundController(this);
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -58,26 +59,23 @@ public class AboutDetailsFragment extends DetailsFragment {
             cardType = Card.Type.values()[ordinal];
         }
 
-        IconCard card = IconCardHelper.getAboutCardByType(getActivity(), cardType);
+        Context context = requireContext();
+        IconCard card = IconCardHelper.getAboutCardByType(context, cardType);
 
         ClassPresenterSelector selector = new ClassPresenterSelector();
 
         FullWidthDetailsOverviewRowPresenter rowPresenter = new FullWidthDetailsOverviewRowPresenter(
-                new AboutDetailsPresenter(getActivity())) {
-
+                new AboutDetailsPresenter(context)) {
             @Override
             protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
                 // Customize Actionbar and Content by using custom colors.
                 RowPresenter.ViewHolder viewHolder = super.createRowViewHolder(parent);
 
-                View actionsView = viewHolder.view.
-                        findViewById(R.id.details_overview_actions_background);
-                actionsView.setBackgroundColor(getActivity().getResources().
-                        getColor(R.color.color_primary_dark));
+                View actionsView = viewHolder.view.findViewById(R.id.details_overview_actions_background);
+                actionsView.setBackgroundColor(getResources().getColor(R.color.color_primary_dark));
 
                 View detailsView = viewHolder.view.findViewById(R.id.details_frame);
-                detailsView.setBackgroundColor(
-                        getResources().getColor(R.color.color_primary_dark));
+                detailsView.setBackgroundColor(getResources().getColor(R.color.color_primary_dark));
                 return viewHolder;
             }
         };
@@ -86,7 +84,7 @@ public class AboutDetailsFragment extends DetailsFragment {
                 new ListRowPresenter());
         ArrayObjectAdapter mRowsAdapter = new ArrayObjectAdapter(selector);
 
-        Resources res = getActivity().getResources();
+        Resources res = getResources();
         DetailsOverviewRow detailsOverview = new DetailsOverviewRow(
                 card);
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
deleted file mode 100644
index db96ff525..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Loïc Siret <loic.siret@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.account;
-
-import android.app.AlertDialog;
-import android.app.DownloadManager;
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.leanback.widget.GuidanceStylist;
-import androidx.leanback.widget.GuidedAction;
-
-import android.text.Layout;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.AlignmentSpan;
-import android.text.style.RelativeSizeSpan;
-import android.text.style.StyleSpan;
-import android.view.View;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-import cx.ring.R;
-import net.jami.account.JamiAccountSummaryPresenter;
-import net.jami.account.JamiAccountSummaryView;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Account;
-import cx.ring.utils.AndroidFileUtils;
-
-public class TVAccountExport extends JamiGuidedStepFragment<JamiAccountSummaryPresenter>
-        implements JamiAccountSummaryView {
-
-    private static final long PASSWORD = 1L;
-    private static final long ACTION = 2L;
-
-    private ProgressDialog mWaitDialog;
-    private String mIdAccount;
-    private boolean mHasPassword;
-
-    public static TVAccountExport createInstance(String idAccount, boolean hasPassword) {
-        TVAccountExport fragment = new TVAccountExport();
-        fragment.mIdAccount = idAccount;
-        fragment.mHasPassword = hasPassword;
-        return fragment;
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-
-        super.onViewCreated(view, savedInstanceState);
-        presenter.setAccountId(mIdAccount);
-    }
-
-    @Override
-    @NonNull
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        String title = getString(R.string.account_export_title);
-        String breadcrumb = "";
-        String description = getString(R.string.account_link_export_info_light);
-        Drawable icon = getContext().getDrawable(R.drawable.baseline_devices_24);
-        return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        if (mHasPassword) {
-            addPasswordAction(getActivity(), actions, PASSWORD, getString(R.string.account_enter_password), "", "");
-        } else {
-            addAction(getContext(), actions, ACTION, R.string.account_start_export_button);
-        }
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        presenter.startAccountExport("");
-    }
-
-    @Override
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-        presenter.startAccountExport(action.getDescription().toString());
-        return GuidedAction.ACTION_ID_NEXT;
-    }
-
-    @Override
-    public int onProvideTheme() {
-        return R.style.Theme_Ring_Leanback_GuidedStep_First;
-    }
-
-    @Override
-    public void showExportingProgressDialog() {
-        mWaitDialog = ProgressDialog.show(getActivity(),
-                getString(R.string.export_account_wait_title),
-                getString(R.string.export_account_wait_message));
-    }
-
-    @Override
-    public void showPasswordProgressDialog() {
-
-    }
-
-    @Override
-    public void accountChanged(Account account) {
-
-    }
-
-    @Override
-    public void showNetworkError() {
-        mWaitDialog.dismiss();
-        new AlertDialog.Builder(getActivity())
-                .setTitle(R.string.account_export_end_network_title)
-                .setMessage(R.string.account_export_end_network_message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    @Override
-    public void showPasswordError() {
-        mWaitDialog.dismiss();
-        new AlertDialog.Builder(getActivity())
-                .setTitle(R.string.account_export_end_error_title)
-                .setMessage(R.string.account_export_end_decryption_message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    @Override
-    public void showGenericError() {
-        mWaitDialog.dismiss();
-        new AlertDialog.Builder(getActivity())
-                .setTitle(R.string.account_export_end_error_title)
-                .setMessage(R.string.account_export_end_error_message)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-    }
-
-    @Override
-    public void showPIN(final String pin) {
-        mWaitDialog.dismiss();
-        String pined = getString(R.string.account_end_export_infos).replace("%%", pin);
-        final SpannableString styledResultText = new SpannableString(pined);
-        int pos = pined.lastIndexOf(pin);
-        styledResultText.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), pos, (pos + pin.length()), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        styledResultText.setSpan(new StyleSpan(Typeface.BOLD), pos, (pos + pin.length()), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        styledResultText.setSpan(new RelativeSizeSpan(2.8f), pos, (pos + pin.length()), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
-        new AlertDialog.Builder(getActivity())
-                .setMessage(styledResultText)
-                .setPositiveButton(android.R.string.ok, (dialog, which) -> getFragmentManager().popBackStack())
-                .show();
-    }
-
-    @Override
-    public void passwordChangeEnded(boolean ok) {
-
-    }
-
-    public void displayCompleteArchive(File dest)  {
-        DownloadManager downloadManager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
-        if (downloadManager != null) {
-            downloadManager.addCompletedDownload(dest.getName(),
-                    dest.getName(),
-                    true,
-                    AndroidFileUtils.getMimeType(dest.getAbsolutePath()),
-                    dest.getAbsolutePath(),
-                    dest.length(),
-                    true);
-        }
-    }
-
-    @Override
-    public void gotToImageCapture() {
-
-    }
-
-    @Override
-    public void askCameraPermission() {
-
-    }
-
-    @Override
-    public void goToGallery() {
-
-    }
-
-    @Override
-    public void askGalleryPermission() {
-
-    }
-
-    @Override
-    public void updateUserView(Account account) {
-
-    }
-
-    @Override
-    public void goToMedia(String accountId) {
-
-    }
-
-    @Override
-    public void goToSystem(String accountId) {
-
-    }
-
-    @Override
-    public void goToAdvanced(String accountId) {
-
-    }
-
-    @Override
-    public void goToAccount(String accountId) {
-
-    }
-
-    @Override
-    public void setSwitchStatus(Account account) {
-
-    }
-
-    @Override
-    public void showRevokingProgressDialog() {
-
-    }
-
-    @Override
-    public void deviceRevocationEnded(String device, int status) {
-
-    }
-
-    @Override
-    public void updateDeviceList(Map<String, String> devices, String currentDeviceId) {
-
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt
new file mode 100644
index 000000000..f5ecb6583
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Loïc Siret <loic.siret@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.app.AlertDialog
+import android.app.DownloadManager
+import android.app.ProgressDialog
+import android.content.Context
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.Layout
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.AlignmentSpan
+import android.text.style.RelativeSizeSpan
+import android.text.style.StyleSpan
+import android.view.View
+import androidx.leanback.widget.GuidanceStylist.Guidance
+import androidx.leanback.widget.GuidedAction
+import cx.ring.R
+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 java.io.File
+
+@AndroidEntryPoint
+class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter>(), JamiAccountSummaryView {
+    private var mWaitDialog: ProgressDialog? = 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 {
+        val title = getString(R.string.account_export_title)
+        val breadcrumb = ""
+        val description = getString(R.string.account_link_export_info_light)
+        val icon = requireContext().getDrawable(R.drawable.baseline_devices_24)
+        return Guidance(title, description, breadcrumb, icon)
+    }
+
+    override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle) {
+        if (mHasPassword) {
+            addPasswordAction(activity, actions, PASSWORD, getString(R.string.account_enter_password), "", "")
+        } else {
+            addAction(context, actions, ACTION, R.string.account_start_export_button)
+        }
+    }
+
+    override fun onGuidedActionClicked(action: GuidedAction) {
+        presenter.startAccountExport("")
+    }
+
+    override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long {
+        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 = ProgressDialog.show(activity,
+            getString(R.string.export_account_wait_title),
+            getString(R.string.export_account_wait_message)
+        )
+    }
+
+    override fun showPasswordProgressDialog() {}
+    override fun accountChanged(account: Account) {}
+    override fun showNetworkError() {
+        mWaitDialog!!.dismiss()
+        AlertDialog.Builder(activity)
+            .setTitle(R.string.account_export_end_network_title)
+            .setMessage(R.string.account_export_end_network_message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    override fun showPasswordError() {
+        mWaitDialog!!.dismiss()
+        AlertDialog.Builder(activity)
+            .setTitle(R.string.account_export_end_error_title)
+            .setMessage(R.string.account_export_end_decryption_message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    override fun showGenericError() {
+        mWaitDialog!!.dismiss()
+        AlertDialog.Builder(activity)
+            .setTitle(R.string.account_export_end_error_title)
+            .setMessage(R.string.account_export_end_error_message)
+            .setPositiveButton(android.R.string.ok, null)
+            .show()
+    }
+
+    override fun showPIN(pin: String) {
+        mWaitDialog!!.dismiss()
+        val pined = getString(R.string.account_end_export_infos).replace("%%", pin)
+        val styledResultText = SpannableString(pined)
+        val pos = pined.lastIndexOf(pin)
+        styledResultText.setSpan(
+            AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
+            pos,
+            pos + pin.length,
+            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+        )
+        styledResultText.setSpan(
+            StyleSpan(Typeface.BOLD),
+            pos,
+            pos + pin.length,
+            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+        )
+        styledResultText.setSpan(
+            RelativeSizeSpan(2.8f),
+            pos,
+            pos + pin.length,
+            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+        )
+        AlertDialog.Builder(activity)
+            .setMessage(styledResultText)
+            .setPositiveButton(android.R.string.ok) { _, _ -> parentFragmentManager.popBackStack() }
+            .show()
+    }
+
+    override fun passwordChangeEnded(ok: Boolean) {}
+    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 askGalleryPermission() {}
+    override fun updateUserView(account: Account) {}
+    override fun goToMedia(accountId: String) {}
+    override fun goToSystem(accountId: String) {}
+    override fun goToAdvanced(accountId: String) {}
+    override fun goToAccount(accountId: String) {}
+    override fun setSwitchStatus(account: Account) {}
+    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 {
+            val fragment = TVAccountExport()
+            fragment.mIdAccount = idAccount
+            fragment.mHasPassword = hasPassword
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.java
deleted file mode 100644
index 72a392148..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.account;
-
-import android.app.Activity;
-import android.app.FragmentManager;
-import android.app.ProgressDialog;
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.leanback.app.GuidedStepSupportFragment;
-import androidx.appcompat.app.AlertDialog;
-
-import android.widget.Toast;
-
-import java.io.File;
-
-import cx.ring.R;
-import cx.ring.account.AccountCreationModelImpl;
-import net.jami.account.AccountWizardPresenter;
-import net.jami.account.AccountWizardView;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Account;
-import net.jami.model.AccountConfig;
-import net.jami.mvp.AccountCreationModel;
-import cx.ring.mvp.BaseActivity;
-import net.jami.utils.VCardUtils;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class TVAccountWizard
-        extends BaseActivity<AccountWizardPresenter>
-        implements AccountWizardView {
-    static final String TAG = TVAccountWizard.class.getName();
-    private TVHomeAccountCreationFragment mHomeFragment = new TVHomeAccountCreationFragment();
-
-    private ProgressDialog mProgress = null;
-    private boolean mLinkAccount = false;
-    private String mAccountType;
-    private AlertDialog mAlertDialog;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        super.onCreate(savedInstanceState);
-        JamiApplication.getInstance().startDaemon();
-
-        Intent intent = getIntent();
-        if (intent != null) {
-            mAccountType = intent.getAction();
-        }
-        if (mAccountType == null) {
-            mAccountType = AccountConfig.ACCOUNT_TYPE_RING;
-        }
-
-        if (savedInstanceState == null) {
-            GuidedStepSupportFragment.addAsRoot(this, mHomeFragment, android.R.id.content);
-        } else {
-            mLinkAccount = savedInstanceState.getBoolean("mLinkAccount");
-        }
-
-        presenter.init(getIntent().getAction() != null ? getIntent().getAction() : AccountConfig.ACCOUNT_TYPE_RING);
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putBoolean("mLinkAccount", mLinkAccount);
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mProgress != null) {
-            mProgress.dismiss();
-            mProgress = null;
-        }
-        super.onDestroy();
-    }
-
-    public void createAccount(AccountCreationModel accountCreationModel) {
-        if (accountCreationModel.isLink()) {
-            presenter.initJamiAccountLink(accountCreationModel,
-                    getText(R.string.ring_account_default_name).toString());
-        } else {
-            presenter.initJamiAccountCreation(accountCreationModel,
-                    getText(R.string.ring_account_default_name).toString());
-        }
-    }
-
-    @Override
-    public void goToHomeCreation() {
-
-    }
-
-    @Override
-    public void goToSipCreation() {
-
-    }
-
-    @Override
-    public void onBackPressed() {
-        GuidedStepSupportFragment fragment = GuidedStepSupportFragment.getCurrentGuidedStepSupportFragment(getSupportFragmentManager());
-        if (fragment instanceof TVProfileCreationFragment)
-            finish();
-        else
-            super.onBackPressed();
-    }
-
-    @Override
-    public void goToProfileCreation(AccountCreationModel accountCreationModel) {
-        GuidedStepSupportFragment.add(getSupportFragmentManager(), TVProfileCreationFragment.newInstance((AccountCreationModelImpl) accountCreationModel));
-    }
-
-    @Override
-    public void displayProgress(boolean display) {
-        if (display) {
-            mProgress = new ProgressDialog(this);
-            mProgress.setTitle(R.string.dialog_wait_create);
-            mProgress.setMessage(getString(R.string.dialog_wait_create_details));
-            mProgress.setCancelable(false);
-            mProgress.setCanceledOnTouchOutside(false);
-            mProgress.show();
-        } else {
-            if (mProgress != null) {
-                if (mProgress.isShowing()) {
-                    mProgress.dismiss();
-                }
-                mProgress = null;
-            }
-        }
-
-    }
-
-    @Override
-    public void displayCreationError() {
-        Toast.makeText(TVAccountWizard.this, "Error creating account", Toast.LENGTH_SHORT).show();
-    }
-
-    @Override
-    public void blockOrientation() {
-        //Noop on TV
-    }
-
-    @Override
-    public void finish(final boolean affinity) {
-        if (affinity) {
-            FragmentManager fm = getFragmentManager();
-            if (fm.getBackStackEntryCount() >= 1) {
-                fm.popBackStack();
-            } else {
-                finish();
-            }
-        } else {
-            finishAffinity();
-        }
-    }
-
-    @Override
-    public Single<VCard> saveProfile(final Account account, final AccountCreationModel accountCreationModel) {
-        File filedir = getFilesDir();
-        return accountCreationModel.toVCard()
-                .flatMap(vcard -> {
-                    account.resetProfile();
-                    return VCardUtils.saveLocalProfileToDisk(vcard, account.getAccountID(), filedir);
-                })
-                .subscribeOn(Schedulers.io());
-    }
-
-    @Override
-    public void displayGenericError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new AlertDialog.Builder(TVAccountWizard.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_cannot_be_found_title)
-                .setMessage(R.string.account_cannot_be_found_message)
-                .show();
-    }
-
-    @Override
-    public void displayNetworkError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new AlertDialog.Builder(TVAccountWizard.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_no_network_title)
-                .setMessage(R.string.account_no_network_message)
-                .show();
-    }
-
-    @Override
-    public void displayCannotBeFoundError() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        mAlertDialog = new AlertDialog.Builder(TVAccountWizard.this)
-                .setPositiveButton(android.R.string.ok, null)
-                .setTitle(R.string.account_cannot_be_found_title)
-                .setMessage(R.string.account_cannot_be_found_message)
-                .show();
-    }
-
-    @Override
-    public void displaySuccessDialog() {
-        if (mAlertDialog != null && mAlertDialog.isShowing()) {
-            return;
-        }
-        setResult(Activity.RESULT_OK, new Intent());
-        //startActivity(new Intent(this, HomeActivity.class));
-        finish();
-    }
-
-    public void profileCreated(AccountCreationModel accountCreationModel, boolean saveProfile) {
-        presenter.profileCreated(accountCreationModel, saveProfile);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt
new file mode 100644
index 000000000..3dc1262c2
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt
@@ -0,0 +1,202 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.app.ProgressDialog
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.leanback.app.GuidedStepSupportFragment
+import cx.ring.R
+import cx.ring.account.AccountCreationModelImpl
+import cx.ring.application.JamiApplication.Companion.instance
+import cx.ring.mvp.BaseActivity
+import cx.ring.tv.account.TVProfileCreationFragment.Companion.newInstance
+import dagger.hilt.android.AndroidEntryPoint
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.account.AccountWizardPresenter
+import net.jami.account.AccountWizardView
+import net.jami.model.Account
+import net.jami.model.AccountConfig
+import net.jami.mvp.AccountCreationModel
+import net.jami.utils.VCardUtils
+
+@AndroidEntryPoint
+class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardView {
+    private var mProgress: ProgressDialog? = null
+    private var mLinkAccount = false
+    private var mAccountType: String? = null
+    private var mAlertDialog: AlertDialog? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        instance?.startDaemon()
+        val intent = intent
+        if (intent != null) {
+            mAccountType = intent.action
+        }
+        if (mAccountType == null) {
+            mAccountType = AccountConfig.ACCOUNT_TYPE_RING
+        }
+        if (savedInstanceState == null) {
+            GuidedStepSupportFragment.addAsRoot(
+                this,
+                TVHomeAccountCreationFragment(),
+                android.R.id.content
+            )
+        } else {
+            mLinkAccount = savedInstanceState.getBoolean("mLinkAccount")
+        }
+        presenter!!.init(if (getIntent().action != null) getIntent().action else AccountConfig.ACCOUNT_TYPE_RING)
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putBoolean("mLinkAccount", mLinkAccount)
+    }
+
+    override fun onDestroy() {
+        if (mProgress != null) {
+            mProgress!!.dismiss()
+            mProgress = null
+        }
+        super.onDestroy()
+    }
+
+    fun createAccount(accountCreationModel: AccountCreationModel) {
+        if (accountCreationModel.isLink) {
+            presenter!!.initJamiAccountLink(accountCreationModel, getText(R.string.ring_account_default_name).toString())
+        } else {
+            presenter!!.initJamiAccountCreation(accountCreationModel, getText(R.string.ring_account_default_name).toString())
+        }
+    }
+
+    override fun goToHomeCreation() {}
+    override fun goToSipCreation() {}
+    override fun onBackPressed() {
+        val fragment = GuidedStepSupportFragment.getCurrentGuidedStepSupportFragment(supportFragmentManager)
+        if (fragment is TVProfileCreationFragment) finish() else super.onBackPressed()
+    }
+
+    override fun goToProfileCreation(accountCreationModel: AccountCreationModel) {
+        GuidedStepSupportFragment.add(
+            supportFragmentManager,
+            newInstance(accountCreationModel as AccountCreationModelImpl)
+        )
+    }
+
+    override fun displayProgress(display: Boolean) {
+        if (display) {
+            mProgress = ProgressDialog(this).apply {
+                setTitle(R.string.dialog_wait_create)
+                setMessage(getString(R.string.dialog_wait_create_details))
+                setCancelable(false)
+                setCanceledOnTouchOutside(false)
+                show()
+            }
+        } else {
+            if (mProgress != null) {
+                if (mProgress!!.isShowing) {
+                    mProgress!!.dismiss()
+                }
+                mProgress = null
+            }
+        }
+    }
+
+    override fun displayCreationError() {
+        Toast.makeText(this@TVAccountWizard, "Error creating account", Toast.LENGTH_SHORT).show()
+    }
+
+    override fun blockOrientation() {
+        //Noop on TV
+    }
+
+    override fun finish(affinity: Boolean) {
+        if (affinity) {
+            val fm = fragmentManager
+            if (fm.backStackEntryCount >= 1) {
+                fm.popBackStack()
+            } else {
+                finish()
+            }
+        } else {
+            finishAffinity()
+        }
+    }
+
+    override fun saveProfile(account: Account, accountCreationModel: AccountCreationModel): Single<VCard> {
+        val filedir = filesDir
+        return accountCreationModel.toVCard()
+            .flatMap { vcard ->
+                account.resetProfile()
+                VCardUtils.saveLocalProfileToDisk(vcard, account.accountID, filedir)
+            }
+            .subscribeOn(Schedulers.io())
+    }
+
+    override fun displayGenericError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = AlertDialog.Builder(this@TVAccountWizard)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_cannot_be_found_title)
+            .setMessage(R.string.account_cannot_be_found_message)
+            .show()
+    }
+
+    override fun displayNetworkError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = AlertDialog.Builder(this@TVAccountWizard)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_no_network_title)
+            .setMessage(R.string.account_no_network_message)
+            .show()
+    }
+
+    override fun displayCannotBeFoundError() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        mAlertDialog = AlertDialog.Builder(this@TVAccountWizard)
+            .setPositiveButton(android.R.string.ok, null)
+            .setTitle(R.string.account_cannot_be_found_title)
+            .setMessage(R.string.account_cannot_be_found_message)
+            .show()
+    }
+
+    override fun displaySuccessDialog() {
+        if (mAlertDialog != null && mAlertDialog!!.isShowing) {
+            return
+        }
+        setResult(RESULT_OK, Intent())
+        //startActivity(new Intent(this, HomeActivity.class));
+        finish()
+    }
+
+    fun profileCreated(accountCreationModel: AccountCreationModel?, saveProfile: Boolean) {
+        presenter!!.profileCreated(accountCreationModel, saveProfile)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.java
index c92376c78..7aee786fd 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.java
@@ -32,7 +32,9 @@ import cx.ring.account.AccountCreationModelImpl;
 import net.jami.account.HomeAccountCreationPresenter;
 import net.jami.account.HomeAccountCreationView;
 import cx.ring.application.JamiApplication;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class TVHomeAccountCreationFragment
         extends JamiGuidedStepFragment<HomeAccountCreationPresenter>
         implements HomeAccountCreationView {
@@ -40,13 +42,6 @@ public class TVHomeAccountCreationFragment
     private static final int LINK_ACCOUNT = 0;
     private static final int CREATE_ACCOUNT = 1;
 
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-
-        super.onViewCreated(view, savedInstanceState);
-    }
-
     @Override
     public void goToAccountCreation() {
         AccountCreationModelImpl ringAccountViewModel = new AccountCreationModelImpl();
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java
index 7e655b010..7b1bd5c57 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java
@@ -36,10 +36,13 @@ import cx.ring.account.AccountCreationModelImpl;
 import net.jami.account.JamiAccountCreationPresenter;
 import net.jami.account.JamiAccountCreationView;
 import cx.ring.application.JamiApplication;
+import dagger.hilt.android.AndroidEntryPoint;
+
 import net.jami.mvp.AccountCreationModel;
 import net.jami.utils.Log;
 import net.jami.utils.StringUtils;
 
+@AndroidEntryPoint
 public class TVJamiAccountCreationFragment
         extends JamiGuidedStepFragment<JamiAccountCreationPresenter>
         implements JamiAccountCreationView {
@@ -56,7 +59,7 @@ public class TVJamiAccountCreationFragment
     private String mPassword;
     private String mPasswordConfirm;
 
-    private TextWatcher mUsernameWatcher = new TextWatcher() {
+    private final TextWatcher mUsernameWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
 
@@ -68,10 +71,10 @@ public class TVJamiAccountCreationFragment
             String newName = s.toString();
             if (!newName.equals(getResources().getString(R.string.register_username))) {
                 boolean empty = newName.isEmpty();
-                /** If the username is empty make sure to set isRegisterUsernameChecked
+                /* If the username is empty make sure to set isRegisterUsernameChecked
                  *  to False, this allows to create an account with an empty username */
                 presenter.registerUsernameChanged(!empty);
-                /** Send the newName even when empty (in order to reset the views) */
+                /* Send the newName even when empty (in order to reset the views) */
                 presenter.userNameChanged(newName);
             }
         }
@@ -88,9 +91,6 @@ public class TVJamiAccountCreationFragment
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-
-        // Bind the presenter to the view
         super.onViewCreated(view, savedInstanceState);
 
         if (model == null) {
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java
index dc30ed0b1..5ff65dcc1 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java
@@ -32,9 +32,12 @@ import cx.ring.account.AccountCreationModelImpl;
 import net.jami.account.JamiLinkAccountPresenter;
 import net.jami.account.JamiLinkAccountView;
 import cx.ring.application.JamiApplication;
+import dagger.hilt.android.AndroidEntryPoint;
+
 import net.jami.mvp.AccountCreationModel;
 import net.jami.utils.StringUtils;
 
+@AndroidEntryPoint
 public class TVJamiLinkAccountFragment extends JamiGuidedStepFragment<JamiLinkAccountPresenter>
         implements JamiLinkAccountView {
     private static final long PASSWORD = 1L;
@@ -53,9 +56,7 @@ public class TVJamiLinkAccountFragment extends JamiGuidedStepFragment<JamiLinkAc
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
         super.onViewCreated(view, savedInstanceState);
-
         presenter.init(model);
         if (model != null && model.getPhoto() != null) {
             getGuidanceStylist().getIconView().setImageBitmap(model.getPhoto());
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
deleted file mode 100644
index e844557cc..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.account;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import androidx.annotation.NonNull;
-import androidx.leanback.app.GuidedStepSupportFragment;
-import androidx.leanback.widget.GuidanceStylist;
-import androidx.leanback.widget.GuidedAction;
-import androidx.appcompat.app.AlertDialog;
-import android.text.TextUtils;
-import android.view.View;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.account.AccountCreationModelImpl;
-import cx.ring.account.ProfileCreationFragment;
-import net.jami.account.ProfileCreationPresenter;
-import net.jami.account.ProfileCreationView;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Account;
-import net.jami.mvp.AccountCreationModel;
-import cx.ring.tv.camera.CustomCameraActivity;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.core.Single;
-
-public class TVProfileCreationFragment extends JamiGuidedStepFragment<ProfileCreationPresenter>
-        implements ProfileCreationView {
-
-    private static final int USER_NAME = 1;
-    private static final int GALLERY = 2;
-    private static final int CAMERA = 3;
-    private static final int NEXT = 4;
-
-    private AccountCreationModelImpl mModel;
-    private int iconSize = -1;
-
-    public static GuidedStepSupportFragment newInstance(AccountCreationModelImpl ringAccountViewModel) {
-        TVProfileCreationFragment fragment = new TVProfileCreationFragment();
-        fragment.mModel = ringAccountViewModel;
-        return fragment;
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
-        switch (requestCode) {
-            case ProfileCreationFragment.REQUEST_CODE_PHOTO:
-                if (resultCode == Activity.RESULT_OK && intent != null && intent.getExtras() != null) {
-                    Uri uri = (Uri) intent.getExtras().get((MediaStore.EXTRA_OUTPUT));
-                    ContentResolver cr = getActivity().getContentResolver();
-                    try {
-                        InputStream is = cr.openInputStream(uri);
-                        Bitmap image = BitmapFactory.decodeStream(is);
-                        presenter.photoUpdated(Single.just(intent)
-                                .map(i -> image));
-                    } catch (FileNotFoundException e) {
-                        e.printStackTrace();
-                    }
-                }
-                break;
-            case ProfileCreationFragment.REQUEST_CODE_GALLERY:
-                if (resultCode == Activity.RESULT_OK && intent != null) {
-                    presenter.photoUpdated(AndroidFileUtils.loadBitmap(getActivity(), intent.getData()).map(b -> (Object)b));
-                }
-                break;
-            default:
-                super.onActivityResult(requestCode, resultCode, intent);
-                break;
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        switch (requestCode) {
-            case ProfileCreationFragment.REQUEST_PERMISSION_CAMERA:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.cameraPermissionChanged(true);
-                    presenter.cameraClick();
-                }
-                break;
-            case ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.galleryClick();
-                }
-                break;
-        }
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        super.onViewCreated(view, savedInstanceState);
-
-        if (mModel == null) {
-            getActivity().finish();
-            return;
-        }
-
-        iconSize = (int) getResources().getDimension(R.dimen.tv_avatar_size);
-        presenter.initPresenter(mModel);
-    }
-
-    @Override
-    @NonNull
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        String title = getString(R.string.account_create_title);
-        String breadcrumb = "";
-        String description = getString(R.string.profile_message_warning);
-        return new GuidanceStylist.Guidance(title, description, breadcrumb,
-                new AvatarDrawable.Builder()
-                    .withNameData(mModel == null ? null : mModel.getFullName(), mModel == null ? null : mModel.getUsername())
-                    .withCircleCrop(true)
-                    .build(getContext())
-        );
-    }
-
-    @Override
-    public int onProvideTheme() {
-        return R.style.Theme_Ring_Leanback_GuidedStep_First;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        addEditTextAction(getActivity(), actions, USER_NAME, R.string.profile_name_hint, R.string.profile_name_hint);
-        addAction(getActivity(), actions, CAMERA, getActivity().getResources().getString(R.string.take_a_photo), "");
-        addAction(getActivity(), actions, GALLERY, getActivity().getResources().getString(R.string.open_the_gallery), "");
-        addAction(getActivity(), actions, NEXT, getActivity().getResources().getString(R.string.wizard_next), "", true);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (action.getId() == CAMERA) {
-            presenter.cameraClick();
-        } else if (action.getId() == GALLERY) {
-            presenter.galleryClick();
-        } else if (action.getId() == NEXT) {
-            presenter.nextClick();
-        }
-    }
-
-    @Override
-    public void displayProfileName(String profileName) {
-        findActionById(USER_NAME).setEditDescription(profileName);
-        notifyActionChanged(findActionPositionById(USER_NAME));
-    }
-
-    @Override
-    public void goToGallery() {
-        try {
-            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-            startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_GALLERY);
-        } catch (ActivityNotFoundException e) {
-            new AlertDialog.Builder(getActivity())
-                    .setPositiveButton(android.R.string.ok, null)
-                    .setTitle(R.string.gallery_error_title)
-                    .setMessage(R.string.gallery_error_message)
-                    .show();
-        }
-    }
-
-    @Override
-    public void goToPhotoCapture() {
-        Intent intent = new Intent(getActivity(), CustomCameraActivity.class);
-        startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_PHOTO);
-    }
-
-    @Override
-    public void askStoragePermission() {
-        requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
-                ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE);
-    }
-
-    @Override
-    public void askPhotoPermission() {
-        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
-                ProfileCreationFragment.REQUEST_PERMISSION_CAMERA);
-    }
-
-    @Override
-    public void goToNext(AccountCreationModel accountCreationModel, boolean saveProfile) {
-        Activity wizardActivity = getActivity();
-        if (wizardActivity instanceof TVAccountWizard) {
-            TVAccountWizard wizard = (TVAccountWizard) wizardActivity;
-            wizard.profileCreated(accountCreationModel, saveProfile);
-        }
-    }
-
-    @Override
-    public void setProfile(AccountCreationModel accountCreationModel) {
-        AccountCreationModelImpl model = ((AccountCreationModelImpl) accountCreationModel);
-        Account newAccount = model.getNewAccount();
-        AvatarDrawable avatar =
-                new AvatarDrawable.Builder()
-                        .withPhoto(model.getPhoto())
-                        .withNameData(accountCreationModel.getFullName(), accountCreationModel.getUsername())
-                        .withId(newAccount == null ? null : newAccount.getUsername())
-                        .withCircleCrop(true)
-                        .build(getContext());
-        avatar.setInSize(iconSize);
-        getGuidanceStylist().getIconView().setImageDrawable(avatar);
-    }
-
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-        switch ((int) action.getId()){
-            case USER_NAME:
-                String username = action.getEditTitle().toString();
-                presenter.fullNameUpdated(username);
-                if (username.isEmpty())
-                    action.setTitle(getString(R.string.profile_name_hint));
-                else
-                    action.setTitle(username);
-                break;
-            case CAMERA:
-                presenter.cameraClick();
-                break;
-            case GALLERY:
-                presenter.galleryClick();
-                break;
-        }
-        return super.onGuidedActionEditedAndProceed(action);
-    }
-
-    @Override
-    public void onGuidedActionEditCanceled(GuidedAction action) {
-        if ((int) action.getId() == USER_NAME) {
-            String username = action.getEditTitle().toString();
-            presenter.fullNameUpdated(username);
-            if (TextUtils.isEmpty(username))
-                action.setTitle(getString(R.string.profile_name_hint));
-            else
-                action.setTitle(username);
-        }
-        super.onGuidedActionEditCanceled(action);
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt
new file mode 100644
index 000000000..4d1e32a33
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt
@@ -0,0 +1,224 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.account
+
+import android.Manifest
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.text.TextUtils
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import androidx.leanback.app.GuidedStepSupportFragment
+import androidx.leanback.widget.GuidanceStylist.Guidance
+import androidx.leanback.widget.GuidedAction
+import cx.ring.R
+import cx.ring.account.AccountCreationModelImpl
+import cx.ring.account.ProfileCreationFragment
+import cx.ring.tv.camera.CustomCameraActivity
+import cx.ring.utils.AndroidFileUtils.loadBitmap
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.core.Single
+import net.jami.account.ProfileCreationPresenter
+import net.jami.account.ProfileCreationView
+import net.jami.mvp.AccountCreationModel
+
+@AndroidEntryPoint
+class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresenter>(),
+    ProfileCreationView {
+    private var mModel: AccountCreationModelImpl? = null
+    private var iconSize = -1
+    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
+        when (requestCode) {
+            ProfileCreationFragment.REQUEST_CODE_PHOTO -> if (resultCode == Activity.RESULT_OK && intent != null && intent.extras != null) {
+                val uri = intent.extras!![MediaStore.EXTRA_OUTPUT] as Uri?
+                try {
+                    requireContext().contentResolver.openInputStream(uri!!).use { iStream ->
+                        val image = BitmapFactory.decodeStream(iStream)
+                        presenter!!.photoUpdated(Single.just(intent).map { image })
+                    }
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                }
+            }
+            ProfileCreationFragment.REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && intent != null) {
+                presenter!!.photoUpdated(
+                    loadBitmap(requireContext(), intent.data!!).map { b: Bitmap? -> b })
+            }
+            else -> super.onActivityResult(requestCode, resultCode, intent)
+        }
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        when (requestCode) {
+            ProfileCreationFragment.REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter!!.cameraPermissionChanged(true)
+                presenter!!.cameraClick()
+            }
+            ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter!!.galleryClick()
+            }
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        if (mModel == null) {
+            activity?.finish()
+            return
+        }
+        iconSize = resources.getDimension(R.dimen.tv_avatar_size).toInt()
+        presenter!!.initPresenter(mModel)
+    }
+
+    override fun onCreateGuidance(savedInstanceState: Bundle): Guidance {
+        val title = getString(R.string.account_create_title)
+        val breadcrumb = ""
+        val description = getString(R.string.profile_message_warning)
+        return Guidance(title, description, breadcrumb, AvatarDrawable.Builder()
+                .withNameData(
+                    if (mModel == null) null else mModel!!.fullName,
+                    if (mModel == null) null else mModel!!.username
+                )
+                .withCircleCrop(true)
+                .build(requireContext())
+        )
+    }
+
+    override fun onProvideTheme(): Int {
+        return R.style.Theme_Ring_Leanback_GuidedStep_First
+    }
+
+    override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle) {
+        addEditTextAction(activity, actions, USER_NAME.toLong(), R.string.profile_name_hint, R.string.profile_name_hint)
+        addAction(activity, actions, CAMERA.toLong(), getString(R.string.take_a_photo), "")
+        addAction(activity, actions, GALLERY.toLong(), getString(R.string.open_the_gallery), "")
+        addAction(activity, actions, NEXT.toLong(), getString(R.string.wizard_next), "", true)
+    }
+
+    override fun onGuidedActionClicked(action: GuidedAction) {
+        when (action.id) {
+            CAMERA.toLong() -> presenter!!.cameraClick()
+            GALLERY.toLong() -> presenter!!.galleryClick()
+            NEXT.toLong() -> presenter!!.nextClick()
+        }
+    }
+
+    override fun displayProfileName(profileName: String) {
+        findActionById(USER_NAME.toLong()).editDescription = profileName
+        notifyActionChanged(findActionPositionById(USER_NAME.toLong()))
+    }
+
+    override fun goToGallery() {
+        try {
+            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+            startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_GALLERY)
+        } catch (e: ActivityNotFoundException) {
+            AlertDialog.Builder(requireActivity())
+                .setPositiveButton(android.R.string.ok, null)
+                .setTitle(R.string.gallery_error_title)
+                .setMessage(R.string.gallery_error_message)
+                .show()
+        }
+    }
+
+    override fun goToPhotoCapture() {
+        val intent = Intent(activity, CustomCameraActivity::class.java)
+        startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_PHOTO)
+    }
+
+    override fun askStoragePermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
+            ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE
+        )
+    }
+
+    override fun askPhotoPermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
+            ProfileCreationFragment.REQUEST_PERMISSION_CAMERA
+        )
+    }
+
+    override fun goToNext(accountCreationModel: AccountCreationModel, saveProfile: Boolean) {
+        val wizardActivity: Activity? = activity
+        if (wizardActivity is TVAccountWizard) {
+            wizardActivity.profileCreated(accountCreationModel, saveProfile)
+        }
+    }
+
+    override fun setProfile(accountCreationModel: AccountCreationModel) {
+        val model = accountCreationModel as AccountCreationModelImpl
+        val newAccount = model.newAccount
+        val avatar = AvatarDrawable.Builder()
+            .withPhoto(model.photo)
+            .withNameData(accountCreationModel.getFullName(), accountCreationModel.getUsername())
+            .withId(newAccount?.username)
+            .withCircleCrop(true)
+            .build(requireContext())
+        avatar.setInSize(iconSize)
+        guidanceStylist.iconView.setImageDrawable(avatar)
+    }
+
+    override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long {
+        when (action.id.toInt()) {
+            USER_NAME -> {
+                val username = action.editTitle.toString()
+                presenter!!.fullNameUpdated(username)
+                if (username.isEmpty()) action.title =
+                    getString(R.string.profile_name_hint) else action.title = username
+            }
+            CAMERA -> presenter!!.cameraClick()
+            GALLERY -> presenter!!.galleryClick()
+        }
+        return super.onGuidedActionEditedAndProceed(action)
+    }
+
+    override fun onGuidedActionEditCanceled(action: GuidedAction) {
+        if (action.id.toInt() == USER_NAME) {
+            val username = action.editTitle.toString()
+            presenter!!.fullNameUpdated(username)
+            if (TextUtils.isEmpty(username)) action.title =
+                getString(R.string.profile_name_hint) else action.title = username
+        }
+        super.onGuidedActionEditCanceled(action)
+    }
+
+    companion object {
+        private const val USER_NAME = 1
+        private const val GALLERY = 2
+        private const val CAMERA = 3
+        private const val NEXT = 4
+
+        fun newInstance(ringAccountViewModel: AccountCreationModelImpl?): GuidedStepSupportFragment {
+            val fragment = TVProfileCreationFragment()
+            fragment.mModel = ringAccountViewModel
+            return fragment
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
deleted file mode 100644
index 646aa46eb..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.account;
-
-import android.Manifest;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ActivityNotFoundException;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import androidx.annotation.NonNull;
-import androidx.leanback.widget.GuidanceStylist;
-import androidx.leanback.widget.GuidedAction;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.account.ProfileCreationFragment;
-import cx.ring.application.JamiApplication;
-import net.jami.model.Account;
-import net.jami.navigation.HomeNavigationPresenter;
-import net.jami.navigation.HomeNavigationView;
-import net.jami.navigation.HomeNavigationViewModel;
-import cx.ring.tv.camera.CustomCameraActivity;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class TVProfileEditingFragment extends JamiGuidedStepFragment<HomeNavigationPresenter>
-        implements HomeNavigationView {
-
-    private static final int USER_NAME = 1;
-    private static final int GALLERY = 2;
-    private static final int CAMERA = 3;
-
-    private List<GuidedAction> actions;
-    private int iconSize = -1;
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case ProfileCreationFragment.REQUEST_CODE_PHOTO:
-                if (resultCode == Activity.RESULT_OK && data != null) {
-                    Bundle extras = data.getExtras();
-                    if (extras == null) {
-                        Log.e(TAG, "onActivityResult: Not able to get picture from extra");
-                        return;
-                    }
-                    Uri uri = (Uri) extras.get((MediaStore.EXTRA_OUTPUT));
-                    if (uri != null) {
-                        ContentResolver cr = requireContext().getContentResolver();
-                        presenter.saveVCardPhoto(Single.fromCallable(() -> BitmapFactory.decodeStream(cr.openInputStream(uri)))
-                                .map(BitmapUtils::bitmapToPhoto));
-                    }
-                }
-                break;
-            case ProfileCreationFragment.REQUEST_CODE_GALLERY:
-                if (resultCode == Activity.RESULT_OK && data != null) {
-                    presenter.saveVCardPhoto(AndroidFileUtils
-                            .loadBitmap(getActivity(), data.getData())
-                            .map(BitmapUtils::bitmapToPhoto));
-                }
-                break;
-            default:
-                break;
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        switch (requestCode) {
-            case ProfileCreationFragment.REQUEST_PERMISSION_CAMERA:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.cameraPermissionChanged(true);
-                    presenter.cameraClicked();
-                }
-                break;
-            case ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE:
-                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
-                    presenter.galleryClicked();
-                }
-                break;
-        }
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        super.onViewCreated(view, savedInstanceState);
-        iconSize = (int) getResources().getDimension(R.dimen.tv_avatar_size);
-    }
-
-    @Override
-    @NonNull
-    public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
-        String title = getString(R.string.profile);
-        String breadcrumb = "";
-        String description = getString(R.string.profile_message_warning);
-        Drawable icon = requireContext().getDrawable(R.drawable.ic_contact_picture_fallback);
-        return new GuidanceStylist.Guidance(title, description, breadcrumb, icon);
-    }
-
-    @Override
-    public int onProvideTheme() {
-        return R.style.Theme_Ring_Leanback_GuidedStep_First;
-    }
-
-    @Override
-    public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
-        addEditTextAction(getActivity(), actions, USER_NAME, R.string.account_edit_profile, R.string.profile_name_hint);
-        addAction(getActivity(), actions, CAMERA, R.string.take_a_photo);
-        addAction(getActivity(), actions, GALLERY, R.string.open_the_gallery);
-        this.actions = actions;
-    }
-
-    public long onGuidedActionEditedAndProceed(GuidedAction action) {
-        if (action.getId() == USER_NAME) {
-            String username = action.getEditTitle().toString();
-            presenter.saveVCardFormattedName(username);
-        } else if (action.getId() == CAMERA) {
-            presenter.cameraClicked();
-        } else if (action.getId() == GALLERY) {
-            presenter.galleryClicked();
-        }
-        return super.onGuidedActionEditedAndProceed(action);
-    }
-
-    @Override
-    public void onGuidedActionClicked(GuidedAction action) {
-        if (action.getId() == CAMERA) {
-            presenter.cameraClicked();
-        } else if (action.getId() == GALLERY) {
-            presenter.galleryClicked();
-        }
-    }
-
-    @Override
-    public void showViewModel(HomeNavigationViewModel viewModel) {
-        Account account = viewModel.getAccount();
-        if (account == null) {
-            Log.e(TAG, "Not able to get current account");
-            return;
-        }
-
-        String alias = viewModel.getAlias();
-        GuidedAction action = actions.isEmpty() ? null : actions.get(0);
-        if (action != null && action.getId() == USER_NAME) {
-            if (TextUtils.isEmpty(alias)) {
-                action.setEditTitle("");
-                action.setTitle(getString(R.string.account_edit_profile));
-
-            } else {
-                action.setEditTitle(alias);
-                action.setTitle(alias);
-            }
-            notifyActionChanged(0);
-        }
-
-        if (TextUtils.isEmpty(alias))
-            getGuidanceStylist().getTitleView().setText(R.string.profile);
-        else
-            getGuidanceStylist().getTitleView().setText(alias);
-
-        AvatarDrawable.load(getContext(), account)
-                .map(avatar -> {
-                    avatar.setInSize(iconSize);
-                    return avatar;
-                })
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(avatar -> getGuidanceStylist().getIconView().setImageDrawable(avatar));
-    }
-
-    @Override
-    public void updateModel(Account account) {
-    }
-
-    @Override
-    public void gotToImageCapture() {
-        Intent intent = new Intent(getActivity(), CustomCameraActivity.class);
-        startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_PHOTO);
-    }
-
-    @Override
-    public void askCameraPermission() {
-        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
-                ProfileCreationFragment.REQUEST_PERMISSION_CAMERA);
-    }
-
-    @Override
-    public void askGalleryPermission() {
-        requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
-                ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE);
-    }
-
-    @Override
-    public void goToGallery() {
-        try {
-            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-            startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_GALLERY);
-        } catch (ActivityNotFoundException e) {
-            new AlertDialog.Builder(requireContext())
-                    .setPositiveButton(android.R.string.ok, null)
-                    .setTitle(R.string.gallery_error_title)
-                    .setMessage(R.string.gallery_error_message)
-                    .show();
-        }
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt
new file mode 100644
index 000000000..7042d907a
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt
@@ -0,0 +1,196 @@
+/*
+ *  Copyright (C) 2004-2021 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.account
+
+import android.Manifest
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Bundle
+import android.provider.MediaStore
+import android.text.TextUtils
+import android.util.Log
+import android.view.View
+import androidx.leanback.widget.GuidanceStylist.Guidance
+import androidx.leanback.widget.GuidedAction
+import cx.ring.R
+import cx.ring.account.ProfileCreationFragment
+import cx.ring.tv.camera.CustomCameraActivity
+import cx.ring.utils.AndroidFileUtils.loadBitmap
+import cx.ring.utils.BitmapUtils
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarDrawable.Companion.load
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Account
+import net.jami.navigation.HomeNavigationPresenter
+import net.jami.navigation.HomeNavigationView
+import net.jami.navigation.HomeNavigationViewModel
+
+@AndroidEntryPoint
+class TVProfileEditingFragment : JamiGuidedStepFragment<HomeNavigationPresenter>(), HomeNavigationView {
+    private var iconSize = -1
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            ProfileCreationFragment.REQUEST_CODE_PHOTO -> if (resultCode == Activity.RESULT_OK && data != null) {
+                val extras = data.extras
+                if (extras == null) {
+                    Log.e(TAG, "onActivityResult: Not able to get picture from extra")
+                    return
+                }
+                val uri = extras[MediaStore.EXTRA_OUTPUT] as Uri?
+                if (uri != null) {
+                    val cr = requireContext().contentResolver
+                    presenter.saveVCardPhoto(Single.fromCallable {
+                        cr.openInputStream(uri).use { BitmapFactory.decodeStream(it) }
+                    }.map { obj: Bitmap -> BitmapUtils.bitmapToPhoto(obj) })
+                }
+            }
+            ProfileCreationFragment.REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && data != null) {
+                presenter.saveVCardPhoto(loadBitmap(requireContext(), data.data!!)
+                    .map { obj: Bitmap -> BitmapUtils.bitmapToPhoto(obj) })
+            }
+            else -> {
+            }
+        }
+    }
+
+    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
+        when (requestCode) {
+            ProfileCreationFragment.REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter!!.cameraPermissionChanged(true)
+                presenter!!.cameraClicked()
+            }
+            ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                presenter!!.galleryClicked()
+            }
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        iconSize = resources.getDimension(R.dimen.tv_avatar_size).toInt()
+    }
+
+    override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance {
+        val title = getString(R.string.profile)
+        val breadcrumb = ""
+        val description = getString(R.string.profile_message_warning)
+        val icon = requireContext().getDrawable(R.drawable.ic_contact_picture_fallback)
+        return Guidance(title, description, breadcrumb, icon)
+    }
+
+    override fun onProvideTheme(): Int {
+        return R.style.Theme_Ring_Leanback_GuidedStep_First
+    }
+
+    override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle?) {
+        addEditTextAction(context, actions, USER_NAME, R.string.account_edit_profile, R.string.profile_name_hint)
+        addAction(context, actions, CAMERA, R.string.take_a_photo)
+        addAction(context, actions, GALLERY, R.string.open_the_gallery)
+        this.actions = actions
+    }
+
+    override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long {
+        when (action.id) {
+            USER_NAME -> presenter?.saveVCardFormattedName(action.editTitle.toString())
+            CAMERA -> presenter?.cameraClicked()
+            GALLERY -> presenter?.galleryClicked()
+        }
+        return super.onGuidedActionEditedAndProceed(action)
+    }
+
+    override fun onGuidedActionClicked(action: GuidedAction) {
+        when (action.id) {
+            CAMERA -> presenter?.cameraClicked()
+            GALLERY -> presenter?.galleryClicked()
+        }
+    }
+
+    override fun showViewModel(viewModel: HomeNavigationViewModel) {
+        val action = actions?.let { if (it.isEmpty()) null else it[0] }
+        if (action != null && action.id == USER_NAME.toLong()) {
+            if (TextUtils.isEmpty(viewModel.alias)) {
+                action.editTitle = ""
+                action.title = getString(R.string.account_edit_profile)
+            } else {
+                action.editTitle = viewModel.alias
+                action.title = viewModel.alias
+            }
+            notifyActionChanged(0)
+        }
+        if (TextUtils.isEmpty(viewModel.alias))
+            guidanceStylist.titleView.setText(R.string.profile)
+        else guidanceStylist.titleView.text = viewModel.alias
+        setPhoto(viewModel.account)
+    }
+
+    override fun setPhoto(account: Account) {
+        load(requireContext(), account)
+            .map { avatar -> avatar.apply { setInSize(iconSize) } }
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { avatar: AvatarDrawable? -> guidanceStylist.iconView.setImageDrawable(avatar) }
+    }
+
+    override fun updateModel(account: Account) {}
+    override fun gotToImageCapture() {
+        val intent = Intent(activity, CustomCameraActivity::class.java)
+        startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_PHOTO)
+    }
+
+    override fun askCameraPermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
+            ProfileCreationFragment.REQUEST_PERMISSION_CAMERA
+        )
+    }
+
+    override fun askGalleryPermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
+            ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE
+        )
+    }
+
+    override fun goToGallery() {
+        try {
+            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+            startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_GALLERY)
+        } catch (e: ActivityNotFoundException) {
+            AlertDialog.Builder(requireContext())
+                .setPositiveButton(android.R.string.ok, null)
+                .setTitle(R.string.gallery_error_title)
+                .setMessage(R.string.gallery_error_message)
+                .show()
+        }
+    }
+
+    companion object {
+        private const val USER_NAME = 1L
+        private const val GALLERY = 2L
+        private const val CAMERA = 3L
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareActivity.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareActivity.java
index 8ff02f643..d398017b4 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareActivity.java
@@ -21,8 +21,9 @@ package cx.ring.tv.account;
 import android.os.Bundle;
 import androidx.fragment.app.FragmentActivity;
 import cx.ring.R;
+import dagger.hilt.android.AndroidEntryPoint;
 
-
+@AndroidEntryPoint
 public class TVShareActivity extends FragmentActivity {
 
     public static final String SHARED_ELEMENT_NAME = "photo";
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.java
deleted file mode 100644
index 5dc6994b1..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *           Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.account;
-
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.databinding.TvFragShareBinding;
-import net.jami.model.Account;
-import cx.ring.mvp.BaseSupportFragment;
-import net.jami.mvp.GenericView;
-import cx.ring.services.VCardServiceImpl;
-import net.jami.share.SharePresenter;
-import net.jami.share.ShareViewModel;
-import net.jami.utils.Log;
-import net.jami.utils.QRCodeUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class TVShareFragment extends BaseSupportFragment<SharePresenter> implements GenericView<ShareViewModel> {
-
-    private TvFragShareBinding binding;
-    private final CompositeDisposable disposable = new CompositeDisposable();
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = TvFragShareBinding.inflate(inflater, container, false);
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-        disposable.clear();
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        disposable.dispose();
-    }
-
-    @Override
-    public void showViewModel(final ShareViewModel viewModel) {
-        if (binding == null)
-            return;
-
-        final QRCodeUtils.QRCodeData qrCodeData = viewModel.getAccountQRCodeData(0x00000000, 0xFFFFFFFF);
-        getUserAvatar(viewModel.getAccount());
-
-        if (qrCodeData == null) {
-            binding.qrImage.setVisibility(View.INVISIBLE);
-        } else {
-            int pad = 56;
-            Bitmap bitmap = Bitmap.createBitmap(qrCodeData.getWidth() + (2 * pad), qrCodeData.getHeight() + (2 * pad), Bitmap.Config.ARGB_8888);
-            bitmap.setPixels(qrCodeData.getData(), 0, qrCodeData.getWidth(), pad, pad, qrCodeData.getWidth(), qrCodeData.getHeight());
-            binding.qrImage.setImageBitmap(bitmap);
-            binding.shareQrInstruction.setText(R.string.share_message);
-            binding.qrImage.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private void getUserAvatar(Account account) {
-        disposable.add(VCardServiceImpl
-                .loadProfile(requireContext(), account)
-                .observeOn(AndroidSchedulers.mainThread())
-                .doOnSuccess(profile -> {
-                    if (binding != null) {
-                        binding.shareUri.setVisibility(View.VISIBLE);
-                        binding.shareUri.setText(account.getDisplayUsername());
-                    }
-                })
-                .flatMap(p -> AvatarDrawable.load(requireContext(), account))
-                .subscribe(a -> {
-                    if (binding != null) {
-                        binding.qrUserPhoto.setVisibility(View.VISIBLE);
-                        binding.qrUserPhoto.setImageDrawable(a);
-                    }
-                }, e-> Log.e(TVShareFragment.class.getSimpleName(), e.getMessage())));
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt
new file mode 100644
index 000000000..b7b868719
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt
@@ -0,0 +1,96 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *           Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.account
+
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import cx.ring.R
+import cx.ring.databinding.TvFragShareBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.services.VCardServiceImpl.Companion.loadProfile
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarDrawable.Companion.build
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.model.Account
+import net.jami.mvp.GenericView
+import net.jami.share.SharePresenter
+import net.jami.share.ShareViewModel
+import net.jami.utils.Log
+
+@AndroidEntryPoint
+class TVShareFragment : BaseSupportFragment<SharePresenter, GenericView<ShareViewModel>>(), GenericView<ShareViewModel> {
+    private var binding: TvFragShareBinding? = null
+    private val disposable = CompositeDisposable()
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        binding = TvFragShareBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+        disposable.clear()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        disposable.dispose()
+    }
+
+    override fun showViewModel(viewModel: ShareViewModel) {
+        binding?.let { binding ->
+            val qrCodeData = viewModel.getAccountQRCodeData(0x00000000, -0x1)
+            getUserAvatar(viewModel.account)
+            if (qrCodeData == null) {
+                binding.qrImage.visibility = View.INVISIBLE
+            } else {
+                val pad = 56
+                val bitmap = Bitmap.createBitmap(qrCodeData.width + 2 * pad, qrCodeData.height + 2 * pad, Bitmap.Config.ARGB_8888)
+                bitmap.setPixels(qrCodeData.data, 0, qrCodeData.width, pad, pad, qrCodeData.width, qrCodeData.height)
+                binding.qrImage.setImageBitmap(bitmap)
+                binding.shareQrInstruction.setText(R.string.share_message)
+                binding.qrImage.visibility = View.VISIBLE
+            }
+        }
+    }
+
+    private fun getUserAvatar(account: Account) {
+        disposable.add(loadProfile(requireContext(), account)
+            .observeOn(AndroidSchedulers.mainThread())
+            .doOnNext {
+                binding?.apply {
+                    shareUri?.visibility = View.VISIBLE
+                    shareUri?.text = account.displayUsername
+                }
+            }
+            .map { p -> build(requireContext(), account, p, true) }
+            .subscribe({ a: AvatarDrawable? ->
+                binding?.apply {
+                    qrUserPhoto?.visibility = View.VISIBLE
+                    qrUserPhoto?.setImageDrawable(a)
+                }
+            }) { e -> Log.e(TVShareFragment::class.simpleName, e.message) })
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
deleted file mode 100644
index eda7b028c..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Michel Schmit <michel.schmit@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.call;
-
-import android.content.Intent;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.WindowManager;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-
-import net.jami.call.CallView;
-
-import cx.ring.utils.ConversationPath;
-
-import net.jami.services.NotificationService;
-import net.jami.utils.Log;
-
-public class TVCallActivity extends FragmentActivity {
-
-    static final String TAG = TVCallActivity.class.getSimpleName();
-    private static final String CALL_FRAGMENT_TAG = "CALL_FRAGMENT_TAG";
-
-    private TVCallFragment callFragment;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Intent intent = getIntent();
-        if (intent == null) {
-            finish();
-            return;
-        }
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
-            setTurnScreenOn(true);
-            setShowWhenLocked(true);
-        } else {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
-                    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-        }
-        setContentView(R.layout.tv_activity_call);
-        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
-
-        // dependency injection
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        JamiApplication.getInstance().startDaemon();
-
-        ConversationPath path = ConversationPath.fromIntent(intent);
-
-        FragmentManager fragmentManager = getSupportFragmentManager();
-        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
-
-        if (path != null) {
-            Log.d(TAG, "onCreate: outgoing call " + path);
-            callFragment = TVCallFragment.newInstance(intent.getAction(),
-                    path.getAccountId(),
-                    path.getConversationId(),
-                    intent.getExtras().getString(Intent.EXTRA_PHONE_NUMBER, path.getConversationId()),
-                    false);
-            fragmentTransaction.replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit();
-        } else {
-            Log.d(TAG, "onCreate: incoming call");
-
-            String confId = getIntent().getStringExtra(NotificationService.KEY_CALL_ID);
-            Log.d(TAG, "onCreate: conf " + confId);
-
-            callFragment = TVCallFragment.newInstance(Intent.ACTION_VIEW, confId);
-            fragmentTransaction.replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit();
-        }
-    }
-
-    @Override
-    public void onUserLeaveHint() {
-        Fragment fragment = getSupportFragmentManager().findFragmentByTag(CALL_FRAGMENT_TAG);
-        if (fragment instanceof CallView) {
-            CallView callFragment = (CallView) fragment;
-            callFragment.onUserLeave();
-        }
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        callFragment.onKeyDown();
-        return super.onKeyDown(keyCode, event);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        callFragment.onKeyDown();
-        return super.onTouchEvent(event);
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt
new file mode 100644
index 000000000..3887e845d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt
@@ -0,0 +1,97 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Michel Schmit <michel.schmit@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.call
+
+import dagger.hilt.android.AndroidEntryPoint
+import androidx.fragment.app.FragmentActivity
+import android.os.Bundle
+import android.content.Intent
+import android.os.Build
+import android.view.WindowManager
+import cx.ring.R
+import android.media.AudioManager
+import android.view.KeyEvent
+import net.jami.services.NotificationService
+import net.jami.call.CallView
+import android.view.MotionEvent
+import cx.ring.application.JamiApplication
+import cx.ring.utils.ConversationPath
+import net.jami.utils.Log
+
+@AndroidEntryPoint
+class TVCallActivity : FragmentActivity() {
+    private var callFragment: TVCallFragment? = null
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val intent = intent
+        if (intent == null) {
+            finish()
+            return
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+            setTurnScreenOn(true)
+            setShowWhenLocked(true)
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
+        }
+        setContentView(R.layout.tv_activity_call)
+        volumeControlStream = AudioManager.STREAM_VOICE_CALL
+        JamiApplication.instance?.startDaemon()
+        val path = ConversationPath.fromIntent(intent)
+        val fragmentManager = supportFragmentManager
+        val fragmentTransaction = fragmentManager.beginTransaction()
+        if (path != null) {
+            Log.d(TAG, "onCreate: outgoing call $path ${intent.action}")
+            callFragment = TVCallFragment.newInstance(intent.action!!, path.accountId, path.conversationId,
+                intent.extras!!.getString(Intent.EXTRA_PHONE_NUMBER, path.conversationId), false)
+            fragmentTransaction.replace(R.id.main_call_layout, callFragment!!, CALL_FRAGMENT_TAG)
+                .commit()
+        } else {
+            Log.d(TAG, "onCreate: incoming call")
+            val confId = getIntent().getStringExtra(NotificationService.KEY_CALL_ID)
+            Log.d(TAG, "onCreate: conf $confId")
+            callFragment = TVCallFragment.newInstance(Intent.ACTION_VIEW, confId)
+            fragmentTransaction.replace(R.id.main_call_layout, callFragment!!, CALL_FRAGMENT_TAG)
+                .commit()
+        }
+    }
+
+    public override fun onUserLeaveHint() {
+        val fragment = supportFragmentManager.findFragmentByTag(CALL_FRAGMENT_TAG)
+        if (fragment is CallView) {
+            val callFragment = fragment as CallView
+            callFragment.onUserLeave()
+        }
+    }
+
+    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+        callFragment?.onKeyDown()
+        return super.onKeyDown(keyCode, event)
+    }
+
+    override fun onTouchEvent(event: MotionEvent): Boolean {
+        callFragment?.onKeyDown()
+        return super.onTouchEvent(event)
+    }
+
+    companion object {
+        val TAG = TVCallActivity::class.simpleName!!
+        private const val CALL_FRAGMENT_TAG = "CALL_FRAGMENT_TAG"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
deleted file mode 100644
index 39b372e7f..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.java
+++ /dev/null
@@ -1,828 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.call;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.ComponentName;
-import android.app.PictureInPictureParams;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.Matrix;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.SurfaceTexture;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.view.menu.MenuBuilder;
-import androidx.appcompat.view.menu.MenuPopupHelper;
-import androidx.appcompat.widget.PopupMenu;
-import androidx.percentlayout.widget.PercentFrameLayout;
-
-import android.support.v4.media.MediaMetadataCompat;
-import android.support.v4.media.session.MediaSessionCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Rational;
-import android.view.LayoutInflater;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AlphaAnimation;
-import android.widget.RelativeLayout;
-
-import com.rodolfonavalon.shaperipplelibrary.model.Circle;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-import javax.inject.Inject;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import net.jami.call.CallPresenter;
-import net.jami.call.CallView;
-import cx.ring.client.ContactDetailsActivity;
-import cx.ring.client.ConversationSelectionActivity;
-import cx.ring.databinding.ItemParticipantLabelBinding;
-import cx.ring.databinding.TvFragCallBinding;
-import cx.ring.fragments.CallFragment;
-import cx.ring.adapters.ConfParticipantAdapter;
-import cx.ring.fragments.ConversationFragment;
-
-import net.jami.daemon.JamiService;
-import net.jami.model.Contact;
-import net.jami.model.Conference;
-import net.jami.model.Call;
-import cx.ring.mvp.BaseSupportFragment;
-
-import net.jami.model.Uri;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import cx.ring.tv.main.HomeActivity;
-import cx.ring.utils.ActionHelper;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import cx.ring.views.AvatarDrawable;
-
-public class TVCallFragment extends BaseSupportFragment<CallPresenter> implements CallView {
-
-    public static final String TAG = TVCallFragment.class.getSimpleName();
-
-    public static final String KEY_ACTION = "action";
-    public static final String KEY_CONF_ID = "confId";
-    public static final String KEY_AUDIO_ONLY = "AUDIO_ONLY";
-
-    private static final int REQUEST_CODE_ADD_PARTICIPANT = 6;
-    private static final int REQUEST_PERMISSION_INCOMING = 1003;
-    private static final int REQUEST_PERMISSION_OUTGOING = 1004;
-
-    private TvFragCallBinding binding;
-
-    // Screen wake lock for incoming call
-    private Runnable runnable;
-    private int mPreviewWidth = 720, mPreviewHeight = 1280;
-    private int mPreviewWidthRot = 720, mPreviewHeightRot = 1280;
-    private PowerManager.WakeLock mScreenWakeLock;
-
-    private boolean mBackstackLost = false;
-    private boolean mTextureAvailable = false;
-    private ConfParticipantAdapter confAdapter = null;
-    private boolean mConferenceMode = false;
-
-    private int mVideoWidth = -1;
-    private int mVideoHeight = -1;
-
-    private final AlphaAnimation fadeOutAnimation = new AlphaAnimation(1, 0);
-
-    private MediaSessionCompat mSession;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    public static TVCallFragment newInstance(@NonNull String action, @NonNull String accountId, @NonNull String conversationId, @NonNull String contactUri, boolean audioOnly) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_ACTION, action);
-        bundle.putAll(ConversationPath.toBundle(accountId, conversationId));
-        bundle.putString(Intent.EXTRA_PHONE_NUMBER, contactUri);
-        bundle.putBoolean(KEY_AUDIO_ONLY, audioOnly);
-        TVCallFragment fragment = new TVCallFragment();
-        fragment.setArguments(bundle);
-        return fragment;
-    }
-
-    public static TVCallFragment newInstance(@NonNull String action, @Nullable String confId) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_ACTION, action);
-        bundle.putString(KEY_CONF_ID, confId);
-        TVCallFragment countDownFragment = new TVCallFragment();
-        countDownFragment.setArguments(bundle);
-        return countDownFragment;
-    }
-
-    public TVCallFragment() {
-        fadeOutAnimation.setInterpolator(new AccelerateInterpolator());
-        fadeOutAnimation.setStartOffset(1000);
-        fadeOutAnimation.setDuration(1000);
-    }
-
-    @Override
-    protected void initPresenter(CallPresenter presenter) {
-        super.initPresenter(presenter);
-        String action = getArguments().getString(KEY_ACTION);
-        if (action != null) {
-            if (action.equals(Intent.ACTION_CALL)) {
-                prepareCall(false);
-            } else if (action.equals(Intent.ACTION_VIEW)) {
-                presenter.initIncomingCall(getArguments().getString(KEY_CONF_ID), true);
-            }
-        }
-    }
-
-    @Override
-    public void handleCallWakelock(boolean isAudioOnly) { }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        binding = TvFragCallBinding.inflate(inflater, container, false);
-        binding.setPresenter(this);
-        return binding.getRoot();
-    }
-
-    private final TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
-        @Override
-        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-            configureTransform(width, height);
-            presenter.previewVideoSurfaceCreated(binding.previewSurface);
-            mTextureAvailable = true;
-        }
-
-        @Override
-        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
-            configureTransform(width, height);
-        }
-
-        @Override
-        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
-            presenter.previewVideoSurfaceDestroyed();
-            mTextureAvailable = false;
-            return true;
-        }
-
-        @Override
-        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
-        }
-    };
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (mScreenWakeLock != null && !mScreenWakeLock.isHeld()) {
-            mScreenWakeLock.acquire();
-        }
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mSession = new MediaSessionCompat(requireContext(), TAG);
-        mSession.setMetadata(new MediaMetadataCompat.Builder()
-                .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, getString(R.string.pip_title))
-                .build());
-
-        PowerManager powerManager = (PowerManager) requireContext().getSystemService(Context.POWER_SERVICE);
-        mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "ring:callLock");
-        mScreenWakeLock.setReferenceCounted(false);
-
-        binding.videoSurface.getHolder().setFormat(PixelFormat.RGBA_8888);
-        binding.videoSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
-            @Override
-            public void surfaceCreated(SurfaceHolder holder) {
-                presenter.videoSurfaceCreated(holder);
-            }
-
-            @Override
-            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-
-            }
-
-            @Override
-            public void surfaceDestroyed(SurfaceHolder holder) {
-                presenter.videoSurfaceDestroyed();
-            }
-        });
-
-        view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                resetVideoSize(mVideoWidth, mVideoHeight));
-
-        binding.previewSurface.setSurfaceTextureListener(listener);
-        binding.shapeRipple.setRippleShape(new Circle());
-        runnable = () -> presenter.uiVisibilityChanged(false);
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if(mTextureAvailable)
-            presenter.previewVideoSurfaceCreated(binding.previewSurface);
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
-            mScreenWakeLock.release();
-        }
-        mScreenWakeLock = null;
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-        presenter.hangupCall();
-        runnable = null;
-        binding = null;
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        if (mScreenWakeLock != null && mScreenWakeLock.isHeld()) {
-            mScreenWakeLock.release();
-        }
-        View view = getView();
-        Runnable r = runnable;
-        if (view != null && r != null) {
-            Handler handler = view.getHandler();
-            if (handler != null)
-                handler.removeCallbacks(r);
-        }
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING)
-            return;
-        for (int i = 0, n = permissions.length; i < n; i++) {
-            boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
-            boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
-            switch (permissions[i]) {
-                case Manifest.permission.CAMERA:
-                    presenter.cameraPermissionChanged(granted);
-                    if (audioGranted) {
-                        initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
-                    }
-                    break;
-                case Manifest.permission.RECORD_AUDIO:
-                    presenter.audioPermissionChanged(granted);
-                    initializeCall(requestCode == REQUEST_PERMISSION_INCOMING);
-                    break;
-            }
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        if (requestCode == REQUEST_CODE_ADD_PARTICIPANT) {
-            if (resultCode == Activity.RESULT_OK && data != null) {
-                ConversationPath path = ConversationPath.fromUri(data.getData());
-                if (path != null) {
-                    presenter.addConferenceParticipant(path.getAccountId(), path.getConversationUri());
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
-        if(!isInPictureInPictureMode) {
-            mBackstackLost = true;
-        }
-        presenter.pipModeChanged(isInPictureInPictureMode);
-    }
-
-    @Override
-    public void displayContactBubble(final boolean display) {
-        binding.contactBubbleLayout.setVisibility(display ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void displayVideoSurface(final boolean displayVideoSurface, final boolean displayPreviewContainer) {
-        binding.videoSurface.setVisibility(displayVideoSurface ? View.VISIBLE : View.GONE);
-        binding.previewContainer.setVisibility(displayPreviewContainer ? View.VISIBLE : View.GONE);
-    }
-
-    @Override
-    public void displayPreviewSurface(boolean display) {
-        if (display) {
-            binding.videoSurface.setZOrderOnTop(false);
-            //mVideoPreview.setZOrderMediaOverlay(true);
-            binding.videoSurface.setZOrderMediaOverlay(false);
-        } else {
-            binding.videoSurface.setZOrderMediaOverlay(true);
-            binding.videoSurface.setZOrderOnTop(true);
-        }
-    }
-
-    @Override
-    public void displayHangupButton(boolean display) {
-        binding.confControlGroup.setVisibility((mConferenceMode && display) ? View.VISIBLE : View.GONE);
-
-        if (display) {
-            binding.callHangupBtn.setVisibility(View.VISIBLE);
-            binding.callAddBtn.setVisibility(View.VISIBLE);
-        } else {
-            binding.callHangupBtn.startAnimation(fadeOutAnimation);
-            binding.callAddBtn.startAnimation(fadeOutAnimation);
-            binding.callHangupBtn.setVisibility(View.GONE);
-            binding.callAddBtn.setVisibility(View.GONE);
-        }
-        if (mConferenceMode && display) {
-            binding.confControlGroup.setVisibility(View.VISIBLE);
-        } else {
-            binding.confControlGroup.startAnimation(fadeOutAnimation);
-        }
-    }
-
-    @Override
-    public void displayDialPadKeyboard() {
-    }
-
-    @Override
-    public void switchCameraIcon(boolean isFront) {
-
-    }
-
-    @Override
-    public void updateAudioState(HardwareService.AudioState state) {
-
-    }
-
-    @Override
-    public void updateMenu() {
-
-    }
-
-    @Override
-    public void updateTime(final long duration) {
-        if (binding != null)
-            binding.callStatusTxt.setText(String.format(Locale.getDefault(), "%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60));
-    }
-
-    @Override
-    public void updateContactBubble(@NonNull final List<Call> calls) {
-        mConferenceMode = calls.size() > 1;
-        String username = mConferenceMode ? "Conference with " + calls.size() + " people" : calls.get(0).getContact().getRingUsername();
-        String displayName = mConferenceMode ? null : calls.get(0).getContact().getDisplayName();
-
-        Contact contact = calls.get(0).getContact();
-        String ringId = contact.getIds().get(0);
-        Log.d(TAG, "updateContactBubble: username=" + username + ", ringId=" + ringId + " photo:" + contact.getPhoto());
-
-        if (mSession != null) {
-            mSession.setMetadata(new MediaMetadataCompat.Builder()
-                    .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, displayName)
-                    .build());
-        }
-
-        boolean hasProfileName = displayName != null && !displayName.contentEquals(username);
-        if (hasProfileName) {
-            binding.contactBubbleNumTxt.setVisibility(View.VISIBLE);
-            binding.contactBubbleTxt.setText(displayName);
-            binding.contactBubbleNumTxt.setText(username);
-        } else {
-            binding.contactBubbleNumTxt.setVisibility(View.GONE);
-            binding.contactBubbleTxt.setText(username);
-        }
-        binding.contactBubble.setImageDrawable(
-                new AvatarDrawable.Builder()
-                        .withContact(contact)
-                        .withCircleCrop(true)
-                        .build(getActivity())
-        );
-
-        /*if (!mConferenceMode) {
-            binding.confControlGroup.setVisibility(View.GONE);
-        } else {
-            binding.confControlGroup.setVisibility(View.VISIBLE);
-            if (confAdapter  == null) {
-                confAdapter = new ConfParticipantAdapter((view, call) -> {
-                    Context context = requireContext();
-                    PopupMenu popup = new PopupMenu(context, view);
-                    popup.inflate(R.menu.conference_participant_actions);
-                    popup.setOnMenuItemClickListener(item -> {
-                        int itemId = item.getItemId();
-                        if (itemId == R.id.conv_contact_details) {
-                            presenter.openParticipantContact(call);
-                        } else if (itemId == R.id.conv_contact_hangup) {
-                            presenter.hangupParticipant(call);
-                        } else {
-                            return false;
-                        }
-                        return true;
-                    });
-                    MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), view);
-                    menuHelper.setForceShowIcon(true);
-                    menuHelper.show();
-                });
-            }
-            confAdapter.updateFromCalls(calls);
-            if (binding.confControlGroup.getAdapter() == null)
-                binding.confControlGroup.setAdapter(confAdapter);
-        }*/
-    }
-
-    @Override
-    public void updateCallStatus(final Call.CallStatus callStatus) {
-        switch (callStatus) {
-            case NONE:
-                binding.callStatusTxt.setText("");
-                break;
-            default:
-                binding.callStatusTxt.setText(CallFragment.callStateToHumanState(callStatus));
-                break;
-        }
-    }
-
-    @Override
-    public void initMenu(boolean isSpeakerOn, boolean displayFlip, boolean canDial,
-                         boolean showPluginBtn, boolean onGoingCall) {
-
-    }
-
-    @Override
-    public void initNormalStateDisplay(boolean audioOnly, boolean muted) {
-        mSession.setActive(true);
-
-        binding.shapeRipple.stopRipple();
-
-        binding.callAcceptBtn.setVisibility(View.GONE);
-        binding.callRefuseBtn.setVisibility(View.GONE);
-        binding.callHangupBtn.setVisibility(View.VISIBLE);
-
-        binding.contactBubbleLayout.setVisibility(audioOnly ? View.VISIBLE : View.INVISIBLE);
-
-        getActivity().invalidateOptionsMenu();
-
-        handleVisibilityTimer();
-    }
-
-    @Override
-    public void initIncomingCallDisplay() {
-        mSession.setActive(true);
-
-        binding.callAcceptBtn.setVisibility(View.VISIBLE);
-        binding.callAcceptBtn.requestFocus();
-        binding.callRefuseBtn.setVisibility(View.VISIBLE);
-        binding.callHangupBtn.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void initOutGoingCallDisplay() {
-        binding.callAcceptBtn.setVisibility(View.GONE);
-        binding.callRefuseBtn.setVisibility(View.VISIBLE);
-        binding.callHangupBtn.setVisibility(View.GONE);
-    }
-
-    @Override
-    public void resetPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
-        if (previewWidth == -1 && previewHeight == -1)
-            return;
-        mPreviewWidth = previewWidth;
-        mPreviewHeight = previewHeight;
-        boolean flip = (rot % 180) != 0;
-        binding.previewSurface.setAspectRatio(flip ? mPreviewHeight : mPreviewWidth, flip ? mPreviewWidth : mPreviewHeight);
-    }
-
-    @Override
-    public void resetPluginPreviewVideoSize(int previewWidth, int previewHeight, int rot) {
-    }
-
-    @Override
-    public void resetVideoSize(final int videoWidth, final int videoHeight) {
-        Log.w(TAG, "resetVideoSize " + videoWidth + "x" + videoHeight);
-        ViewGroup rootView = (ViewGroup) getView();
-        if (rootView == null)
-            return;
-
-        double videoRatio = videoWidth / (double) videoHeight;
-        double screenRatio = getView().getWidth() / (double) getView().getHeight();
-
-        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) binding.videoSurface.getLayoutParams();
-        int oldW = params.width;
-        int oldH = params.height;
-        if (videoRatio >= screenRatio) {
-            params.width = RelativeLayout.LayoutParams.MATCH_PARENT;
-            params.height = (int) (videoHeight * (double) rootView.getWidth() / (double) videoWidth);
-        } else {
-            params.height = RelativeLayout.LayoutParams.MATCH_PARENT;
-            params.width = (int) (videoWidth * (double) rootView.getHeight() / (double) videoHeight);
-        }
-
-        if (oldW != params.width || oldH != params.height) {
-            binding.videoSurface.setLayoutParams(params);
-        }
-        mVideoWidth = videoWidth;
-        mVideoHeight = videoHeight;
-    }
-
-    private void configureTransform(int viewWidth, int viewHeight) {
-        Activity activity = getActivity();
-        if (null == binding || null == activity) {
-            return;
-        }
-        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
-        boolean rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation;
-        Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot=" + rot + " mPreviewWidth=" + mPreviewWidth + " mPreviewHeight=" + mPreviewHeight);
-        Matrix matrix = new Matrix();
-        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
-        float centerX = viewRect.centerX();
-        float centerY = viewRect.centerY();
-        if (rot) {
-            RectF bufferRect = new RectF(0, 0, mPreviewHeightRot, mPreviewWidthRot);
-            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
-            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
-            float scale = Math.max(
-                    (float) viewHeight / mPreviewHeightRot,
-                    (float) viewWidth / mPreviewWidthRot);
-            matrix.postScale(scale, scale, centerX, centerY);
-            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
-        } else if (Surface.ROTATION_180 == rotation) {
-            matrix.postRotate(180, centerX, centerY);
-        }
-        binding.previewSurface.setTransform(matrix);
-    }
-
-    /**
-     * Checks if permissions are accepted for camera and microphone. Takes into account whether call is incoming and outgoing, and requests permissions if not available.
-     * Initializes the call if permissions are accepted.
-     *
-     * @param isIncoming true if call is incoming, false for outgoing
-     * @see #initializeCall(boolean) initializeCall
-     */
-    @Override
-    public void prepareCall(boolean isIncoming) {
-        Bundle args = getArguments();
-        boolean audioGranted = mDeviceRuntimeService.hasAudioPermission();
-        boolean audioOnly;
-        int permissionType;
-
-        if (isIncoming) {
-            audioOnly = presenter.isAudioOnly();
-            permissionType = REQUEST_PERMISSION_INCOMING;
-
-        } else {
-            audioOnly = args.getBoolean(KEY_AUDIO_ONLY);
-            permissionType = REQUEST_PERMISSION_OUTGOING;
-        }
-        if (!audioOnly) {
-            boolean videoGranted = mDeviceRuntimeService.hasVideoPermission();
-
-            if ((!audioGranted || !videoGranted) && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                ArrayList<String> perms = new ArrayList<>();
-                if (!videoGranted) {
-                    perms.add(Manifest.permission.CAMERA);
-                }
-                if (!audioGranted) {
-                    perms.add(Manifest.permission.RECORD_AUDIO);
-                }
-                requestPermissions(perms.toArray(new String[perms.size()]), permissionType);
-            } else if (audioGranted && videoGranted) {
-                initializeCall(isIncoming);
-            }
-        } else {
-            if (!audioGranted && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, permissionType);
-            } else if (audioGranted) {
-                initializeCall(isIncoming);
-            }
-        }
-    }
-
-
-
-    /**
-     * Starts a call. Takes into account whether call is incoming or outgoing.
-     *
-     * @param isIncoming true if call is incoming, false for outgoing
-     */
-    public void initializeCall(boolean isIncoming) {
-        if (isIncoming) {
-            presenter.acceptCall();
-        } else {
-            Bundle args;
-            args = getArguments();
-            if (args != null) {
-                ConversationPath conversation = ConversationPath.fromBundle(args);
-                presenter.initOutGoing(conversation.getAccountId(),
-                        conversation.getConversationUri(),
-                        args.getString(Intent.EXTRA_PHONE_NUMBER),
-                        args.getBoolean(KEY_AUDIO_ONLY));
-            }
-        }
-    }
-
-    @Override
-    public void goToContact(String accountId, Contact contact) {
-        startActivity(new Intent(Intent.ACTION_VIEW, android.net.Uri.withAppendedPath(android.net.Uri.withAppendedPath(ContentUriHandler.CONTACT_CONTENT_URI, accountId), contact.getPrimaryNumber()))
-                .setClass(requireContext(), ContactDetailsActivity.class));
-    }
-
-    @Override
-    public boolean displayPluginsButton() {
-        return false;
-    }
-
-    @Override
-    public void updateConfInfo(List<Conference.ParticipantInfo> info) {
-        binding.participantLabelContainer.removeAllViews();
-        if (!info.isEmpty()) {
-            LayoutInflater inflater = LayoutInflater.from(binding.participantLabelContainer.getContext());
-            for (Conference.ParticipantInfo i : info) {
-                String displayName = i.contact.getDisplayName();
-                if (!TextUtils.isEmpty(displayName)) {
-                    ItemParticipantLabelBinding label = ItemParticipantLabelBinding.inflate(inflater);
-                    PercentFrameLayout.LayoutParams params = new PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-                    params.getPercentLayoutInfo().leftMarginPercent = i.x / (float) mVideoWidth;
-                    params.getPercentLayoutInfo().topMarginPercent = i.y / (float) mVideoHeight;
-                    label.participantName.setText(displayName);
-                    binding.participantLabelContainer.addView(label.getRoot(), params);
-                }
-            }
-        }
-        binding.participantLabelContainer.setVisibility(info.isEmpty() ? View.GONE : View.VISIBLE);
-
-        if (!mConferenceMode) {
-            binding.confControlGroup.setVisibility(View.GONE);
-        } else {
-            binding.confControlGroup.setVisibility(View.VISIBLE);
-            if (confAdapter  == null) {
-                confAdapter = new ConfParticipantAdapter((view, call) -> {
-                    Context context = requireContext();
-                    PopupMenu popup = new PopupMenu(context, view);
-                    popup.inflate(R.menu.conference_participant_actions);
-                    popup.setOnMenuItemClickListener(item -> {
-                        int itemId = item.getItemId();
-                        if (itemId == R.id.conv_contact_details) {
-                            presenter.openParticipantContact(call);
-                        } else if (itemId == R.id.conv_contact_hangup) {
-                            presenter.hangupParticipant(call);
-                        } else {
-                            return false;
-                        }
-                        return true;
-                    });
-                    MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), view);
-                    menuHelper.setForceShowIcon(true);
-                    menuHelper.show();
-                });
-            }
-            confAdapter.updateFromCalls(info);
-            if (binding.confControlGroup.getAdapter() == null)
-                binding.confControlGroup.setAdapter(confAdapter);
-        }
-    }
-
-    @Override
-    public void updateParticipantRecording(Set<Contact> contacts) {
-
-    }
-
-    @Override
-    public void toggleCallMediaHandler(String id, String callId, boolean toggle) {
-        JamiService.toggleCallMediaHandler(id, callId, toggle);
-    }
-
-    @Override
-    public void goToConversation(String accountId, Uri conversationId) {
-
-    }
-
-    @Override
-    public void goToAddContact(Contact contact) {
-        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact),
-                ConversationFragment.REQ_ADD_CONTACT);
-    }
-
-    @Override
-    public void startAddParticipant(String conferenceId) {
-        startActivityForResult(
-                new Intent(Intent.ACTION_PICK)
-                        .setClass(requireActivity(), ConversationSelectionActivity.class)
-                        .putExtra(KEY_CONF_ID, conferenceId),
-                REQUEST_CODE_ADD_PARTICIPANT);
-    }
-
-    public void addParticipant() {
-        presenter.startAddParticipant();
-    }
-
-    public void hangUpClicked() {
-        presenter.hangupCall();
-    }
-
-    public void refuseClicked() {
-        presenter.refuseCall();
-    }
-
-    public void acceptClicked() {
-        prepareCall(true);
-    }
-
-    @Override
-    public void finish() {
-        Activity activity = getActivity();
-        if (mSession != null) {
-            mSession.setActive(false);
-        }
-        if (activity != null) {
-            if (mBackstackLost) {
-                activity.finishAndRemoveTask();
-                startActivity(
-                        Intent.makeMainActivity(
-                                new ComponentName(activity, HomeActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            } else {
-                activity.finish();
-            }
-        }
-    }
-
-    @Override
-    public void onUserLeave() {
-        presenter.requestPipMode();
-    }
-
-    @Override
-    public void enterPipMode(String callId) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-            return;
-        }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            PictureInPictureParams.Builder paramBuilder = new PictureInPictureParams.Builder();
-            if (binding.videoSurface.getVisibility() == View.VISIBLE) {
-                int[] l = new int[2];
-                binding.videoSurface.getLocationInWindow(l);
-                int x = l[0];
-                int y = l[1];
-                int w = binding.videoSurface.getWidth();
-                int h = binding.videoSurface.getHeight();
-                Rect videoBounds = new Rect(x, y, x + w, y + h);
-                paramBuilder.setAspectRatio(new Rational(w, h));
-                paramBuilder.setSourceRectHint(videoBounds);
-            }
-            requireActivity().enterPictureInPictureMode(paramBuilder.build());
-        } else {
-            requireActivity().enterPictureInPictureMode();
-        }
-    }
-
-    public void onKeyDown() {
-        handleVisibilityTimer();
-    }
-
-    private void handleVisibilityTimer() {
-        presenter.uiVisibilityChanged(true);
-        View view = getView();
-        Runnable r = runnable;
-        if (view != null && r != null) {
-            Handler handler = view.getHandler();
-            if (handler != null) {
-                handler.removeCallbacks(r);
-                handler.postDelayed(r, 5000);
-            }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
new file mode 100644
index 000000000..5fca0317e
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
@@ -0,0 +1,744 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.call
+
+import android.Manifest
+import android.app.Activity
+import android.app.PictureInPictureParams
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.*
+import android.os.Build
+import android.os.Bundle
+import android.os.PowerManager
+import android.support.v4.media.MediaMetadataCompat
+import android.support.v4.media.session.MediaSessionCompat
+import android.text.TextUtils
+import android.util.Log
+import android.util.Rational
+import android.view.*
+import android.view.TextureView.SurfaceTextureListener
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import android.view.animation.LinearInterpolator
+import android.widget.RelativeLayout
+import androidx.appcompat.view.menu.MenuBuilder
+import androidx.appcompat.view.menu.MenuPopupHelper
+import androidx.appcompat.widget.PopupMenu
+import androidx.percentlayout.widget.PercentFrameLayout
+import com.rodolfonavalon.shaperipplelibrary.model.Circle
+import cx.ring.R
+import cx.ring.adapters.ConfParticipantAdapter
+import cx.ring.adapters.ConfParticipantAdapter.ConfParticipantSelected
+import cx.ring.client.CallActivity
+import cx.ring.client.ContactDetailsActivity
+import cx.ring.client.ConversationSelectionActivity
+import cx.ring.databinding.ItemParticipantLabelBinding
+import cx.ring.databinding.TvFragCallBinding
+import cx.ring.fragments.CallFragment
+import cx.ring.fragments.ConversationFragment
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.tv.main.HomeActivity
+import cx.ring.utils.ActionHelper
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ConversationPath
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import net.jami.call.CallPresenter
+import net.jami.call.CallView
+import net.jami.daemon.JamiService
+import net.jami.model.Call
+import net.jami.model.Call.CallStatus
+import net.jami.model.Conference.ParticipantInfo
+import net.jami.model.Contact
+import net.jami.model.Uri
+import net.jami.services.DeviceRuntimeService
+import net.jami.services.HardwareService.AudioState
+import java.util.*
+import javax.inject.Inject
+import kotlin.math.max
+
+@AndroidEntryPoint
+class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView {
+    private var binding: TvFragCallBinding? = null
+
+    // Screen wake lock for incoming call
+    private var runnable: Runnable? = null
+    private var mPreviewWidth = 720
+    private var mPreviewHeight = 1280
+    private val mPreviewWidthRot = 720
+    private val mPreviewHeightRot = 1280
+    private var mScreenWakeLock: PowerManager.WakeLock? = null
+    private var mBackstackLost = false
+    private var mTextureAvailable = false
+    private var confAdapter: ConfParticipantAdapter? = null
+    private var mConferenceMode = false
+    private var mVideoWidth = -1
+    private var mVideoHeight = -1
+    private val fadeOutAnimation: Animation by lazy { AlphaAnimation(1f, 0f).apply {
+        interpolator = AccelerateInterpolator()
+        startOffset = 1000
+        duration = 1000
+    }}
+    private val blinkingAnimation: Animation by lazy { AlphaAnimation(1f, 0f).apply {
+        duration = 400
+        interpolator = LinearInterpolator()
+        repeatCount = Animation.INFINITE
+        repeatMode = Animation.REVERSE
+    }}
+    private var mSession: MediaSessionCompat? = null
+
+    @Inject
+    lateinit var mDeviceRuntimeService: DeviceRuntimeService
+
+    override fun initPresenter(presenter: CallPresenter) {
+        val args = requireArguments()
+        args.getString(CallFragment.KEY_ACTION)?.let { action ->
+            if (action == CallFragment.ACTION_PLACE_CALL || action == Intent.ACTION_CALL)
+                prepareCall(false)
+            else if (action == CallFragment.ACTION_GET_CALL || action == CallActivity.ACTION_CALL_ACCEPT)
+                presenter.initIncomingCall(args.getString(CallFragment.KEY_CONF_ID)!!, action == CallFragment.ACTION_GET_CALL)
+        }
+    }
+
+    override fun handleCallWakelock(isAudioOnly: Boolean) {}
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+        return TvFragCallBinding.inflate(inflater, container, false).also { b ->
+            b.presenter = this
+            binding = b
+        }.root
+    }
+
+    private val listener: SurfaceTextureListener = object : SurfaceTextureListener {
+        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+            configureTransform(width, height)
+            presenter.previewVideoSurfaceCreated(binding!!.previewSurface)
+            mTextureAvailable = true
+        }
+
+        override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
+            configureTransform(width, height)
+        }
+
+        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
+            presenter.previewVideoSurfaceDestroyed()
+            mTextureAvailable = false
+            return true
+        }
+
+        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
+    }
+
+    override fun onStart() {
+        super.onStart()
+        mScreenWakeLock?.apply {
+            if (!isHeld) acquire()
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        Log.w(TAG, "onViewCreated");
+        mSession = MediaSessionCompat(requireContext(), TAG).apply {
+            setMetadata(MediaMetadataCompat.Builder()
+                .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, getString(R.string.pip_title))
+                .build())
+        }
+        val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
+        mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP or PowerManager.ON_AFTER_RELEASE, "ring:callLock")
+            .apply { setReferenceCounted(false) }
+        binding!!.videoSurface.holder.setFormat(PixelFormat.RGBA_8888)
+        binding!!.videoSurface.holder.addCallback(object : SurfaceHolder.Callback {
+            override fun surfaceCreated(holder: SurfaceHolder) {
+                presenter.videoSurfaceCreated(holder)
+            }
+
+            override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
+
+            override fun surfaceDestroyed(holder: SurfaceHolder) {
+                presenter.videoSurfaceDestroyed()
+            }
+        })
+        view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> resetVideoSize(mVideoWidth, mVideoHeight) }
+        binding!!.previewSurface.surfaceTextureListener = listener
+        binding!!.shapeRipple.rippleShape = Circle()
+        runnable = Runnable { presenter.uiVisibilityChanged(false) }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (mTextureAvailable) presenter.previewVideoSurfaceCreated(binding!!.previewSurface)
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        if (mScreenWakeLock != null && mScreenWakeLock!!.isHeld) {
+            mScreenWakeLock!!.release()
+        }
+        mScreenWakeLock = null
+        if (mSession != null) {
+            mSession!!.release()
+            mSession = null
+        }
+        presenter.hangupCall()
+        runnable = null
+        binding = null
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (mScreenWakeLock != null && mScreenWakeLock!!.isHeld) {
+            mScreenWakeLock!!.release()
+        }
+        val r = runnable
+        if (r != null) {
+            view?.handler?.removeCallbacks(r)
+        }
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING) return
+        var i = 0
+        val n = permissions.size
+        while (i < n) {
+            val audioGranted = mDeviceRuntimeService.hasAudioPermission()
+            val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+            when (permissions[i]) {
+                Manifest.permission.CAMERA -> {
+                    presenter.cameraPermissionChanged(granted)
+                    if (audioGranted) {
+                        initializeCall(requestCode == REQUEST_PERMISSION_INCOMING)
+                    }
+                }
+                Manifest.permission.RECORD_AUDIO -> {
+                    presenter.audioPermissionChanged(granted)
+                    initializeCall(requestCode == REQUEST_PERMISSION_INCOMING)
+                }
+            }
+            i++
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == REQUEST_CODE_ADD_PARTICIPANT) {
+            if (resultCode == Activity.RESULT_OK && data != null) {
+                val path = ConversationPath.fromUri(data.data)
+                if (path != null) {
+                    presenter.addConferenceParticipant(path.accountId, path.conversationUri)
+                }
+            }
+        }
+    }
+
+    override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+        if (!isInPictureInPictureMode) {
+            mBackstackLost = true
+        }
+        presenter.pipModeChanged(isInPictureInPictureMode)
+    }
+
+    override fun displayContactBubble(display: Boolean) {
+        binding!!.contactBubbleLayout.visibility = if (display) View.VISIBLE else View.GONE
+    }
+
+    override fun displayVideoSurface(
+        displayVideoSurface: Boolean,
+        displayPreviewContainer: Boolean
+    ) {
+        binding!!.videoSurface.visibility =
+            if (displayVideoSurface) View.VISIBLE else View.GONE
+        binding!!.previewContainer.visibility =
+            if (displayPreviewContainer) View.VISIBLE else View.GONE
+    }
+
+    override fun displayPreviewSurface(display: Boolean) {
+        if (display) {
+            binding!!.videoSurface.setZOrderOnTop(false)
+            //mVideoPreview.setZOrderMediaOverlay(true);
+            binding!!.videoSurface.setZOrderMediaOverlay(false)
+        } else {
+            binding!!.videoSurface.setZOrderMediaOverlay(true)
+            binding!!.videoSurface.setZOrderOnTop(true)
+        }
+    }
+
+    override fun displayHangupButton(display: Boolean) {
+        binding?.apply {
+            confControlGroup!!.visibility = if (mConferenceMode && display) View.VISIBLE else View.GONE
+            if (display) {
+                callHangupBtn.visibility = View.VISIBLE
+                callAddBtn.visibility = View.VISIBLE
+            } else {
+                callHangupBtn.startAnimation(fadeOutAnimation)
+                callAddBtn.startAnimation(fadeOutAnimation)
+                callHangupBtn.visibility = View.GONE
+                callAddBtn.visibility = View.GONE
+            }
+            if (mConferenceMode && display) {
+                confControlGroup.visibility = View.VISIBLE
+            } else {
+                confControlGroup.startAnimation(fadeOutAnimation)
+            }
+        }
+    }
+
+    override fun displayDialPadKeyboard() {}
+    override fun switchCameraIcon(isFront: Boolean) {}
+    override fun updateAudioState(state: AudioState) {}
+    override fun updateMenu() {}
+    override fun updateTime(duration: Long) {
+        binding?.callStatusTxt?.text = String.format(
+            Locale.getDefault(),
+            "%d:%02d:%02d",
+            duration / 3600,
+            duration % 3600 / 60,
+            duration % 60
+        )
+    }
+
+    override fun updateContactBubble(calls: List<Call>) {
+        mConferenceMode = calls.size > 1
+        val contact = calls[0].contact!!
+        val username = if (mConferenceMode) "Conference with " + calls.size + " people" else contact.ringUsername
+        val displayName = if (mConferenceMode) null else contact.displayName
+        Log.d(TAG, "updateContactBubble: username=" + username + ", uri=" + contact.uri + " photo:" + contact.photo)
+        mSession?.setMetadata(MediaMetadataCompat.Builder()
+                .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, displayName)
+                .build())
+        val hasProfileName = displayName != null && !displayName.contentEquals(username)
+        if (hasProfileName) {
+            binding!!.contactBubbleNumTxt.visibility = View.VISIBLE
+            binding!!.contactBubbleTxt.text = displayName
+            binding!!.contactBubbleNumTxt.text = username
+        } else {
+            binding!!.contactBubbleNumTxt.visibility = View.GONE
+            binding!!.contactBubbleTxt.text = username
+        }
+        binding!!.contactBubble.setImageDrawable(
+            AvatarDrawable.Builder()
+                .withContact(contact)
+                .withCircleCrop(true)
+                .build(requireActivity())
+        )
+
+        /*if (!mConferenceMode) {
+            binding.confControlGroup.setVisibility(View.GONE);
+        } else {
+            binding.confControlGroup.setVisibility(View.VISIBLE);
+            if (confAdapter  == null) {
+                confAdapter = new ConfParticipantAdapter((view, call) -> {
+                    Context context = requireContext();
+                    PopupMenu popup = new PopupMenu(context, view);
+                    popup.inflate(R.menu.conference_participant_actions);
+                    popup.setOnMenuItemClickListener(item -> {
+                        int itemId = item.getItemId();
+                        if (itemId == R.id.conv_contact_details) {
+                            presenter.openParticipantContact(call);
+                        } else if (itemId == R.id.conv_contact_hangup) {
+                            presenter.hangupParticipant(call);
+                        } else {
+                            return false;
+                        }
+                        return true;
+                    });
+                    MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) popup.getMenu(), view);
+                    menuHelper.setForceShowIcon(true);
+                    menuHelper.show();
+                });
+            }
+            confAdapter.updateFromCalls(calls);
+            if (binding.confControlGroup.getAdapter() == null)
+                binding.confControlGroup.setAdapter(confAdapter);
+        }*/
+    }
+
+    override fun updateCallStatus(callStatus: CallStatus) {
+        when (callStatus) {
+            CallStatus.NONE -> binding!!.callStatusTxt.text = ""
+            else -> binding!!.callStatusTxt.setText(CallFragment.callStateToHumanState(callStatus))
+        }
+    }
+
+    override fun initMenu(
+        isSpeakerOn: Boolean, displayFlip: Boolean, canDial: Boolean,
+        showPluginBtn: Boolean, onGoingCall: Boolean
+    ) {
+    }
+
+    override fun initNormalStateDisplay(audioOnly: Boolean, muted: Boolean) {
+        mSession!!.isActive = true
+        binding?.apply {
+            shapeRipple.stopRipple()
+            callAcceptBtn.visibility = View.GONE
+            callRefuseBtn.visibility = View.GONE
+            callHangupBtn.visibility = View.VISIBLE
+            contactBubbleLayout.visibility = if (audioOnly) View.VISIBLE else View.INVISIBLE
+        }
+        requireActivity().invalidateOptionsMenu()
+        handleVisibilityTimer()
+    }
+
+    override fun initIncomingCallDisplay() {
+        mSession!!.isActive = true
+        binding?.apply {
+            callAcceptBtn.visibility = View.VISIBLE
+            callAcceptBtn.requestFocus()
+            callRefuseBtn.visibility = View.VISIBLE
+            callHangupBtn.visibility = View.GONE
+        }
+    }
+
+    override fun initOutGoingCallDisplay() {
+        binding?.apply {
+            callAcceptBtn.visibility = View.GONE
+            callRefuseBtn.visibility = View.VISIBLE
+            callHangupBtn.visibility = View.GONE
+        }
+    }
+
+    override fun resetPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) {
+        if (previewWidth == -1 && previewHeight == -1) return
+        mPreviewWidth = previewWidth
+        mPreviewHeight = previewHeight
+        val flip = rot % 180 != 0
+        binding!!.previewSurface.setAspectRatio(
+            if (flip) mPreviewHeight else mPreviewWidth,
+            if (flip) mPreviewWidth else mPreviewHeight
+        )
+    }
+
+    override fun resetPluginPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) {}
+
+    override fun resetVideoSize(videoWidth: Int, videoHeight: Int) {
+        Log.w(TAG, "resetVideoSize " + videoWidth + "x" + videoHeight)
+        val rootView = view as ViewGroup? ?: return
+        val videoRatio = videoWidth / videoHeight.toDouble()
+        val screenRatio = rootView.width / rootView.height.toDouble()
+        val params = binding!!.videoSurface.layoutParams as RelativeLayout.LayoutParams
+        val oldW = params.width
+        val oldH = params.height
+        if (videoRatio >= screenRatio) {
+            params.width = RelativeLayout.LayoutParams.MATCH_PARENT
+            params.height = (videoHeight * rootView.width.toDouble() / videoWidth.toDouble()).toInt()
+        } else {
+            params.height = RelativeLayout.LayoutParams.MATCH_PARENT
+            params.width = (videoWidth * rootView.height.toDouble() / videoHeight.toDouble()).toInt()
+        }
+        if (oldW != params.width || oldH != params.height) {
+            binding!!.videoSurface.layoutParams = params
+        }
+        mVideoWidth = videoWidth
+        mVideoHeight = videoHeight
+    }
+
+    private fun configureTransform(viewWidth: Int, viewHeight: Int) {
+        val activity: Activity? = activity
+        if (null == binding || null == activity) {
+            return
+        }
+        val rotation = activity.windowManager.defaultDisplay.rotation
+        val rot = Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation
+        Log.w(TAG, "configureTransform " + viewWidth + "x" + viewHeight + " rot=" + rot + " mPreviewWidth=" + mPreviewWidth + " mPreviewHeight=" + mPreviewHeight)
+        val matrix = Matrix()
+        val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
+        val centerX = viewRect.centerX()
+        val centerY = viewRect.centerY()
+        if (rot) {
+            val bufferRect = RectF(0f, 0f, mPreviewHeightRot.toFloat(), mPreviewWidthRot.toFloat())
+            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
+            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
+            val scale = max(viewHeight.toFloat() / mPreviewHeightRot, viewWidth.toFloat() / mPreviewWidthRot)
+            matrix.postScale(scale, scale, centerX, centerY)
+            matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
+        } else if (Surface.ROTATION_180 == rotation) {
+            matrix.postRotate(180f, centerX, centerY)
+        }
+        binding!!.previewSurface.setTransform(matrix)
+    }
+
+    /**
+     * Checks if permissions are accepted for camera and microphone. Takes into account whether call is incoming and outgoing, and requests permissions if not available.
+     * Initializes the call if permissions are accepted.
+     *
+     * @param isIncoming true if call is incoming, false for outgoing
+     * @see .initializeCall
+     */
+    override fun prepareCall(isIncoming: Boolean) {
+        val audioGranted = mDeviceRuntimeService.hasAudioPermission()
+        val audioOnly: Boolean
+        val permissionType: Int
+        if (isIncoming) {
+            audioOnly = presenter.isAudioOnly
+            permissionType = REQUEST_PERMISSION_INCOMING
+        } else {
+            audioOnly = requireArguments().getBoolean(CallFragment.KEY_AUDIO_ONLY)
+            permissionType = REQUEST_PERMISSION_OUTGOING
+        }
+        if (!audioOnly) {
+            val videoGranted = mDeviceRuntimeService.hasVideoPermission()
+            if ((!audioGranted || !videoGranted) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                val perms = ArrayList<String>()
+                if (!videoGranted) {
+                    perms.add(Manifest.permission.CAMERA)
+                }
+                if (!audioGranted) {
+                    perms.add(Manifest.permission.RECORD_AUDIO)
+                }
+                requestPermissions(perms.toTypedArray(), permissionType)
+            } else if (audioGranted && videoGranted) {
+                initializeCall(isIncoming)
+            }
+        } else {
+            if (!audioGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), permissionType)
+            } else if (audioGranted) {
+                initializeCall(isIncoming)
+            }
+        }
+    }
+
+    /**
+     * Starts a call. Takes into account whether call is incoming or outgoing.
+     *
+     * @param isIncoming true if call is incoming, false for outgoing
+     */
+    private fun initializeCall(isIncoming: Boolean) {
+        Log.w(TAG, "initializeCall $isIncoming")
+        if (isIncoming) {
+            presenter.acceptCall()
+        } else {
+            arguments?.let { args ->
+                Log.w(TAG, "initializeCall presenter.initOutGoing")
+                val conversation = ConversationPath.fromBundle(args)!!
+                presenter.initOutGoing(
+                    conversation.accountId,
+                    conversation.conversationUri,
+                    args.getString(Intent.EXTRA_PHONE_NUMBER),
+                    args.getBoolean(CallFragment.KEY_AUDIO_ONLY)
+                )
+            }
+        }
+    }
+
+    override fun goToContact(accountId: String, contact: Contact) {
+        startActivity(Intent(Intent.ACTION_VIEW,
+            android.net.Uri.withAppendedPath(
+                android.net.Uri.withAppendedPath(
+                    ContentUriHandler.CONTACT_CONTENT_URI,
+                    accountId
+                ), contact.primaryNumber))
+            .setClass(requireContext(), ContactDetailsActivity::class.java)
+        )
+    }
+
+    override fun displayPluginsButton(): Boolean {
+        return false
+    }
+
+    override fun updateConfInfo(info: List<ParticipantInfo>) {
+        val binding = binding!!
+        binding.participantLabelContainer.removeAllViews()
+        if (info.isNotEmpty()) {
+            val inflater = LayoutInflater.from(binding.participantLabelContainer.context)
+            for (i in info) {
+                val displayName = i.contact.displayName
+                if (!TextUtils.isEmpty(displayName)) {
+                    val label = ItemParticipantLabelBinding.inflate(inflater)
+                    val params = PercentFrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+                    params.percentLayoutInfo.leftMarginPercent = i.x / mVideoWidth.toFloat()
+                    params.percentLayoutInfo.topMarginPercent = i.y / mVideoHeight.toFloat()
+                    label.participantName.text = displayName
+                    binding.participantLabelContainer.addView(label.root, params)
+                }
+            }
+        }
+        binding.participantLabelContainer.visibility = if (info.isEmpty()) View.GONE else View.VISIBLE
+        if (!mConferenceMode) {
+            binding.confControlGroup!!.visibility = View.GONE
+        } else {
+            binding.confControlGroup!!.visibility = View.VISIBLE
+            if (confAdapter == null) {
+                confAdapter = ConfParticipantAdapter(object : ConfParticipantSelected {
+                    override fun onParticipantSelected(view: View, contact: ParticipantInfo) {
+                        val context = requireContext()
+                        val popup = PopupMenu(context, view)
+                        popup.inflate(R.menu.conference_participant_actions)
+                        popup.setOnMenuItemClickListener { item: MenuItem ->
+                            when (item.itemId) {
+                                R.id.conv_contact_details -> presenter.openParticipantContact(contact)
+                                R.id.conv_contact_hangup -> presenter.hangupParticipant(contact)
+                                else -> return@setOnMenuItemClickListener false
+                            }
+                            true
+                        }
+                        val menuHelper = MenuPopupHelper(context, (popup.menu as MenuBuilder), view)
+                        menuHelper.setForceShowIcon(true)
+                        menuHelper.show()
+                    }
+                })
+            }
+            confAdapter!!.updateFromCalls(info)
+            if (binding.confControlGroup.adapter == null)
+                binding.confControlGroup.adapter = confAdapter
+        }
+    }
+
+    override fun updateParticipantRecording(contacts: Set<Contact>) {
+        binding?.let { binding ->
+            if (contacts.isEmpty()) {
+                binding.recordLayout.visibility = View.INVISIBLE
+                binding.recordIndicator.clearAnimation()
+                return
+            }
+            val names = StringBuilder()
+            val contact = contacts.iterator()
+            for (i in contacts.indices) {
+                names.append(" ").append(contact.next().displayName)
+                if (i != contacts.size - 1) {
+                    names.append(",")
+                }
+            }
+            binding.recordLayout.visibility = View.VISIBLE
+            binding.recordIndicator.animation = blinkingAnimation
+            binding.recordName.text = getString(R.string.remote_recording, names)
+        }
+    }
+
+    override fun toggleCallMediaHandler(id: String, callId: String, toggle: Boolean) {
+        JamiService.toggleCallMediaHandler(id, callId, toggle)
+    }
+
+    override fun goToConversation(accountId: String, conversationId: Uri) {}
+    override fun goToAddContact(contact: Contact) {
+        startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), ConversationFragment.REQ_ADD_CONTACT)
+    }
+
+    override fun startAddParticipant(conferenceId: String) {
+        startActivityForResult(Intent(Intent.ACTION_PICK)
+                .setClass(requireActivity(), ConversationSelectionActivity::class.java)
+                .putExtra(CallFragment.KEY_CONF_ID, conferenceId),
+            REQUEST_CODE_ADD_PARTICIPANT)
+    }
+
+    fun addParticipant() {
+        presenter.startAddParticipant()
+    }
+
+    fun hangUpClicked() {
+        presenter.hangupCall()
+    }
+
+    fun refuseClicked() {
+        presenter.refuseCall()
+    }
+
+    fun acceptClicked() {
+        prepareCall(true)
+    }
+
+    override fun finish() {
+        mSession?.isActive = false
+        activity?.let { activity ->
+            if (mBackstackLost) {
+                activity.finishAndRemoveTask()
+                startActivity(Intent.makeMainActivity(ComponentName(activity, HomeActivity::class.java))
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+            } else {
+                activity.finish()
+            }
+        }
+    }
+
+    override fun onUserLeave() {
+        presenter.requestPipMode()
+    }
+
+    override fun enterPipMode(callId: String) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+            return
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val paramBuilder = PictureInPictureParams.Builder()
+            if (binding!!.videoSurface.visibility == View.VISIBLE) {
+                val l = IntArray(2)
+                binding!!.videoSurface.getLocationInWindow(l)
+                val x = l[0]
+                val y = l[1]
+                val w = binding!!.videoSurface.width
+                val h = binding!!.videoSurface.height
+                val videoBounds = Rect(x, y, x + w, y + h)
+                paramBuilder.setAspectRatio(Rational(w, h))
+                paramBuilder.setSourceRectHint(videoBounds)
+            }
+            requireActivity().enterPictureInPictureMode(paramBuilder.build())
+        } else {
+            requireActivity().enterPictureInPictureMode()
+        }
+    }
+
+    fun onKeyDown() {
+        handleVisibilityTimer()
+    }
+
+    private fun handleVisibilityTimer() {
+        presenter.uiVisibilityChanged(true)
+        val view = view
+        val r = runnable
+        if (view != null && r != null) {
+            val handler = view.handler
+            if (handler != null) {
+                handler.removeCallbacks(r)
+                handler.postDelayed(r, 5000)
+            }
+        }
+    }
+
+    companion object {
+        private val TAG = TVCallFragment::class.simpleName!!
+        private const val REQUEST_CODE_ADD_PARTICIPANT = 6
+        private const val REQUEST_PERMISSION_INCOMING = 1003
+        private const val REQUEST_PERMISSION_OUTGOING = 1004
+
+        fun newInstance(action: String, accountId: String, conversationId: String, contactUri: String, audioOnly: Boolean): TVCallFragment {
+            return TVCallFragment().apply { arguments = Bundle().apply {
+                putString(CallFragment.KEY_ACTION, action)
+                putAll(ConversationPath.toBundle(accountId, conversationId))
+                putString(Intent.EXTRA_PHONE_NUMBER, contactUri)
+                putBoolean(CallFragment.KEY_AUDIO_ONLY, audioOnly)
+            }}
+        }
+
+        fun newInstance(action: String, confId: String?): TVCallFragment {
+            return TVCallFragment().apply { arguments = Bundle().apply {
+                putString(CallFragment.KEY_ACTION, action)
+                putString(CallFragment.KEY_CONF_ID, confId)
+            }}
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java b/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java
deleted file mode 100644
index 2c503874d..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Loïc Siret <loic.siret@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package cx.ring.tv.camera;
-
-import android.animation.Animator;
-import android.app.Activity;
-import android.content.Intent;
-import android.hardware.Camera;
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder;
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.widget.Toast;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import cx.ring.R;
-import cx.ring.databinding.CamerapickerBinding;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.ContentUriHandler;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-@SuppressWarnings("deprecation")
-public class CustomCameraActivity extends Activity {
-    private static final String TAG = "CustomCameraActivity";
-    public static final String TYPE_IMAGE = "image/jpeg";
-    public static final String TYPE_VIDEO = "video";
-
-    private CamerapickerBinding binding;
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-
-    private int cameraFront = -1;
-    private int cameraBack = -1;
-    private int currentCamera = 0;
-
-    private MediaRecorder recorder;
-    private boolean mRecording = false;
-    private boolean mActionVideo = false;
-
-    private File mVideoFile;
-
-    private Camera mCamera;
-    private CameraPreview mCameraPreview;
-    private final Camera.PictureCallback mPicture = (input, camera) -> mDisposableBag.add(Single.fromCallable(() ->  {
-            if (mCameraPreview != null)
-                mCameraPreview.stop();
-            File file = AndroidFileUtils.createImageFile(this);
-            try (OutputStream out = new FileOutputStream(file)) {
-                out.write(input);
-                out.flush();
-            }
-            return ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file);
-        })
-        .subscribeOn(Schedulers.io())
-        .observeOn(AndroidSchedulers.mainThread())
-        .subscribe(uri -> {
-                setResult(RESULT_OK, new Intent()
-                        .putExtra(MediaStore.EXTRA_OUTPUT, uri)
-                        .setType(TYPE_IMAGE));
-                finish();
-            }, e -> {
-                Log.e(TAG, "Error saving picture", e);
-                setResult(RESULT_CANCELED);
-                finish();
-            }));
-
-    public void takePicture() {
-        if (mRecording)
-            releaseMediaRecorder();
-        if (mCamera != null) {
-            binding.buttonPicture.setEnabled(false);
-            binding.buttonVideo.setVisibility(View.GONE);
-            try {
-                mCamera.takePicture(null, null, mPicture);
-            } catch (Exception e) {
-                Toast.makeText(this, "Error taking picture", Toast.LENGTH_LONG).show();
-                finish();
-            }
-        }
-    }
-
-    public void takeVideo() {
-        if (mRecording) {
-            releaseMediaRecorder();
-            mCameraPreview.stop();
-            Intent intent = new Intent()
-                    .putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, mVideoFile))
-                    .setType(TYPE_VIDEO);
-            setResult(RESULT_OK, intent);
-            binding.buttonVideo.setImageResource(R.drawable.baseline_videocam_24);
-            finish();
-        } else {
-            if (mCamera != null) {
-                initRecorder();
-                binding.buttonVideo.setImageResource(R.drawable.lb_ic_stop);
-                binding.buttonPicture.setVisibility(View.GONE);
-            }
-        }
-        mRecording = !mRecording;
-    }
-
-    /**
-     * Called when the activity is first created.
-     */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        binding = CamerapickerBinding.inflate(getLayoutInflater());
-
-        if (getIntent().getAction() != null) {
-            mActionVideo = getIntent().getAction().equals(MediaStore.ACTION_VIDEO_CAPTURE);
-        }
-
-        binding.buttonVideo.setEnabled(false);
-        binding.buttonPicture.setEnabled(false);
-        if (mActionVideo) {
-            binding.buttonVideo.setVisibility(View.VISIBLE);
-        }
-        setContentView(binding.getRoot());
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        mDisposableBag.add(Single.fromCallable(this::getCameraInstance)
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(camera -> {
-                    if (binding == null) {
-                        camera.release();
-                    } else {
-                        mCamera = camera;
-                        mCameraPreview = new CameraPreview(this, mCamera);
-                        binding.cameraPreview.addView(mCameraPreview, 0);
-                        binding.buttonVideo.setEnabled(true);
-                        binding.buttonPicture.setEnabled(true);
-                        binding.buttonPicture.setOnClickListener(v -> takePicture());
-                        binding.buttonVideo.setOnClickListener(v -> takeVideo());
-
-                        int endRadius = Math.max(binding.getRoot().getWidth(), binding.getRoot().getHeight());
-                        int x = binding.getRoot().getWidth()/2;
-                        int y = binding.getRoot().getHeight()/2;
-
-                        if (binding.loadClip.getVisibility() == View.VISIBLE) {
-                            Animator anim = ViewAnimationUtils.createCircularReveal(binding.loadClip, x, y, endRadius, 0);
-                            anim.addListener(new Animator.AnimatorListener() {
-                                @Override
-                                public void onAnimationStart(Animator animator) {
-                                }
-
-                                @Override
-                                public void onAnimationEnd(Animator animator) {
-                                    binding.loadClip.setVisibility(View.GONE);
-                                }
-
-                                @Override
-                                public void onAnimationCancel(Animator animator) {
-                                }
-
-                                @Override
-                                public void onAnimationRepeat(Animator animator) {
-                                }
-                            });
-                            anim.setDuration(600);
-                            anim.setStartDelay(50);
-                            anim.start();
-                        }
-                    }
-                }, e -> {
-                    Toast.makeText(this, "Can't open camera", Toast.LENGTH_LONG).show();
-                    finish();
-                }));
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        if (mCameraPreview != null) {
-            mCameraPreview.stop();
-            mCameraPreview = null;
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mDisposableBag.dispose();
-        binding = null;
-        if (mCamera != null) {
-            mCamera.release();
-            mCamera = null;
-        }
-    }
-
-    public void initVideo() {
-        int numberCameras = Camera.getNumberOfCameras();
-        if (numberCameras == 0)
-            return;
-        Camera.CameraInfo camInfo = new Camera.CameraInfo();
-        for (int i = 0; i < numberCameras; i++) {
-            Camera.getCameraInfo(i, camInfo);
-            if (camInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
-                cameraFront = i;
-            } else {
-                cameraBack = i;
-            }
-        }
-        currentCamera = cameraFront == -1 ? cameraBack : cameraFront;
-    }
-
-    /**
-     * Helper method to access the camera returns null if it cannot get the
-     * camera or does not exist
-     */
-    private Camera getCameraInstance() {
-        initVideo();
-        return Camera.open(currentCamera);
-    }
-
-    private void initRecorder() {
-        int videoWidth = mCamera.getParameters().getPreviewSize().width;
-        int videoHeight = mCamera.getParameters().getPreviewSize().height;
-        mCamera.unlock();
-        recorder = new MediaRecorder();
-        recorder.setCamera(mCamera);
-        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        recorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
-        recorder.setProfile(CamcorderProfile.get(currentCamera, CamcorderProfile.QUALITY_HIGH));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            try {
-                mVideoFile = AndroidFileUtils.createVideoFile(this);
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-            recorder.setOutputFile(mVideoFile);
-        }
-        recorder.setVideoSize(videoWidth, videoHeight);
-
-        prepareRecorder();
-    }
-
-    private void prepareRecorder() {
-        recorder.setPreviewDisplay(mCameraPreview.getHolder().getSurface());
-        try {
-            recorder.prepare();
-            recorder.start();
-        } catch (Exception e) {
-            Toast.makeText(this, "Error starting the recorder: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
-            finish();
-        }
-    }
-
-    private void releaseMediaRecorder() {
-        if (recorder != null) {
-            recorder.reset();
-            recorder.release();
-            recorder = null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.kt b/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.kt
new file mode 100644
index 000000000..a18724d0d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Loïc Siret <loic.siret@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.camera
+
+import android.animation.Animator
+import android.app.Activity
+import android.media.MediaRecorder
+import android.hardware.Camera.PictureCallback
+import cx.ring.utils.ContentUriHandler
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import android.content.Intent
+import android.hardware.Camera
+import android.provider.MediaStore
+import android.widget.Toast
+import cx.ring.R
+import android.os.Bundle
+import android.view.ViewAnimationUtils
+import android.hardware.Camera.CameraInfo
+import android.media.CamcorderProfile
+import android.net.Uri
+import android.os.Build
+import android.util.Log
+import android.view.View
+import cx.ring.databinding.CamerapickerBinding
+import cx.ring.utils.AndroidFileUtils
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.lang.Exception
+import kotlin.math.max
+
+class CustomCameraActivity : Activity() {
+    private var binding: CamerapickerBinding? = null
+    private val mDisposableBag = CompositeDisposable()
+    private var cameraFront = -1
+    private var cameraBack = -1
+    private var currentCamera = 0
+    private var recorder: MediaRecorder? = null
+    private var mRecording = false
+    private var mActionVideo = false
+    private var mVideoFile: File? = null
+    private var mCamera: Camera? = null
+    private var mCameraPreview: CameraPreview? = null
+
+    private val mPicture = PictureCallback { input: ByteArray, camera ->
+        mDisposableBag.add(
+            Single.fromCallable {
+                if (mCameraPreview != null) mCameraPreview!!.stop()
+                val file = AndroidFileUtils.createImageFile(this)
+                FileOutputStream(file).use { out ->
+                    out.write(input)
+                    out.flush()
+                }
+                ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file)
+            }
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ uri: Uri ->
+                    setResult(RESULT_OK, Intent()
+                            .putExtra(MediaStore.EXTRA_OUTPUT, uri)
+                            .setType(TYPE_IMAGE))
+                    finish()
+                }) { e: Throwable ->
+                    Log.e(TAG, "Error saving picture", e)
+                    setResult(RESULT_CANCELED)
+                    finish()
+                })
+    }
+
+    private fun takePicture() {
+        if (mRecording) releaseMediaRecorder()
+        if (mCamera != null) {
+            binding!!.buttonPicture.isEnabled = false
+            binding!!.buttonVideo.visibility = View.GONE
+            try {
+                mCamera!!.takePicture(null, null, mPicture)
+            } catch (e: Exception) {
+                Toast.makeText(this, "Error taking picture", Toast.LENGTH_LONG).show()
+                finish()
+            }
+        }
+    }
+
+    private fun takeVideo() {
+        if (mRecording) {
+            releaseMediaRecorder()
+            mCameraPreview!!.stop()
+            val intent = Intent()
+                .putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, mVideoFile!!))
+                .setType(TYPE_VIDEO)
+            setResult(RESULT_OK, intent)
+            binding!!.buttonVideo.setImageResource(R.drawable.baseline_videocam_24)
+            finish()
+        } else {
+            if (mCamera != null) {
+                initRecorder()
+                binding!!.buttonVideo.setImageResource(R.drawable.lb_ic_stop)
+                binding!!.buttonPicture.visibility = View.GONE
+            }
+        }
+        mRecording = !mRecording
+    }
+
+    /**
+     * Called when the activity is first created.
+     */
+    public override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        binding = CamerapickerBinding.inflate(layoutInflater)
+        if (intent.action != null) {
+            mActionVideo = intent.action == MediaStore.ACTION_VIDEO_CAPTURE
+        }
+        binding!!.buttonVideo.isEnabled = false
+        binding!!.buttonPicture.isEnabled = false
+        if (mActionVideo) {
+            binding!!.buttonVideo.visibility = View.VISIBLE
+        }
+        setContentView(binding!!.root)
+    }
+
+    override fun onStart() {
+        super.onStart()
+        mDisposableBag.add(Single.fromCallable { cameraInstance }
+            .subscribeOn(Schedulers.io())
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe({ camera ->
+                if (binding == null) {
+                    camera.release()
+                } else {
+                    mCamera = camera
+                    mCameraPreview = CameraPreview(this, camera)
+                    binding!!.cameraPreview.addView(mCameraPreview, 0)
+                    binding!!.buttonVideo.isEnabled = true
+                    binding!!.buttonPicture.isEnabled = true
+                    binding!!.buttonPicture.setOnClickListener { takePicture() }
+                    binding!!.buttonVideo.setOnClickListener { takeVideo() }
+                    val endRadius = max(binding!!.root.width, binding!!.root.height)
+                    val x = binding!!.root.width / 2
+                    val y = binding!!.root.height / 2
+                    if (binding!!.loadClip.visibility == View.VISIBLE) {
+                        val anim = ViewAnimationUtils.createCircularReveal(
+                            binding!!.loadClip, x, y, endRadius.toFloat(), 0f
+                        )
+                        anim.addListener(object : Animator.AnimatorListener {
+                            override fun onAnimationStart(animator: Animator) {}
+                            override fun onAnimationEnd(animator: Animator) {
+                                binding!!.loadClip.visibility = View.GONE
+                            }
+
+                            override fun onAnimationCancel(animator: Animator) {}
+                            override fun onAnimationRepeat(animator: Animator) {}
+                        })
+                        anim.duration = 600
+                        anim.startDelay = 50
+                        anim.start()
+                    }
+                }
+            }) { e: Throwable ->
+                Toast.makeText(this, "Can't open camera", Toast.LENGTH_LONG).show()
+                finish()
+            })
+    }
+
+    override fun onStop() {
+        super.onStop()
+        if (mCameraPreview != null) {
+            mCameraPreview!!.stop()
+            mCameraPreview = null
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mDisposableBag.dispose()
+        binding = null
+        mCamera?.apply {
+            release()
+            mCamera = null
+        }
+    }
+
+    private fun initVideo() {
+        val numberCameras = Camera.getNumberOfCameras()
+        if (numberCameras == 0) return
+        val camInfo = CameraInfo()
+        for (i in 0 until numberCameras) {
+            Camera.getCameraInfo(i, camInfo)
+            if (camInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
+                cameraFront = i
+            } else {
+                cameraBack = i
+            }
+        }
+        currentCamera = if (cameraFront == -1) cameraBack else cameraFront
+    }
+
+    /**
+     * Helper method to access the camera returns null if it cannot get the
+     * camera or does not exist
+     */
+    private val cameraInstance: Camera
+        get() {
+            initVideo()
+            return Camera.open(currentCamera)
+        }
+
+    private fun initRecorder() {
+        val videoWidth = mCamera!!.parameters.previewSize.width
+        val videoHeight = mCamera!!.parameters.previewSize.height
+        mCamera?.unlock()
+        recorder = MediaRecorder().apply {
+            setCamera(mCamera)
+            setAudioSource(MediaRecorder.AudioSource.MIC)
+            setVideoSource(MediaRecorder.VideoSource.DEFAULT)
+            setProfile(CamcorderProfile.get(currentCamera, CamcorderProfile.QUALITY_HIGH))
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                try {
+                    mVideoFile = AndroidFileUtils.createVideoFile(this@CustomCameraActivity)
+                } catch (e: IOException) {
+                    e.printStackTrace()
+                }
+                setOutputFile(mVideoFile)
+            }
+            setVideoSize(videoWidth, videoHeight)
+        }
+        prepareRecorder()
+    }
+
+    private fun prepareRecorder() {
+        recorder!!.setPreviewDisplay(mCameraPreview!!.holder.surface)
+        try {
+            recorder!!.prepare()
+            recorder!!.start()
+        } catch (e: Exception) {
+            Toast.makeText(this, "Error starting the recorder: " + e.localizedMessage, Toast.LENGTH_LONG).show()
+            finish()
+        }
+    }
+
+    private fun releaseMediaRecorder() {
+        recorder?.apply {
+            reset()
+            release()
+            recorder = null
+        }
+    }
+
+    companion object {
+        private const val TAG = "CustomCameraActivity"
+        const val TYPE_IMAGE = "image/jpeg"
+        const val TYPE_VIDEO = "video"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java b/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
index e2b38635e..86c40d356 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/Card.java
@@ -136,20 +136,17 @@ public class Card {
     }
 
     public enum Type {
-        ABOUT_VERSION,
         DEFAULT,
         SEARCH_RESULT,
-        ABOUT_CONTRIBUTOR,
         ACCOUNT_ADD_DEVICE,
         ACCOUNT_EDIT_PROFILE,
         ACCOUNT_SHARE_ACCOUNT,
-        ABOUT_LICENCES,
+        ADD_CONTACT,
         CONTACT,
         CONTACT_ONLINE,
         CONTACT_WITH_USERNAME,
         CONTACT_WITH_USERNAME_ONLINE,
         CONTACT_REQUEST,
-        ACCOUNT_SETTINGS,
         CONTACT_REQUEST_WITH_USERNAME
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java b/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
index 14e21dd04..cfdc82d71 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/CardPresenterSelector.java
@@ -46,18 +46,13 @@ public class CardPresenterSelector extends PresenterSelector {
         Presenter presenter = presenters.get(card.getType());
         if (presenter == null) {
             switch (card.getType()) {
-                case ABOUT_VERSION:
-                case ABOUT_CONTRIBUTOR:
-                case ABOUT_LICENCES:
                 case ACCOUNT_ADD_DEVICE:
                 case ACCOUNT_EDIT_PROFILE:
                 case ACCOUNT_SHARE_ACCOUNT:
-                case ACCOUNT_SETTINGS:
+                case ADD_CONTACT:
                     presenter = new IconCardPresenter(mContext);
                     break;
                 case SEARCH_RESULT:
-                    presenter = new ContactCardPresenter(mContext, R.style.SearchCardTheme);
-                    break;
                 case CONTACT:
                     presenter = new ContactCardPresenter(mContext, R.style.ContactCardTheme);
                     break;
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/CardView.java b/ring-android/app/src/main/java/cx/ring/tv/cards/CardView.java
new file mode 100644
index 000000000..0bd5f0463
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/CardView.java
@@ -0,0 +1,313 @@
+package cx.ring.tv.cards;
+
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.core.content.res.ResourcesCompat;
+import androidx.core.view.ViewCompat;
+import androidx.leanback.widget.BaseCardView;
+
+import androidx.leanback.R;
+
+public class CardView extends BaseCardView {
+
+    public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0;
+    public static final int CARD_TYPE_FLAG_TITLE = 1;
+    public static final int CARD_TYPE_FLAG_CONTENT = 2;
+    public static final int CARD_TYPE_FLAG_ICON_RIGHT = 3;
+    
+    private static final int CARD_HEIGHT = 290;
+    private static final String ALPHA = "alpha";
+
+    private ImageView mImageView;
+    private ViewGroup mInfoArea;
+    private TextView mTitleView;
+    private TextView mContentView;
+    private ImageView mBadgeImage;
+    private boolean mAttachedToWindow;
+    private ObjectAnimator mFadeInAnimator;
+
+    @Deprecated
+    public CardView(Context context, int themeResId) {
+        this(new ContextThemeWrapper(context, themeResId));
+    }
+
+    public CardView(Context context) {
+        this(context, null);
+    }
+
+    public CardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.imageCardViewStyle);
+    }
+
+    public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        buildImageCardView(attrs, defStyleAttr, R.style.Widget_Leanback_ImageCardView);
+    }
+
+    @SuppressLint("CustomViewStyleable")
+    private void buildImageCardView(AttributeSet attrs, int defStyleAttr, int defStyle) {
+        // Make sure the ImageCardView is focusable.
+        setFocusable(true);
+        setFocusableInTouchMode(true);
+        setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, CARD_HEIGHT));
+
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        inflater.inflate(R.layout.lb_image_card_view, this);
+        TypedArray cardAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.lbImageCardView, defStyleAttr, defStyle);
+        ViewCompat.saveAttributeDataForStyleable(this, getContext(), R.styleable.lbImageCardView, attrs, cardAttrs, defStyleAttr, defStyle);
+        int cardType = cardAttrs.getInt(R.styleable.lbImageCardView_lbImageCardViewType, CARD_TYPE_FLAG_IMAGE_ONLY);
+
+        boolean hasImageOnly = cardType == CARD_TYPE_FLAG_IMAGE_ONLY;
+        boolean hasTitle = (cardType & CARD_TYPE_FLAG_TITLE) == CARD_TYPE_FLAG_TITLE;
+        boolean hasContent = (cardType & CARD_TYPE_FLAG_CONTENT) == CARD_TYPE_FLAG_CONTENT;
+        boolean hasIconRight = (cardType & CARD_TYPE_FLAG_ICON_RIGHT) == CARD_TYPE_FLAG_ICON_RIGHT;
+
+        mImageView = findViewById(R.id.main_image);
+        if (mImageView.getDrawable() == null) {
+            mImageView.setVisibility(View.INVISIBLE);
+        }
+
+        // Set Object Animator for image view.
+        mFadeInAnimator = ObjectAnimator.ofFloat(mImageView, ALPHA, 1f);
+        mFadeInAnimator.setDuration(mImageView.getResources().getInteger(android.R.integer.config_shortAnimTime));
+
+        mInfoArea = findViewById(R.id.info_field);
+
+        Typeface mulishBold = ResourcesCompat.getFont(getContext(), cx.ring.R.font.mulish_semibold);
+        Typeface mulishRegular = ResourcesCompat.getFont(getContext(), cx.ring.R.font.mulish_regular);
+
+        if (hasImageOnly) {
+            removeView(mInfoArea);
+            cardAttrs.recycle();
+            return;
+        }
+
+        if (hasTitle) {
+            mTitleView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_title, mInfoArea, false);
+            mTitleView.setTextSize(12);
+            mTitleView.setTypeface(mulishBold);
+            mInfoArea.addView(mTitleView);
+        }
+
+        if (hasContent) {
+            mContentView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_content, mInfoArea, false);
+            mContentView.setTextSize(10);
+            mContentView.setTypeface(mulishRegular);
+            mContentView.setTextColor(getResources().getColor(cx.ring.R.color.white));
+            mInfoArea.addView(mContentView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+        }
+
+        if (hasIconRight) {
+            int layoutId = R.layout.lb_image_card_view_themed_badge_right;
+            mBadgeImage = (ImageView) inflater.inflate(layoutId, mInfoArea, false);
+            mInfoArea.addView(mBadgeImage);
+        }
+
+        // Set up LayoutParams for children
+        if (mBadgeImage != null) {
+            RelativeLayout.LayoutParams relativeLayoutParams =
+                    new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            if (hasTitle) {
+                relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+                relativeLayoutParams.addRule(RelativeLayout.ALIGN_TOP, mTitleView.getId());
+                relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mTitleView.getId());
+                relativeLayoutParams.setMargins(0,5,0,0);
+            }
+            mBadgeImage.setLayoutParams(relativeLayoutParams);
+        }
+
+        if (hasTitle && mBadgeImage != null) {
+            RelativeLayout.LayoutParams relativeLayoutParams =
+                    (RelativeLayout.LayoutParams) mTitleView.getLayoutParams();
+            relativeLayoutParams.addRule(RelativeLayout.START_OF, mBadgeImage.getId());
+            relativeLayoutParams.addRule(RelativeLayout.LEFT_OF, mBadgeImage.getId());
+            mTitleView.setLayoutParams(relativeLayoutParams);
+        }
+
+        if (hasContent) {
+            RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mContentView.getLayoutParams();
+            if (!hasTitle) {
+                relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+            } else {
+                relativeLayoutParams.addRule(RelativeLayout.BELOW, mTitleView.getId());
+            }
+            mContentView.setLayoutParams(relativeLayoutParams);
+        }
+
+        cardAttrs.recycle();
+    }
+
+    public final ImageView getMainImageView() {
+        return mImageView;
+    }
+
+    public void setMainImageAdjustViewBounds(boolean adjustViewBounds) {
+        if (mImageView != null) {
+            mImageView.setAdjustViewBounds(adjustViewBounds);
+        }
+    }
+
+    public void setMainImage(Drawable drawable) {
+        setMainImage(drawable, true);
+    }
+
+    public void setMainImage(Drawable drawable, boolean fade) {
+        if (mImageView == null) {
+            return;
+        }
+
+        mImageView.setImageDrawable(drawable);
+        if (drawable == null) {
+            mFadeInAnimator.cancel();
+            mImageView.setAlpha(1f);
+            mImageView.setVisibility(View.INVISIBLE);
+        } else {
+            mImageView.setVisibility(View.VISIBLE);
+            if (fade) {
+                fadeIn();
+            } else {
+                mFadeInAnimator.cancel();
+                mImageView.setAlpha(1f);
+            }
+        }
+    }
+
+    public void setMainImageDimensions(int width, int height) {
+        ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
+        lp.width = width;
+        lp.height = height;
+        mImageView.setLayoutParams(lp);
+    }
+
+    public Drawable getMainImage() {
+        if (mImageView == null) {
+            return null;
+        }
+
+        return mImageView.getDrawable();
+    }
+
+    public Drawable getInfoAreaBackground() {
+        if (mInfoArea != null) {
+            return mInfoArea.getBackground();
+        }
+        return null;
+    }
+
+    public void setInfoAreaBackground(Drawable drawable) {
+        if (mInfoArea != null) {
+            mInfoArea.setBackground(drawable);
+        }
+    }
+
+    public void setInfoAreaBackgroundColor(@ColorInt int color) {
+        if (mInfoArea != null) {
+            mInfoArea.setBackgroundColor(color);
+        }
+    }
+
+    public void setTitleText(CharSequence text) {
+        if (mTitleView == null) {
+            return;
+        }
+        mTitleView.setText(text);
+    }
+
+    public CharSequence getTitleText() {
+        if (mTitleView == null) {
+            return null;
+        }
+
+        return mTitleView.getText();
+    }
+
+    public TextView getTitleTextView() {
+        return mTitleView;
+    }
+
+    public void setContentText(CharSequence text) {
+        if (mContentView == null) {
+            return;
+        }
+        mContentView.setText(text);
+    }
+
+    public CharSequence getContentText() {
+        if (mContentView == null) {
+            return null;
+        }
+
+        return mContentView.getText();
+    }
+
+    public void setBadgeImage(Drawable drawable) {
+        if (mBadgeImage == null) {
+            return;
+        }
+        mBadgeImage.setImageDrawable(drawable);
+        if (drawable != null) {
+            mBadgeImage.setVisibility(View.VISIBLE);
+        } else {
+            mBadgeImage.setVisibility(View.GONE);
+        }
+    }
+
+    public Drawable getBadgeImage() {
+        if (mBadgeImage == null) {
+            return null;
+        }
+
+        return mBadgeImage.getDrawable();
+    }
+
+    public void setTitleSingleLine(boolean singleLine) {
+        mTitleView.setSingleLine(singleLine);
+        mTitleView.setEllipsize(TextUtils.TruncateAt.END);
+    }
+
+    private void fadeIn() {
+        mImageView.setAlpha(0f);
+        if (mAttachedToWindow) {
+            mFadeInAnimator.start();
+        }
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAttachedToWindow = true;
+        if (mImageView.getAlpha() == 0) {
+            fadeIn();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mAttachedToWindow = false;
+        mFadeInAnimator.cancel();
+        mImageView.setAlpha(1f);
+        super.onDetachedFromWindow();
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/ShadowRowPresenterSelector.java b/ring-android/app/src/main/java/cx/ring/tv/cards/ShadowRowPresenterSelector.java
index e968c731b..fcbdfcc40 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/ShadowRowPresenterSelector.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/ShadowRowPresenterSelector.java
@@ -14,9 +14,17 @@
  */
 package cx.ring.tv.cards;
 
+import android.content.Context;
+import android.view.ViewGroup;
+
+import androidx.core.content.res.ResourcesCompat;
 import androidx.leanback.widget.ListRowPresenter;
 import androidx.leanback.widget.Presenter;
 import androidx.leanback.widget.PresenterSelector;
+import androidx.leanback.widget.RowHeaderPresenter;
+import androidx.leanback.widget.RowHeaderView;
+
+import cx.ring.R;
 
 /**
  * This {@link PresenterSelector} will return a {@link ListRowPresenter} which has shadow support
@@ -24,8 +32,8 @@ import androidx.leanback.widget.PresenterSelector;
  */
 public class ShadowRowPresenterSelector extends PresenterSelector {
 
-    private ListRowPresenter mShadowEnabledRowPresenter = new ListRowPresenter();
-    private ListRowPresenter mShadowDisabledRowPresenter = new NoDimListRowPresenter();
+    private CustomListRowPresenter mShadowEnabledRowPresenter = new CustomListRowPresenter();
+    private CustomDimListRowPresenter mShadowDisabledRowPresenter = new CustomDimListRowPresenter();
 
     public ShadowRowPresenterSelector() {
         mShadowEnabledRowPresenter.setNumRows(1);
@@ -48,4 +56,30 @@ public class ShadowRowPresenterSelector extends PresenterSelector {
                 mShadowEnabledRowPresenter
         };
     }
+
+    private static class CustomListRowPresenter extends ListRowPresenter {
+        public CustomListRowPresenter() {
+            super();
+            setHeaderPresenter(new CustomRowHeaderPresenter());
+        }
+    }
+
+    private static class CustomDimListRowPresenter extends NoDimListRowPresenter {
+        public CustomDimListRowPresenter() {
+            super();
+            setHeaderPresenter(new CustomRowHeaderPresenter());
+        }
+    }
+
+    private static class CustomRowHeaderPresenter extends RowHeaderPresenter {
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            super.onBindViewHolder(viewHolder, item);
+            RowHeaderView titleView = viewHolder.view.findViewById(R.id.row_header);
+            titleView.setTypeface(ResourcesCompat.getFont(titleView.getContext(), R.font.ubuntu_medium));
+            titleView.setTextSize(16);
+            viewHolder.view.setAlpha(1);
+        }
+    }
+
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
index cc75834a1..c0d4e0336 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java
@@ -21,17 +21,17 @@ package cx.ring.tv.cards.contacts;
 
 import android.content.Context;
 
-import androidx.leanback.widget.ImageCardView;
 import android.view.ContextThemeWrapper;
 
-import cx.ring.R;
-
 import net.jami.smartlist.SmartListViewModel;
+
+import cx.ring.R;
 import cx.ring.tv.cards.AbstractCardPresenter;
 import cx.ring.tv.cards.Card;
+import cx.ring.tv.cards.CardView;
 import cx.ring.views.AvatarDrawable;
 
-public class ContactCardPresenter extends AbstractCardPresenter<ImageCardView> {
+public class ContactCardPresenter extends AbstractCardPresenter<CardView> {
 
     private static final String TAG = ContactCardPresenter.class.getSimpleName();
 
@@ -40,32 +40,21 @@ public class ContactCardPresenter extends AbstractCardPresenter<ImageCardView> {
     }
 
     @Override
-    protected ImageCardView onCreateView() {
-        return new ImageCardView(getContext());
+    protected CardView onCreateView() {
+        return new CardView(getContext());
     }
 
     @Override
-    public void onBindViewHolder(Card card, ImageCardView cardView) {
+    public void onBindViewHolder(Card card, CardView cardView) {
         ContactCard contact = (ContactCard) card;
 
         SmartListViewModel model = contact.getModel();
-        /*String username = model.getContact().getUsername();
-
-        if (username == null) {
-            username = model.getUri().getRawUriString();
-        }
-
-        if (username != null && (username.isEmpty() || username.equals(model.getContactName()))) {
-            cardView.setTitleText(username);
-            cardView.setContentText("");
-        } else {
-            cardView.setTitleText(model.getContactName());
-            cardView.setContentText(username);
-        }*/
         cardView.setTitleText(card.getTitle());
         cardView.setContentText(card.getDescription());
+        cardView.setTitleSingleLine(true);
+        cardView.setBackgroundColor(getContext().getResources().getColor(R.color.tv_transparent));
+        cardView.setInfoAreaBackgroundColor(getContext().getResources().getColor(R.color.transparent));
 
-        cardView.setBackgroundColor(cardView.getResources().getColor(R.color.color_primary_dark));
         cardView.setMainImage(
                 new AvatarDrawable.Builder()
                         .withViewModel(model)
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardHelper.java b/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardHelper.java
index 3952a18ba..0bfbbfb65 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardHelper.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardHelper.java
@@ -38,18 +38,10 @@ public final class IconCardHelper {
 
     public static IconCard getAboutCardByType(Context pContext, Card.Type type) {
         switch (type) {
-            case ABOUT_CONTRIBUTOR:
-                return getContributorCard(pContext);
-            case ABOUT_LICENCES:
-                return getLicencesCard(pContext);
-            case ABOUT_VERSION:
-                return getVersionCard(pContext);
             case ACCOUNT_ADD_DEVICE:
                 return getAccountAddDeviceCard(pContext);
             case ACCOUNT_EDIT_PROFILE:
                 return getAccountManagementCard(pContext);
-            case ACCOUNT_SETTINGS:
-                return getAccountSettingsCard(pContext);
             case ACCOUNT_SHARE_ACCOUNT:
                 return getAccountShareCard(pContext, null);
             default:
@@ -58,60 +50,19 @@ public final class IconCardHelper {
     }
 
     public static IconCard getAccountAddDeviceCard(Context pContext) {
-        return new IconCard(Card.Type.ACCOUNT_ADD_DEVICE, pContext.getString(R.string.account_link_export_button), "", R.drawable.baseline_add_24);
+        return new IconCard(Card.Type.ACCOUNT_ADD_DEVICE, pContext.getString(R.string.account_export_title), "", R.drawable.baseline_androidtv_link_device);
     }
 
     public static IconCard getAccountManagementCard(Context pContext) {
-        return new IconCard(Card.Type.ACCOUNT_EDIT_PROFILE, pContext.getString(R.string.account_edit_profile), "", R.drawable.baseline_account_card_details);
-    }
-
-    public static IconCard getAccountSettingsCard(Context pContext) {
-        return new IconCard(Card.Type.ACCOUNT_SETTINGS, pContext.getString(R.string.menu_item_settings), "", R.drawable.baseline_settings_24);
+        return new IconCard(Card.Type.ACCOUNT_EDIT_PROFILE, pContext.getString(R.string.account_edit_profile), "", R.drawable.baseline_androidtv_account);
     }
 
     public static IconCard getAccountShareCard(Context pContext, BitmapDrawable bitmapDrawable) {
         return new IconCard(Card.Type.ACCOUNT_SHARE_ACCOUNT, pContext.getString(R.string.menu_item_share), "", bitmapDrawable);
     }
 
-    public static IconCard getVersionCard(Context pContext) {
-        return new IconCard(Card.Type.ABOUT_VERSION, pContext.getString(R.string.version_section) + " " + BuildConfig.VERSION_NAME, "", R.drawable.ic_ring_logo_white_vd);
-    }
-
-    public static IconCard getLicencesCard(Context pContext) {
-        return new IconCard(Card.Type.ABOUT_LICENCES, pContext.getString(R.string.section_license), formatLicence(pContext), R.drawable.baseline_description_24);
+    public static IconCard getAddContactCard(Context pContext) {
+        return new IconCard(Card.Type.ADD_CONTACT, pContext.getString(R.string.account_tv_add_contact), "", R.drawable.baseline_androidtv_add_user);
     }
 
-    public static IconCard getContributorCard(Context pContext) {
-        return new IconCard(Card.Type.ABOUT_CONTRIBUTOR, pContext.getString(R.string.credits), formatContributors(pContext), R.drawable.baseline_face_24);
-    }
-
-    private static CharSequence formatLicence(Context pContext) {
-        Resources res = pContext.getResources();
-
-        SpannableString version = new SpannableString(res.getString(R.string.version_section));
-        version.setSpan(new UnderlineSpan(), 0, version.length(), 0);
-        CharSequence versioned = res.getString(R.string.app_release, BuildConfig.VERSION_NAME);
-
-        SpannableString licence = new SpannableString(res.getString(R.string.section_license));
-        licence.setSpan(new UnderlineSpan(), 0, licence.length(), 0);
-        CharSequence licenced = res.getString(R.string.license);
-
-        SpannableString copyright = new SpannableString(res.getString(R.string.copyright_section));
-        copyright.setSpan(new UnderlineSpan(), 0, copyright.length(), 0);
-        CharSequence copyrighted = res.getString(R.string.copyright);
-
-
-        return Html.fromHtml("<b><u>" + version + "</u></b><br/>" + versioned + "<BR/><BR/>"
-                + "<b><u>" + licence + "</u></b><br/>" + licenced + "<BR/><BR/>"
-                + "<b><u>" + copyright + "</u></b><br/>" + copyrighted);
-    }
-
-    private static CharSequence formatContributors(Context pContext) {
-        Resources res = pContext.getResources();
-
-        SpannableString developedby = new SpannableString(res.getString(R.string.developed_by));
-        developedby.setSpan(new UnderlineSpan(), 0, developedby.length(), 0);
-        CharSequence developed = res.getString(R.string.credits_developer).replaceAll("\n", "<br/>");
-        return Html.fromHtml("<b><u>" + developedby + "</u></b><br/>" + developed);
-    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardPresenter.java
index 6b534472c..f3c65634f 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/cards/iconcards/IconCardPresenter.java
@@ -2,6 +2,7 @@
  *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
  *
  *  Author: Loïc Siret <loic.siret@savoirfairelinux.com>
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
  *
  *  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
@@ -19,51 +20,44 @@
  */
 package cx.ring.tv.cards.iconcards;
 
-import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import androidx.leanback.widget.ImageCardView;
+import android.graphics.PorterDuff;
 
 import android.view.ContextThemeWrapper;
+import android.view.View;
 import android.widget.ImageView;
 
-
 import cx.ring.R;
-import cx.ring.application.JamiApplication;
 import cx.ring.tv.cards.AbstractCardPresenter;
 import cx.ring.tv.cards.Card;
+import cx.ring.tv.cards.CardView;
 
-public class IconCardPresenter extends AbstractCardPresenter<ImageCardView> {
+public class IconCardPresenter extends AbstractCardPresenter<CardView> {
 
-    private static final int ANIMATION_DURATION = 200;
+    private static final int IMAGE_PADDING = 35;
 
     public IconCardPresenter(Context context) {
-        super(new ContextThemeWrapper(context, R.style.IconCardTheme));
+        super(new ContextThemeWrapper(context, R.style.ContactCardTheme));
     }
 
     @Override
-    protected ImageCardView onCreateView() {
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
-        ImageCardView imageCardView = new ImageCardView(getContext());
-        final ImageView image = imageCardView.getMainImageView();
-        image.setBackgroundResource(R.drawable.icon_focused);
-        image.getBackground().setAlpha(0);
-        imageCardView.setOnFocusChangeListener((v, hasFocus) -> animateIconBackground(image.getBackground(), hasFocus));
-        return imageCardView;
+    protected CardView onCreateView() {
+        CardView cardView = new CardView(getContext());
+        cardView.setTitleSingleLine(false);
+        cardView.setBackgroundColor(getContext().getResources().getColor(R.color.tv_transparent));
+        cardView.setInfoAreaBackgroundColor(getContext().getResources().getColor(R.color.transparent));
+        ImageView image = cardView.getMainImageView();
+        image.setPadding(IMAGE_PADDING, IMAGE_PADDING, IMAGE_PADDING, IMAGE_PADDING);
+        image.setColorFilter(getContext().getResources().getColor(android.R.color.white), PorterDuff.Mode.SRC_IN);
+        cardView.getTitleTextView().setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
+        return cardView;
     }
 
     @Override
-    public void onBindViewHolder(Card card, ImageCardView cardView) {
+    public void onBindViewHolder(Card card, CardView cardView) {
         cardView.setTitleText(card.getTitle());
         cardView.setContentText(card.getDescription());
         cardView.setMainImage(card.getDrawable(cardView.getContext()));
     }
-
-    private void animateIconBackground(Drawable drawable, boolean hasFocus) {
-        if (hasFocus) {
-            ObjectAnimator.ofInt(drawable, "alpha", 0, 255).setDuration(ANIMATION_DURATION).start();
-        } else {
-            ObjectAnimator.ofInt(drawable, "alpha", 255, 0).setDuration(ANIMATION_DURATION).start();
-        }
-    }
+    
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java
index 97ff42355..86eac3fff 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactActivity.java
@@ -19,17 +19,97 @@
  */
 package cx.ring.tv.contact;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.renderscript.Type;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 
+import androidx.core.content.ContextCompat;
 import androidx.fragment.app.FragmentActivity;
+import androidx.leanback.app.BackgroundManager;
+
 import cx.ring.R;
+import cx.ring.tv.camera.CameraPreview;
+import dagger.hilt.android.AndroidEntryPoint;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
 
+@AndroidEntryPoint
 public class TVContactActivity extends FragmentActivity {
+
+    private static final String TAG = TVContactActivity.class.getSimpleName();
+
+    private final CompositeDisposable mDisposable = new CompositeDisposable();
     public static final String SHARED_ELEMENT_NAME = "photo";
     public static final String CONTACT_REQUEST_URI = "uri";
     public static final String TYPE_CONTACT_REQUEST_INCOMING = "incoming";
     public static final String TYPE_CONTACT_REQUEST_OUTGOING = "outgoing";
 
+    private int mDisplayWidth, mDisplayHeight;
+    private BackgroundManager mBackgroundManager;
+    private ImageView mBackgroundImage;
+    private FrameLayout mPreviewView;
+    private Camera mCamera;
+    private CameraPreview mCameraPreview;
+    private CameraManager mCameraManager;
+
+    private Bitmap mBackgroundBitmap;
+    private RenderScript rs;
+    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
+    private Allocation in, out;
+
+    private final Camera.ErrorCallback mErrorCallback = new Camera.ErrorCallback() {
+        @Override
+        public void onError(int error, Camera camera) {
+            mBackgroundImage.setVisibility(View.INVISIBLE);
+            mBackgroundManager.setDrawable(ContextCompat.getDrawable(TVContactActivity.this, R.drawable.tv_background));
+        }
+    };
+
+    private final Object mCameraAvailabilityCallback = new CameraManager.AvailabilityCallback() {
+        @Override
+        public void onCameraAvailable(String cameraId) {
+            super.onCameraAvailable(cameraId);
+            if (mBackgroundImage.getVisibility() == View.INVISIBLE) {
+                setUpCamera();
+            }
+        }
+    };
+
+    private final Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            if (mBackgroundBitmap == null) {
+                mBackgroundBitmap = Bitmap.createBitmap(mDisplayWidth, mDisplayHeight, Bitmap.Config.ARGB_8888);
+                rs = RenderScript.create(TVContactActivity.this);
+                yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
+                Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(data.length);
+                Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(mDisplayWidth).setY(mDisplayHeight);
+                in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);
+                out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);
+            }
+            in.copyFrom(data);
+            yuvToRgbIntrinsic.setInput(in);
+            yuvToRgbIntrinsic.forEach(out);
+            out.copyTo(mBackgroundBitmap);
+
+            mBackgroundImage.setImageBitmap(mBackgroundBitmap);
+        }
+    };
+
     /**
      * Called when the activity is first created.
      */
@@ -37,5 +117,87 @@ public class TVContactActivity extends FragmentActivity {
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.tv_frag_contact);
+
+        mBackgroundManager = BackgroundManager.getInstance(this);
+        mBackgroundManager.attach(getWindow());
+        mPreviewView = findViewById(R.id.previewView);
+        mBackgroundImage = findViewById(R.id.background);
+
+        DisplayMetrics displayMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+        mDisplayHeight = displayMetrics.heightPixels;
+        mDisplayWidth = displayMetrics.widthPixels;
     }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        setUpCamera();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mCameraPreview != null) {
+            mCamera.setPreviewCallback(null);
+            mCameraPreview.stop();
+            mCameraPreview = null;
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDisposable.dispose();
+        if (mCameraManager != null) {
+            mCameraManager.unregisterAvailabilityCallback((CameraManager.AvailabilityCallback) mCameraAvailabilityCallback);
+        }
+        if (mCamera != null) {
+            mCamera.release();
+            mCamera = null;
+        }
+        if (mBackgroundBitmap != null){
+            rs.destroy();
+            in.destroy();
+            out.destroy();
+            yuvToRgbIntrinsic.destroy();
+            mBackgroundBitmap.recycle();
+            mBackgroundBitmap = null;
+            rs = null;
+            in = null;
+            out = null;
+        }
+    }
+
+    private Camera getCameraInstance() {
+        try {
+            int currentCamera = 0;
+            mCamera = Camera.open(currentCamera);
+        }
+        catch (RuntimeException e) {
+            Log.e(TAG, "failed to open camera");
+        }
+        return mCamera;
+    }
+
+    private void setUpCamera() {
+        mDisposable.add(Single.fromCallable(this::getCameraInstance)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(camera -> {
+                    mCamera.setErrorCallback(mErrorCallback);
+                    mCamera.setPreviewCallback(mPreviewCallback);
+                    mCameraPreview = new CameraPreview(this, mCamera);
+                    mPreviewView.removeAllViews();
+                    mPreviewView.addView(mCameraPreview, 0);
+                    mBackgroundImage.setVisibility(View.VISIBLE);
+                    if (mCameraManager == null) {
+                        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+                        mCameraManager.registerAvailabilityCallback((CameraManager.AvailabilityCallback) mCameraAvailabilityCallback, null);
+                    }
+                }, e -> {
+                    mBackgroundManager.setDrawable(ContextCompat.getDrawable(TVContactActivity.this, R.drawable.tv_background));
+                }));
+    }
+
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java
deleted file mode 100644
index d1a032064..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.contact;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.leanback.widget.Presenter;
-
-import cx.ring.R;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.tv.conversation.TvConversationFragment;
-import cx.ring.utils.ConversationPath;
-
-public class TVContactDetailPresenter extends Presenter {
-
-    public static final String FRAGMENT_TAG = "conversation";
-
-    @Override
-    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
-        return new CustomViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.tv, viewGroup, false));
-    }
-
-    @Override
-    public void onBindViewHolder(ViewHolder viewHolder, Object object) {
-        ((CustomViewHolder) viewHolder).bind((SmartListViewModel) object);
-    }
-
-    @Override
-    public void onUnbindViewHolder(ViewHolder viewHolder) {
-
-    }
-
-    private static class CustomViewHolder extends Presenter.ViewHolder {
-
-        CustomViewHolder(View view) {
-            super(view);
-        }
-
-        void bind(SmartListViewModel object) {
-            Fragment fragment = TvConversationFragment.newInstance(ConversationPath.toBundle(object.getAccountId(), object.getUri()));
-            FragmentManager fragmentManager = ((TVContactActivity) view.getContext()).getSupportFragmentManager();
-
-            fragmentManager.beginTransaction()
-                    .replace(R.id.content, fragment, FRAGMENT_TAG)
-                    .commit();
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.kt
new file mode 100644
index 000000000..a31f2572a
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactDetailPresenter.kt
@@ -0,0 +1,58 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.contact
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+import androidx.leanback.widget.Presenter
+import cx.ring.R
+import cx.ring.tv.conversation.TvConversationFragment
+import cx.ring.utils.ConversationPath
+import net.jami.smartlist.SmartListViewModel
+
+class TVContactDetailPresenter : Presenter() {
+    override fun onCreateViewHolder(viewGroup: ViewGroup): ViewHolder {
+        return CustomViewHolder(
+            LayoutInflater.from(viewGroup.context).inflate(R.layout.tv, viewGroup, false)
+        )
+    }
+
+    override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
+        (viewHolder as CustomViewHolder).bind(item as SmartListViewModel)
+    }
+
+    override fun onUnbindViewHolder(viewHolder: ViewHolder) {}
+
+    private class CustomViewHolder(view: View) : ViewHolder(view) {
+        fun bind(item: SmartListViewModel) {
+            val fragment = TvConversationFragment.newInstance(ConversationPath.toBundle(item.accountId, item.uri))
+            val fragmentManager = (view.context as FragmentActivity).supportFragmentManager
+            fragmentManager.beginTransaction()
+                .replace(R.id.content, fragment, FRAGMENT_TAG)
+                .commit()
+        }
+    }
+
+    companion object {
+        const val FRAGMENT_TAG = "conversation"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java
deleted file mode 100644
index 5db0ccb3c..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.contact;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.Button;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.leanback.app.BackgroundManager;
-import androidx.leanback.widget.Action;
-import androidx.leanback.widget.ArrayObjectAdapter;
-import androidx.leanback.widget.ClassPresenterSelector;
-import androidx.leanback.widget.DetailsOverviewLogoPresenter;
-import androidx.leanback.widget.DetailsOverviewRow;
-import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
-import androidx.leanback.widget.FullWidthDetailsOverviewSharedElementHelper;
-import androidx.leanback.widget.ListRow;
-import androidx.leanback.widget.ListRowPresenter;
-import androidx.leanback.widget.SparseArrayObjectAdapter;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import cx.ring.client.HomeActivity;
-import cx.ring.fragments.CallFragment;
-import cx.ring.fragments.ConversationFragment;
-import net.jami.model.Uri;
-import net.jami.services.NotificationService;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.tv.contactrequest.TVContactRequestDetailPresenter;
-import cx.ring.tv.main.BaseDetailFragment;
-import cx.ring.utils.ConversationPath;
-import cx.ring.views.AvatarDrawable;
-
-public class TVContactFragment extends BaseDetailFragment<TVContactPresenter> implements TVContactView {
-
-    private static final int ACTION_CALL = 0;
-    private static final int ACTION_DELETE = 1;
-    private static final int ACTION_ACCEPT = 2;
-    private static final int ACTION_REFUSE = 3;
-    private static final int ACTION_BLOCK = 4;
-    private static final int ACTION_ADD_CONTACT = 5;
-    private static final int ACTION_CLEAR_HISTORY = 6;
-
-    private static final int DIALOG_WIDTH = 900;
-    private static final int DIALOG_HEIGHT = 400;
-
-    private ArrayObjectAdapter mAdapter;
-    private int iconSize = -1;
-
-    private boolean isIncomingRequest = false;
-    private boolean isOutgoingRequest = false;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        String type = getActivity().getIntent().getType();
-        if (type != null) {
-            switch (type) {
-                case TVContactActivity.TYPE_CONTACT_REQUEST_INCOMING:
-                    isIncomingRequest = true;
-                    break;
-                case TVContactActivity.TYPE_CONTACT_REQUEST_OUTGOING:
-                    isOutgoingRequest = true;
-                    break;
-            }
-        }
-
-        // Override down navigation as we do not use it in this screen
-        // Only the detailPresenter will be displayed
-
-        prepareBackgroundManager();
-        setupAdapter();
-        Resources res = getResources();
-        iconSize = res.getDimensionPixelSize(R.dimen.tv_avatar_size);
-        presenter.setContact(ConversationPath.fromIntent(getActivity().getIntent()));
-    }
-
-    private void prepareBackgroundManager() {
-        Activity activity = requireActivity();
-        BackgroundManager mBackgroundManager = BackgroundManager.getInstance(activity);
-        mBackgroundManager.attach(activity.getWindow());
-    }
-
-    private void setupAdapter() {
-        // Set detail background and style.
-        FullWidthDetailsOverviewRowPresenter detailsPresenter;
-        if (isIncomingRequest || isOutgoingRequest) {
-            detailsPresenter = new FullWidthDetailsOverviewRowPresenter(
-                    new TVContactRequestDetailPresenter(),
-                    new DetailsOverviewLogoPresenter());
-        } else {
-            detailsPresenter = new FullWidthDetailsOverviewRowPresenter(
-                    new TVContactDetailPresenter(),
-                    new DetailsOverviewLogoPresenter());
-        }
-
-        detailsPresenter.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.grey_900));
-        detailsPresenter.setInitialState(FullWidthDetailsOverviewRowPresenter.STATE_HALF);
-
-        // Hook up transition element.
-        Activity activity = getActivity();
-        if (activity != null) {
-            FullWidthDetailsOverviewSharedElementHelper mHelper = new FullWidthDetailsOverviewSharedElementHelper();
-            mHelper.setSharedElementEnterTransition(activity, TVContactActivity.SHARED_ELEMENT_NAME);
-            detailsPresenter.setListener(mHelper);
-            detailsPresenter.setParticipatingEntranceTransition(false);
-            prepareEntranceTransition();
-        }
-
-        detailsPresenter.setOnActionClickedListener(action -> {
-            if (action.getId() == ACTION_CALL) {
-                presenter.contactClicked();
-            } else if (action.getId() == ACTION_DELETE) {
-                createDeleteDialog();
-            } else if (action.getId() == ACTION_CLEAR_HISTORY) {
-                presenter.clearHistory();
-            } else if (action.getId() == ACTION_ADD_CONTACT) {
-                presenter.onAddContact();
-            } else if (action.getId() == ACTION_ACCEPT) {
-                presenter.acceptTrustRequest();
-            } else if (action.getId() == ACTION_REFUSE) {
-                presenter.refuseTrustRequest();
-            } else if (action.getId() == ACTION_BLOCK) {
-                presenter.blockTrustRequest();
-            }
-        });
-
-        ClassPresenterSelector mPresenterSelector = new ClassPresenterSelector();
-        mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);
-        mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
-        mAdapter = new ArrayObjectAdapter(mPresenterSelector);
-        setAdapter(mAdapter);
-    }
-
-    public void showContact(SmartListViewModel model) {
-        final DetailsOverviewRow row = new DetailsOverviewRow(model);
-        AvatarDrawable avatar =
-                new AvatarDrawable.Builder()
-                        .withViewModel(model)
-                        //.withPresence(false)
-                        .withCircleCrop(false)
-                        .build(getActivity());
-        avatar.setInSize(iconSize);
-        row.setImageDrawable(avatar);
-
-        SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
-        if (isIncomingRequest) {
-            adapter.set(ACTION_ACCEPT, new Action(ACTION_ACCEPT, getResources()
-                    .getString(R.string.accept)));
-            adapter.set(ACTION_REFUSE, new Action(ACTION_REFUSE, getResources().getString(R.string.refuse)));
-            adapter.set(ACTION_BLOCK, new Action(ACTION_BLOCK, getResources().getString(R.string.block)));
-        } else if (isOutgoingRequest) {
-            adapter.set(ACTION_ADD_CONTACT, new Action(ACTION_ADD_CONTACT, getResources().getString(R.string.ab_action_contact_add)));
-        } else {
-            adapter.set(ACTION_CALL, new Action(ACTION_CALL, getResources().getString(R.string.ab_action_video_call),
-                    null, requireContext().getDrawable(R.drawable.baseline_videocam_24)));
-            adapter.set(ACTION_DELETE, new Action(ACTION_DELETE, getResources().getString(R.string.conversation_action_remove_this)));
-            adapter.set(ACTION_CLEAR_HISTORY, new Action(ACTION_CLEAR_HISTORY, getResources().getString(R.string.conversation_action_history_clear)));
-        }
-        row.setActionsAdapter(adapter);
-
-        mAdapter.add(row);
-    }
-
-    @Override
-    public void callContact(String accountId, Uri conversationUri, Uri uri) {
-        startActivity(new Intent(Intent.ACTION_CALL)
-                .setClass(requireContext(), TVCallActivity.class)
-                .putExtras(ConversationPath.toBundle(accountId, conversationUri))
-                .putExtra(Intent.EXTRA_PHONE_NUMBER, uri.getUri()));
-    }
-
-    @Override
-    public void goToCallActivity(String id) {
-        startActivity(new Intent(requireContext(), TVCallActivity.class)
-                .putExtra(NotificationService.KEY_CALL_ID, id));
-    }
-
-    @Override
-    public void switchToConversationView() {
-        isIncomingRequest = false;
-        isOutgoingRequest = false;
-        setupAdapter();
-        presenter.setContact(ConversationPath.fromIntent(getActivity().getIntent()));
-    }
-
-    @Override
-    public void finishView() {
-        Activity activity = getActivity();
-        if (activity != null) {
-            activity.finish();
-        }
-    }
-
-    private void createDeleteDialog() {
-        AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
-                .setTitle(R.string.conversation_action_remove_this_title)
-                .setMessage("")
-                .setPositiveButton(R.string.menu_delete, (dialog, whichButton) -> presenter.removeContact())
-                .setNegativeButton(android.R.string.cancel, null)
-                .create();
-        alertDialog.getWindow().setLayout(DIALOG_WIDTH, DIALOG_HEIGHT);
-        alertDialog.setOwnerActivity(requireActivity());
-        alertDialog.setOnShowListener(dialog -> {
-            Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-            positive.setFocusable(true);
-            positive.setFocusableInTouchMode(true);
-            positive.requestFocus();
-        });
-
-        alertDialog.show();
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt
new file mode 100644
index 000000000..af1757d9f
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt
@@ -0,0 +1,207 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.contact
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.core.content.ContextCompat
+import androidx.leanback.widget.*
+import cx.ring.R
+import cx.ring.tv.call.TVCallActivity
+import cx.ring.tv.contact.more.TVContactMoreActivity
+import cx.ring.tv.contact.more.TVContactMoreFragment
+import cx.ring.tv.contactrequest.TVContactRequestDetailPresenter
+import cx.ring.tv.main.BaseDetailFragment
+import cx.ring.utils.ConversationPath
+import cx.ring.views.AvatarDrawable
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.model.Uri
+import net.jami.services.NotificationService
+import net.jami.smartlist.SmartListViewModel
+
+@AndroidEntryPoint
+class TVContactFragment : BaseDetailFragment<TVContactPresenter>(), TVContactView {
+    private val mDisposableBag = CompositeDisposable()
+    private var mAdapter: ArrayObjectAdapter? = null
+    private var iconSize = -1
+    private var isIncomingRequest = false
+    private var isOutgoingRequest = false
+    private lateinit var mConversationPath: ConversationPath
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        val type: String?
+        if (arguments != null) {
+            mConversationPath = ConversationPath.fromBundle(arguments)!!
+            type = arguments?.getString("type")
+        } else {
+            mConversationPath = ConversationPath.fromIntent(requireActivity().intent)!!
+            type = activity?.intent?.type
+        }
+        if (type != null) {
+            when (type) {
+                TVContactActivity.TYPE_CONTACT_REQUEST_INCOMING -> isIncomingRequest = true
+                TVContactActivity.TYPE_CONTACT_REQUEST_OUTGOING -> isOutgoingRequest = true
+            }
+        }
+        setupAdapter()
+        val res = resources
+        iconSize = res.getDimensionPixelSize(R.dimen.tv_avatar_size)
+        presenter!!.setContact(mConversationPath)
+    }
+
+    private fun setupAdapter() {
+        // Set detail background and style.
+        val detailsPresenter: FullWidthDetailsOverviewRowPresenter = if (isIncomingRequest || isOutgoingRequest) {
+            FullWidthDetailsOverviewRowPresenter(
+                TVContactRequestDetailPresenter(),
+                DetailsOverviewLogoPresenter()
+            )
+        } else {
+            FullWidthDetailsOverviewRowPresenter(
+                TVContactDetailPresenter(),
+                DetailsOverviewLogoPresenter()
+            )
+        }
+        detailsPresenter.backgroundColor =
+            ContextCompat.getColor(requireContext(), R.color.tv_contact_background)
+        detailsPresenter.actionsBackgroundColor =
+            ContextCompat.getColor(requireContext(), R.color.tv_contact_row_background)
+        detailsPresenter.initialState = FullWidthDetailsOverviewRowPresenter.STATE_HALF
+
+        // Hook up transition element.
+        val activity: Activity? = activity
+        if (activity != null) {
+            val mHelper = FullWidthDetailsOverviewSharedElementHelper()
+            mHelper.setSharedElementEnterTransition(activity, TVContactActivity.SHARED_ELEMENT_NAME)
+            detailsPresenter.setListener(mHelper)
+            detailsPresenter.isParticipatingEntranceTransition = false
+            prepareEntranceTransition()
+        }
+        detailsPresenter.onActionClickedListener = OnActionClickedListener { action: Action ->
+            if (action.id == ACTION_CALL.toLong()) {
+                presenter!!.contactClicked()
+            } else if (action.id == ACTION_ADD_CONTACT.toLong()) {
+                presenter!!.onAddContact()
+            } else if (action.id == ACTION_ACCEPT.toLong()) {
+                presenter!!.acceptTrustRequest()
+            } else if (action.id == ACTION_REFUSE.toLong()) {
+                presenter!!.refuseTrustRequest()
+            } else if (action.id == ACTION_BLOCK.toLong()) {
+                presenter!!.blockTrustRequest()
+            } else if (action.id == ACTION_MORE.toLong()) {
+                startActivityForResult(
+                    Intent(getActivity(), TVContactMoreActivity::class.java)
+                        .setDataAndType(
+                            mConversationPath!!.toUri(),
+                            TVContactMoreActivity.CONTACT_REQUEST_URI
+                        ),
+                    REQUEST_CODE
+                )
+            }
+        }
+        val mPresenterSelector = ClassPresenterSelector()
+        mPresenterSelector.addClassPresenter(DetailsOverviewRow::class.java, detailsPresenter)
+        mPresenterSelector.addClassPresenter(ListRow::class.java, ListRowPresenter())
+        mAdapter = ArrayObjectAdapter(mPresenterSelector)
+        adapter = mAdapter
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode == REQUEST_CODE) {
+            if (resultCode == TVContactMoreFragment.DELETE) finishView()
+        }
+    }
+
+    override fun showContact(model: SmartListViewModel) {
+        val context = requireContext()
+        val row = DetailsOverviewRow(model)
+        val avatar = AvatarDrawable.Builder()
+            .withViewModel(model) //.withPresence(false)
+            .withCircleCrop(false)
+            .build(context)
+        avatar.setInSize(iconSize)
+        row.imageDrawable = avatar
+        val adapter = SparseArrayObjectAdapter()
+        if (isIncomingRequest) {
+            adapter[ACTION_ACCEPT] = Action(ACTION_ACCEPT.toLong(), resources
+                    .getString(R.string.accept))
+            adapter[ACTION_REFUSE] = Action(ACTION_REFUSE.toLong(), resources.getString(R.string.refuse))
+            adapter[ACTION_BLOCK] = Action(ACTION_BLOCK.toLong(), resources.getString(R.string.block))
+        } else if (isOutgoingRequest) {
+            adapter[ACTION_ADD_CONTACT] = Action(ACTION_ADD_CONTACT.toLong(), resources.getString(R.string.ab_action_contact_add))
+        } else {
+            adapter[ACTION_CALL] = Action(ACTION_CALL.toLong(), resources.getString(R.string.ab_action_video_call), null, context.getDrawable(R.drawable.baseline_videocam_24))
+            adapter[ACTION_MORE] = Action(ACTION_MORE.toLong(), resources.getString(R.string.tv_action_more), null, context.getDrawable(R.drawable.baseline_more_vert_24))
+        }
+        row.actionsAdapter = adapter
+        mAdapter!!.add(row)
+    }
+
+    override fun callContact(accountId: String, conversationUri: Uri, uri: Uri) {
+        startActivity(
+            Intent(Intent.ACTION_CALL)
+                .setClass(requireContext(), TVCallActivity::class.java)
+                .putExtras(ConversationPath.toBundle(accountId, conversationUri))
+                .putExtra(Intent.EXTRA_PHONE_NUMBER, uri.uri)
+        )
+    }
+
+    override fun goToCallActivity(id: String) {
+        startActivity(
+            Intent(requireContext(), TVCallActivity::class.java)
+                .putExtra(NotificationService.KEY_CALL_ID, id)
+        )
+    }
+
+    override fun switchToConversationView() {
+        isIncomingRequest = false
+        isOutgoingRequest = false
+        setupAdapter()
+        presenter!!.setContact(mConversationPath)
+    }
+
+    override fun finishView() {
+        val activity: Activity? = activity
+        activity?.onBackPressed()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        mDisposableBag.dispose()
+    }
+
+    companion object {
+        @JvmField
+        val TAG = TVContactFragment::class.simpleName!!
+        private const val ACTION_CALL = 0
+        private const val ACTION_ACCEPT = 1
+        private const val ACTION_REFUSE = 2
+        private const val ACTION_BLOCK = 3
+        private const val ACTION_ADD_CONTACT = 4
+        private const val ACTION_MORE = 5
+        private const val REQUEST_CODE = 100
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java
deleted file mode 100644
index 410f956e0..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.contact;
-
-import javax.inject.Inject;
-
-import net.jami.daemon.Blob;
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Account;
-import net.jami.model.Conference;
-import net.jami.model.Conversation;
-import net.jami.model.Call;
-import net.jami.model.Uri;
-import net.jami.mvp.RootPresenter;
-import net.jami.services.AccountService;
-import net.jami.services.VCardService;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.utils.ConversationPath;
-import io.reactivex.rxjava3.core.Scheduler;
-
-import net.jami.utils.VCardUtils;
-
-public class TVContactPresenter extends RootPresenter<TVContactView> {
-
-    private final AccountService mAccountService;
-    private final ConversationFacade mConversationService;
-    private final Scheduler mUiScheduler;
-    private final VCardService mVCardService;
-
-    private String mAccountId;
-    private Uri mUri;
-
-    @Inject
-    public TVContactPresenter(AccountService accountService,
-                              ConversationFacade conversationService,
-                              Scheduler uiScheduler,
-                              VCardService vCardService) {
-        mAccountService = accountService;
-        mConversationService = conversationService;
-        mUiScheduler = uiScheduler;
-        mVCardService = vCardService;
-    }
-
-    public void setContact(ConversationPath path) {
-        mAccountId = path.getAccountId();
-        mUri = path.getConversationUri();
-        mCompositeDisposable.clear();
-        mCompositeDisposable.add(mConversationService
-                .getAccountSubject(path.getAccountId())
-                .map(a -> new SmartListViewModel(a.getByUri(mUri), true))
-                .observeOn(mUiScheduler)
-                .subscribe(c -> getView().showContact(c)));
-    }
-
-    public void removeContact() {
-        mConversationService.removeConversation(mAccountId, mUri).subscribe();
-        getView().finishView();
-    }
-
-    public void contactClicked() {
-        Account account = mAccountService.getAccount(mAccountId);
-        if (account != null) {
-            Conversation conversation = account.getByUri(mUri);
-            Conference conf = conversation.getCurrentCall();
-            if (conf != null
-                    && !conf.getParticipants().isEmpty()
-                    && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.INACTIVE
-                    && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.FAILURE) {
-                getView().goToCallActivity(conf.getId());
-            } else {
-                if (conversation.isSwarm()) {
-                    getView().callContact(mAccountId, mUri, conversation.getContact().getUri());
-                } else {
-                    getView().callContact(mAccountId, mUri, mUri);
-                }
-            }
-        }
-    }
-
-    public void clearHistory() {
-        mConversationService.clearHistory(mAccountId, mUri).subscribe();
-    }
-
-    public void onAddContact() {
-        sendTrustRequest(mAccountId, mUri);
-        getView().switchToConversationView();
-    }
-
-    private void sendTrustRequest(String accountId, Uri conversationUri) {
-        Conversation conversation = mAccountService.getAccount(accountId).getByUri(conversationUri);
-        mVCardService.loadSmallVCardWithDefault(accountId, VCardService.MAX_SIZE_REQUEST)
-                .subscribe(vCard -> mAccountService.sendTrustRequest(conversation, conversationUri, Blob.fromString(VCardUtils.vcardToString(vCard))),
-                        e -> mAccountService.sendTrustRequest(conversation, conversationUri, null));
-    }
-
-    public void acceptTrustRequest() {
-        mConversationService.acceptRequest(mAccountId, mUri);
-        getView().switchToConversationView();
-    }
-
-    public void refuseTrustRequest() {
-        mConversationService.discardRequest(mAccountId, mUri);
-        getView().finishView();
-    }
-
-    public void blockTrustRequest() {
-        mConversationService.discardRequest(mAccountId, mUri);
-        mAccountService.removeContact(mAccountId, mUri.getRawRingId(), true);
-        getView().finishView();
-    }
-
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt
new file mode 100644
index 000000000..31c10e90d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt
@@ -0,0 +1,108 @@
+/*
+ *  Copyright (C) 2004-2018 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.contact
+
+import cx.ring.utils.ConversationPath
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Scheduler
+import net.jami.daemon.Blob
+import net.jami.services.ConversationFacade
+import net.jami.model.Account
+import net.jami.model.Call
+import net.jami.model.Uri
+import net.jami.mvp.RootPresenter
+import net.jami.services.AccountService
+import net.jami.services.VCardService
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.VCardUtils.vcardToString
+import javax.inject.Inject
+import javax.inject.Named
+
+class TVContactPresenter @Inject constructor(
+    private val mAccountService: AccountService,
+    private val mConversationService: ConversationFacade,
+    @param:Named("UiScheduler") private val mUiScheduler: Scheduler,
+    private val mVCardService: VCardService
+) : RootPresenter<TVContactView>() {
+    private var mAccountId: String? = null
+    private var mUri: Uri? = null
+
+    fun setContact(path: ConversationPath) {
+        mAccountId = path.accountId
+        mUri = path.conversationUri
+        mCompositeDisposable.clear()
+        mCompositeDisposable.add(mConversationService
+            .getAccountSubject(path.accountId)
+            .map { a: Account -> SmartListViewModel(a.getByUri(mUri), true) }
+            .observeOn(mUiScheduler)
+            .subscribe { c: SmartListViewModel -> view!!.showContact(c) })
+    }
+
+    fun contactClicked() {
+        val account = mAccountService.getAccount(mAccountId!!)
+        if (account != null) {
+            val conversation = account.getByUri(mUri)
+            val conf = conversation!!.currentCall
+            if (conf != null && conf.participants.isNotEmpty()
+                && conf.participants[0].callStatus !== Call.CallStatus.INACTIVE && conf.participants[0].callStatus !== Call.CallStatus.FAILURE
+            ) {
+                view?.goToCallActivity(conf.id)
+            } else {
+                if (conversation.isSwarm) {
+                    view?.callContact(mAccountId, mUri, conversation.contact!!.uri)
+                } else {
+                    view?.callContact(mAccountId, mUri, mUri)
+                }
+            }
+        }
+    }
+
+    fun onAddContact() {
+        mAccountId?.let { accountId -> mUri?.let { uri ->
+            sendTrustRequest(accountId, uri)
+        } }
+        view?.switchToConversationView()
+    }
+
+    private fun sendTrustRequest(accountId: String, conversationUri: Uri) {
+        val conversation = mAccountService.getAccount(accountId)!!.getByUri(conversationUri)
+        mVCardService.loadSmallVCardWithDefault(accountId, VCardService.MAX_SIZE_REQUEST)
+            .subscribe({ vCard: VCard ->
+                mAccountService.sendTrustRequest(conversation!!, conversationUri, Blob.fromString(vcardToString(vCard))) })
+            { mAccountService.sendTrustRequest(conversation!!, conversationUri, null) }
+    }
+
+    fun acceptTrustRequest() {
+        mConversationService.acceptRequest(mAccountId!!, mUri!!)
+        view?.switchToConversationView()
+    }
+
+    fun refuseTrustRequest() {
+        mConversationService.discardRequest(mAccountId!!, mUri!!)
+        view?.finishView()
+    }
+
+    fun blockTrustRequest() {
+        mConversationService.discardRequest(mAccountId!!, mUri!!)
+        mAccountService.removeContact(mAccountId!!, mUri!!.rawRingId, true)
+        view?.finishView()
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsActivity.java b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java
similarity index 69%
rename from ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsActivity.java
rename to ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java
index e7fb5e6ef..40e8b2413 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ * Copyright (C) 2004-2020 Savoir-faire Linux Inc.
  *
- * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
+ * Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
  *
  * 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
@@ -18,18 +18,23 @@
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
-package cx.ring.tv.account;
+package cx.ring.tv.contact.more;
 
 import android.os.Bundle;
 
 import androidx.fragment.app.FragmentActivity;
 
 import cx.ring.R;
+import dagger.hilt.android.AndroidEntryPoint;
+
+@AndroidEntryPoint
+public class TVContactMoreActivity extends FragmentActivity {
+
+    public static final String CONTACT_REQUEST_URI = "uri";
 
-public class TVSettingsActivity extends FragmentActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.tv_activity_settings);
+        setContentView(R.layout.tv_activity_contact_more);
     }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt
new file mode 100644
index 000000000..e8996373d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ * Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.contact.more
+
+import android.app.Activity
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AlertDialog
+import androidx.leanback.preference.LeanbackSettingsFragmentCompat
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceScreen
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import cx.ring.R
+import cx.ring.tv.account.JamiPreferenceFragment
+import cx.ring.utils.ConversationPath.Companion.fromIntent
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TVContactMoreFragment : LeanbackSettingsFragmentCompat() {
+    override fun onPreferenceStartInitialScreen() {
+        startPreferenceFragment(PrefsFragment.newInstance())
+    }
+
+    override fun onPreferenceStartFragment(
+        preferenceFragment: PreferenceFragmentCompat,
+        preference: Preference
+    ): Boolean {
+        return false
+    }
+
+    override fun onPreferenceStartScreen(
+        caller: PreferenceFragmentCompat,
+        pref: PreferenceScreen
+    ): Boolean {
+        return false
+    }
+
+    @AndroidEntryPoint
+    class PrefsFragment : JamiPreferenceFragment<TVContactMorePresenter>(), TVContactMoreView {
+        override fun onCreatePreferences(savedInstanceState: Bundle, rootKey: String) {
+            setPreferencesFromResource(R.xml.tv_contact_more_pref, rootKey)
+        }
+
+        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+            super.onViewCreated(view, savedInstanceState)
+            presenter!!.setContact(fromIntent(requireActivity().intent))
+        }
+
+        override fun onPreferenceTreeClick(preference: Preference): Boolean {
+            if (preference.key == "Contact.clear") {
+                createDialog(
+                    getString(R.string.conversation_action_history_clear_title),
+                    getString(R.string.clear_history)
+                ) { dialog: DialogInterface?, whichButton: Int -> presenter!!.clearHistory() }
+            } else if (preference.key == "Contact.delete") {
+                createDialog(
+                    getString(R.string.conversation_action_remove_this_title),
+                    getString(R.string.menu_delete)
+                ) { dialog: DialogInterface?, whichButton: Int -> presenter!!.removeContact() }
+            }
+            return super.onPreferenceTreeClick(preference)
+        }
+
+        private fun createDialog(
+            title: String,
+            buttonText: String,
+            onClickListener: DialogInterface.OnClickListener
+        ) {
+            val alertDialog = MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
+                .setTitle(title)
+                .setMessage("")
+                .setPositiveButton(buttonText, onClickListener)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
+            alertDialog.window!!.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
+            alertDialog.setOwnerActivity(requireActivity())
+            alertDialog.setOnShowListener {
+                val positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+                positive.isFocusable = true
+                positive.isFocusableInTouchMode = true
+                positive.requestFocus()
+            }
+            alertDialog.show()
+        }
+
+        override fun finishView(finishParent: Boolean) {
+            val activity: Activity? = activity
+            if (activity != null) {
+                activity.setResult(if (finishParent) DELETE else CLEAR)
+                activity.finish()
+            }
+        }
+
+        companion object {
+            fun newInstance(): PrefsFragment {
+                return PrefsFragment()
+            }
+        }
+    }
+
+    companion object {
+        const val CLEAR = 101
+        const val DELETE = 102
+        private const val DIALOG_WIDTH = 900
+        private const val DIALOG_HEIGHT = 400
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMorePresenter.java b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMorePresenter.java
new file mode 100644
index 000000000..f69283d56
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMorePresenter.java
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.contact.more;
+
+import net.jami.services.ConversationFacade;
+import net.jami.model.Uri;
+import net.jami.mvp.RootPresenter;
+
+import javax.inject.Inject;
+
+import cx.ring.utils.ConversationPath;
+
+public class TVContactMorePresenter extends RootPresenter<TVContactMoreView> {
+
+    private static final String TAG = TVContactMorePresenter.class.getSimpleName();
+
+    private final ConversationFacade mConversationService;
+
+    private String mAccountId;
+    private Uri mUri;
+
+    @Inject
+    TVContactMorePresenter(ConversationFacade conversationService) {
+        mConversationService = conversationService;
+    }
+
+    public void setContact(ConversationPath path) {
+        mAccountId = path.getAccountId();
+        mUri = path.getConversationUri();
+    }
+
+    public void clearHistory() {
+        mConversationService.clearHistory(mAccountId, mUri).subscribe();
+        getView().finishView(false);
+    }
+
+    public void removeContact() {
+        mConversationService.removeConversation(mAccountId, mUri).subscribe();
+        getView().finishView(true);
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreView.java b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreView.java
new file mode 100644
index 000000000..ee9f67e1d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreView.java
@@ -0,0 +1,26 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.contact.more;
+
+public interface TVContactMoreView {
+
+    void finishView(boolean finishParent);
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java
deleted file mode 100644
index 23c5ff6c9..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java
+++ /dev/null
@@ -1,823 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors:    AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.tv.conversation;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.media.MediaPlayer;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.transition.TransitionManager;
-
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.speech.RecognizerIntent;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.widget.Button;
-import android.widget.Toast;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.snackbar.Snackbar;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.MediaViewerActivity;
-import cx.ring.views.AvatarFactory;
-import net.jami.conversation.ConversationPresenter;
-import net.jami.conversation.ConversationView;
-import net.jami.model.Account;
-import cx.ring.databinding.FragConversationTvBinding;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.DataTransfer;
-import net.jami.model.Error;
-import net.jami.model.Interaction;
-import cx.ring.mvp.BaseSupportFragment;
-import cx.ring.service.DRingService;
-import cx.ring.tv.camera.CustomCameraActivity;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import net.jami.utils.StringUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class TvConversationFragment extends BaseSupportFragment<ConversationPresenter> implements ConversationView {
-    private static final String TAG = TvConversationFragment.class.getSimpleName();
-
-    private static final String ARG_MODEL = "model";
-    private static final String KEY_AUDIOFILE = "audiofile";
-
-    private static final int REQUEST_CODE_PHOTO = 101;
-    private static final int REQUEST_SPEECH_CODE = 102;
-    private static final int REQUEST_CODE_SAVE_FILE = 103;
-
-    private static final int DIALOG_WIDTH = 900;
-    private static final int DIALOG_HEIGHT = 400;
-
-    private ConversationPath mConversationPath;
-
-    private int mSelectedPosition;
-
-    private static final String[] permissions = { Manifest.permission.RECORD_AUDIO };
-    private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
-    private File fileName = null;
-
-    private MediaRecorder recorder = null;
-    private MediaPlayer player = null;
-
-    boolean mStartRecording = true;
-    boolean mStartPlaying = true;
-
-    private TvConversationAdapter mAdapter = null;
-    private AvatarDrawable mConversationAvatar;
-    private final Map<String, AvatarDrawable> mParticipantAvatars = new HashMap<>();
-
-    private final CompositeDisposable mCompositeDisposable = new CompositeDisposable();
-    private FragConversationTvBinding binding;
-
-    private String mCurrentFileAbsolutePath = null;
-
-    public static TvConversationFragment newInstance(Bundle args) {
-        TvConversationFragment fragment = new TvConversationFragment();
-        fragment.setArguments(args);
-        return fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (getArguments() != null) {
-            mConversationPath = ConversationPath.fromBundle(getArguments());
-        }
-        String audiofile = savedInstanceState == null ? null : savedInstanceState.getString(KEY_AUDIOFILE);
-        if (audiofile != null) {
-            fileName = new File(audiofile);
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        if (fileName != null) {
-            outState.putString(KEY_AUDIOFILE, fileName.getAbsolutePath());
-        }
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        binding = FragConversationTvBinding.inflate(inflater, container, false);
-        ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
-        return binding.getRoot();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
-    // Create an intent that can start the Speech Recognizer activity
-    private void displaySpeechRecognizer() {
-        if (!checkAudioPermission())
-            return;
-        try {
-            Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
-                    .putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
-                    .putExtra(RecognizerIntent.EXTRA_PROMPT, getText(R.string.conversation_input_speech_hint));
-            startActivityForResult(intent, REQUEST_SPEECH_CODE);
-        } catch (Exception e) {
-            Snackbar.make(requireView(), "Can't get voice input", Snackbar.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        //ConversationPath path = ConversationPath.fromIntent(requireActivity().getIntent());
-        presenter.init(mConversationPath.getConversationUri(), mConversationPath.getAccountId());
-        mAdapter = new TvConversationAdapter(this, presenter);
-
-        binding.buttonText.setOnClickListener(v -> displaySpeechRecognizer());
-        binding.buttonVideo.setOnClickListener(v -> {
-            Intent intent = new Intent(getActivity(), CustomCameraActivity.class)
-                    .setAction(MediaStore.ACTION_VIDEO_CAPTURE);
-            startActivityForResult(intent, REQUEST_CODE_PHOTO);
-        });
-
-        binding.buttonAudio.setOnClickListener(v -> {
-            onRecord(mStartRecording);
-            mStartRecording = !mStartRecording;
-        });
-
-        binding.buttonText.setOnFocusChangeListener((v, hasFocus) -> {
-            TransitionManager.beginDelayedTransition(binding.textContainer);
-            binding.textText.setVisibility(hasFocus ? View.VISIBLE : View.GONE);
-        });
-
-        binding.buttonAudio.setOnFocusChangeListener((v, hasFocus) -> {
-            TransitionManager.beginDelayedTransition(binding.audioContainer);
-            binding.textAudio.setVisibility(hasFocus ? View.VISIBLE : View.GONE);
-        });
-
-        binding.buttonVideo.setOnFocusChangeListener((v, hasFocus) -> {
-            TransitionManager.beginDelayedTransition(binding.videoContainer);
-            binding.textVideo.setVisibility(hasFocus ? View.VISIBLE : View.GONE);
-        });
-
-        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
-        linearLayoutManager.setReverseLayout(true);
-        linearLayoutManager.setStackFromEnd(true);
-        binding.recyclerView.setLayoutManager(linearLayoutManager);
-        binding.recyclerView.setAdapter(mAdapter);
-    }
-
-    private boolean checkAudioPermission() {
-        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
-            requestPermissions(permissions, REQUEST_RECORD_AUDIO_PERMISSION);
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onContextItemSelected(@NonNull MenuItem item) {
-        if (mAdapter.onContextItemSelected(item))
-            return true;
-        return super.onContextItemSelected(item);
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
-        switch (requestCode) {
-            case REQUEST_CODE_PHOTO:
-                if (resultCode == Activity.RESULT_OK && data != null) {
-                    Uri media = (Uri) data.getExtras().get(MediaStore.EXTRA_OUTPUT);
-                    String type = data.getType();
-                    createMediaDialog(media, type);
-                }
-                break;
-            case REQUEST_SPEECH_CODE:
-                if (resultCode == Activity.RESULT_OK && data != null) {
-                    List<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
-                    String spokenText = results.get(0);
-                    createTextDialog(spokenText);
-                }
-                break;
-            case REQUEST_CODE_SAVE_FILE:
-                if(resultCode == Activity.RESULT_OK) {
-                    if (data != null && data.getData() != null) {
-                        writeToFile(data.getData());
-                    }
-                }
-            default:
-                super.onActivityResult(requestCode, resultCode, data);
-                break;
-        }
-    }
-
-    private void writeToFile(Uri data) {
-        File input = new File(mCurrentFileAbsolutePath);
-        if (requireContext().getContentResolver() != null)
-            mCompositeDisposable.add(AndroidFileUtils.copyFileToUri(requireContext().getContentResolver(), input, data).
-                    observeOn(AndroidSchedulers.mainThread()).
-                    subscribe(() -> Toast.makeText(getContext(), R.string.file_saved_successfully, Toast.LENGTH_SHORT).show(),
-                            error -> Toast.makeText(getContext(), R.string.generic_error, Toast.LENGTH_SHORT).show()));
-    }
-
-    private void createTextDialog(String spokenText) {
-        if (StringUtils.isEmpty(spokenText)) {
-            return;
-        }
-
-        AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
-                .setTitle(spokenText)
-                .setMessage("")
-                .setPositiveButton(R.string.tv_dialog_send, (dialog, whichButton) -> presenter.sendTextMessage(spokenText))
-                .setNegativeButton(android.R.string.cancel, null)
-                .create();
-        alertDialog.getWindow().setLayout(DIALOG_WIDTH, DIALOG_HEIGHT);
-        alertDialog.setOwnerActivity(requireActivity());
-        alertDialog.setOnShowListener(dialog -> {
-            Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-            positive.setFocusable(true);
-            positive.setFocusableInTouchMode(true);
-            positive.requestFocus();
-        });
-
-        alertDialog.show();
-    }
-
-    private void createMediaDialog(Uri media, String type) {
-        if (media == null) {
-            return;
-        }
-        Activity activity = getActivity();
-        if (activity == null)
-            return;
-
-        Single<File> file = AndroidFileUtils.getCacheFile(activity, media);
-        AlertDialog alertDialog = new MaterialAlertDialogBuilder(activity, R.style.Theme_MaterialComponents_Dialog)
-                .setTitle(type.equals(CustomCameraActivity.TYPE_IMAGE) ? R.string.tv_send_image_dialog_message : R.string.tv_send_video_dialog_message)
-                .setMessage("")
-                .setPositiveButton(R.string.tv_dialog_send, (dialog, whichButton) ->
-                        startFileSend(file.flatMapCompletable(TvConversationFragment.this::sendFile)))
-                .setNegativeButton(android.R.string.cancel, null)
-                .setNeutralButton(R.string.tv_media_preview, null)
-                .create();
-        alertDialog.getWindow().setLayout(DIALOG_WIDTH, DIALOG_HEIGHT);
-        alertDialog.setOwnerActivity(activity);
-        alertDialog.setOnShowListener(dialog -> {
-            Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-            positive.setFocusable(true);
-            positive.setFocusableInTouchMode(true);
-            positive.requestFocus();
-
-            Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
-            button.setOnClickListener(v -> {
-                if (type.equals(CustomCameraActivity.TYPE_IMAGE)) {
-                    Intent i = new Intent(getContext(), MediaViewerActivity.class);
-                    i.setAction(Intent.ACTION_VIEW).setDataAndType(media, "image/*").setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                    startActivity(i);
-                } else {
-                    Intent intent = new Intent(Intent.ACTION_VIEW, media);
-                    intent.setDataAndType(media, "video/*").setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                    startActivity(intent);
-                }
-            });
-        });
-
-        alertDialog.show();
-    }
-
-    private void createAudioDialog() {
-        AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
-                .setTitle(R.string.tv_send_audio_dialog_message)
-                .setMessage("")
-                .setPositiveButton(R.string.tv_dialog_send, (dialog, whichButton) -> sendAudio())
-                .setNegativeButton(android.R.string.cancel, null)
-                .setNeutralButton(R.string.tv_audio_play, null)
-                .create();
-        alertDialog.getWindow().setLayout(DIALOG_WIDTH, DIALOG_HEIGHT);
-        alertDialog.setOwnerActivity(requireActivity());
-        alertDialog.setOnShowListener(dialog -> {
-            Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
-            positive.setFocusable(true);
-            positive.setFocusableInTouchMode(true);
-            positive.requestFocus();
-
-            Button button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
-            button.setOnClickListener(v -> {
-                onPlay(mStartPlaying);
-                if (mStartPlaying) {
-                    button.setText(R.string.tv_audio_pause);
-                    if (player != null) {
-                        player.setOnCompletionListener(mp -> {
-                            button.setText(R.string.tv_audio_play);
-                            mStartPlaying = true;
-                        });
-                    }
-                } else {
-                    button.setText(R.string.tv_audio_play);
-                }
-                mStartPlaying = !mStartPlaying;
-            });
-        });
-
-        alertDialog.show();
-    }
-
-    @Override
-    public void addElement(Interaction element) {
-        mAdapter.add(element);
-        scrollToTop();
-    }
-
-    @Override
-    public void shareFile(File path, String displayName) {
-        Context c = getContext();
-        if (c == null)
-            return;
-        try {
-            android.net.Uri fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path);
-            if (fileUri != null) {
-                Intent sendIntent = new Intent();
-                sendIntent.setAction(Intent.ACTION_SEND);
-                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                String type = c.getContentResolver().getType(fileUri);
-                sendIntent.setDataAndType(fileUri, type);
-                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
-                startActivity(Intent.createChooser(sendIntent, null));
-            }
-        } catch (Exception e) {
-            Snackbar.make(requireView(), "Error sharing file: " + e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
-        }
-    }
-
-    @Override
-    public void openFile(File path, String displayName) {
-        Context c = getContext();
-        if (c == null)
-            return;
-        try {
-            android.net.Uri fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path);
-            if (fileUri != null) {
-                Intent sendIntent = new Intent();
-                sendIntent.setAction(Intent.ACTION_VIEW);
-                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-                String type = c.getContentResolver().getType(fileUri);
-                sendIntent.setDataAndType(fileUri, type);
-                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
-                startActivity(Intent.createChooser(sendIntent, null));
-            }
-        } catch (IllegalArgumentException e) {
-            Snackbar.make(requireView(), "Error opening file: " + e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
-        }
-    }
-
-    /**
-     * Creates an intent using Android Storage Access Framework
-     * This intent is then received by applications that can handle it like
-     * Downloads or Google drive
-     * @param file DataTransfer of the file that is going to be stored
-     * @param currentFileAbsolutePath absolute path of the file we want to save
-     */
-    public void startSaveFile(DataTransfer file, String currentFileAbsolutePath) {
-        mCurrentFileAbsolutePath = currentFileAbsolutePath;
-        try {
-            // Use Android Storage File Access to download the file
-            Intent downloadFileIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-            downloadFileIntent.setType(AndroidFileUtils.getMimeTypeFromExtension(file.getExtension()));
-
-            downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE);
-            downloadFileIntent.putExtra(Intent.EXTRA_TITLE, file.getDisplayName());
-
-            startActivityForResult(downloadFileIntent, REQUEST_CODE_SAVE_FILE);
-        } catch (Exception e) {
-            Log.i(TAG, "No app detected for saving files.");
-            File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
-            if (!directory.exists()) {
-                directory.mkdirs();
-            }
-            writeToFile(Uri.fromFile(new File(directory, file.getDisplayName())));
-        }
-    }
-
-    @Override
-    public void refreshView(List<Interaction> interactions) {
-        if (interactions == null) {
-            return;
-        }
-        if (mAdapter != null) {
-            mAdapter.updateDataset(interactions);
-        }
-        requireActivity().invalidateOptionsMenu();
-    }
-
-    @Override
-    public void onStop() {
-        releaseRecorder();
-        super.onStop();
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
-        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-        if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
-            // NOOP
-        }
-    }
-
-    private void onRecord(boolean start) {
-        if (start) {
-            startRecording();
-        } else {
-            stopRecording();
-        }
-    }
-
-    private void onPlay(boolean start) {
-        if (start) {
-            startPlaying();
-        } else {
-            stopPlaying();
-        }
-    }
-
-    private void startPlaying() {
-        if (fileName == null)
-            return;
-        player = new MediaPlayer();
-        try {
-            player.setDataSource(fileName.getAbsolutePath());
-            player.prepare();
-            player.start();
-        } catch (IOException e) {
-            Log.e(TAG, "prepare() failed");
-        }
-    }
-
-    private void stopPlaying() {
-        player.release();
-        player = null;
-    }
-
-    private void startRecording() {
-        if (!checkAudioPermission())
-            return;
-        if (recorder != null) {
-            return;
-        }
-        try {
-            fileName = AndroidFileUtils.createAudioFile(requireContext());
-            recorder = new MediaRecorder();
-            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-            recorder.setOutputFile(fileName.getAbsolutePath());
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                recorder.setOutputFormat(MediaRecorder.OutputFormat.OGG);
-                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS);
-            } else {
-                recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
-                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-            }
-
-            recorder.prepare();
-            recorder.start();
-        } catch (Exception e) {
-            Toast.makeText(requireContext(), "Error starting recording: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
-            if (recorder != null) {
-                recorder.release();
-                recorder = null;
-            }
-            return;
-        }
-
-        binding.buttonAudio.setImageResource(R.drawable.lb_ic_stop);
-        binding.textAudio.setText(R.string.tv_audio_recording);
-        Animation anim = new AlphaAnimation(0.0f, 1.0f);
-        anim.setDuration(500);
-        anim.setStartOffset(100);
-        anim.setRepeatMode(Animation.REVERSE);
-        anim.setRepeatCount(Animation.INFINITE);
-        binding.textAudio.startAnimation(anim);
-    }
-
-    private void releaseRecorder() {
-        if (recorder != null) {
-            try {
-                recorder.stop();
-            } catch (Exception e) {
-                Log.w(TAG, "Exception stopping recorder");
-            }
-            recorder.release();
-            recorder = null;
-        }
-    }
-
-    private void stopRecording() {
-        releaseRecorder();
-        binding.buttonAudio.setImageResource(R.drawable.baseline_mic_24);
-        binding.textAudio.setText(R.string.tv_send_audio);
-        binding.textAudio.clearAnimation();
-        createAudioDialog();
-    }
-
-    private void sendAudio() {
-        if (fileName != null) {
-            Single<File> file = Single.just(fileName);
-            fileName = null;
-            startFileSend(file.flatMapCompletable(this::sendFile));
-        }
-    }
-
-    private void startFileSend(Completable op) {
-        op.observeOn(AndroidSchedulers.mainThread())
-                .subscribe(() -> {
-                }, e -> {
-                    Log.e(TAG, "startFileSend: not able to create cache file", e);
-                    displayErrorToast(Error.INVALID_FILE);
-                });
-    }
-
-    private Completable sendFile(File file) {
-        return Completable.fromAction(() -> presenter.sendFile(file));
-    }
-
-    public void updatePosition(int position) {
-        mSelectedPosition = position;
-    }
-
-    public void updateAdapterItem() {
-        if (mSelectedPosition != -1) {
-            mAdapter.notifyItemChanged(mSelectedPosition);
-            mSelectedPosition = -1;
-        }
-    }
-
-    private void scrollToTop() {
-        if (mAdapter.getItemCount() > 0) {
-            binding.recyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
-        }
-    }
-
-    @Override
-    public void displayContact(Conversation conversation) {
-        List<Contact> contacts = conversation.getContacts();
-        mCompositeDisposable.clear();
-        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true)
-                .doOnSuccess(d -> {
-                    mConversationAvatar = (AvatarDrawable) d;
-                    mParticipantAvatars.put(contacts.get(0).getPrimaryNumber(),
-                            new AvatarDrawable((AvatarDrawable) d));
-                })
-                .flatMapObservable(d -> contacts.get(0).getUpdatesSubject())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(c -> {
-                    String id = c.getRingUsername();
-                    String displayName = c.getDisplayName();
-                    binding.title.setText(displayName);
-                    if (TextUtils.isEmpty(displayName) || !displayName.equals(id))
-                        binding.subtitle.setText(id);
-                    else
-                        binding.subtitle.setVisibility(View.GONE);
-                    mConversationAvatar.update(c);
-                    String uri = contacts.get(0).getPrimaryNumber();
-                    AvatarDrawable a = mParticipantAvatars.get(uri);
-                    if (a != null)
-                        a.update(c);
-                    mAdapter.setPhoto();
-                }));
-    }
-
-    @Override
-    public void updateElement(Interaction element) {
-        mAdapter.update(element);
-    }
-
-    @Override
-    public void removeElement(Interaction element) {
-        mAdapter.remove(element);
-    }
-
-    public AvatarDrawable getConversationAvatar(String uri) {
-        return mParticipantAvatars.get(uri);
-    }
-
-    public void askWriteExternalStoragePermission() {
-        requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, JamiApplication.PERMISSIONS_REQUEST);
-    }
-
-    @Override
-    public void scrollToEnd() {
-
-    }
-
-    @Override
-    public void updateContact(Contact contact) {
-        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), contact, true)
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(avatar -> {
-                    mParticipantAvatars.put(contact.getPrimaryNumber(), (AvatarDrawable) avatar);
-                    mAdapter.setPhoto();
-                }));
-    }
-
-    @Override
-    public void setComposingStatus(Account.ComposingStatus composingStatus) {
-
-    }
-
-    @Override
-    public void setLastDisplayed(Interaction interaction) {
-
-    }
-
-    @Override
-    public void setConversationColor(int integer) {
-
-    }
-
-    @Override
-    public void setConversationSymbol(CharSequence symbol) {
-
-    }
-
-    @Override
-    public void startShareLocation(String accountId, String contactId) {
-
-    }
-
-    @Override
-    public void showMap(String accountId, String contactId, boolean open) {
-
-    }
-
-    @Override
-    public void hideMap() {
-
-    }
-
-    public void showPluginListHandlers(String accountId, String contactId) {
-
-    }
-
-    @Override
-    public void hideErrorPanel() {
-
-    }
-
-    @Override
-    public void displayNetworkErrorPanel() {
-
-    }
-
-    @Override
-    public void displayAccountOfflineErrorPanel() {
-
-    }
-
-    @Override
-    public void setReadIndicatorStatus(boolean show) {
-
-    }
-
-    @Override
-    public void updateLastRead(String last) {
-
-    }
-
-    @Override
-    public void displayOnGoingCallPane(boolean display) {
-
-    }
-
-    @Override
-    public void displayNumberSpinner(Conversation conversation, net.jami.model.Uri number) {
-
-    }
-
-    @Override
-    public void hideNumberSpinner() {
-
-    }
-
-    @Override
-    public void clearMsgEdit() {
-
-    }
-
-    @Override
-    public void goToHome() {
-
-    }
-
-    @Override
-    public void goToAddContact(Contact contact) {
-
-    }
-
-    @Override
-    public void goToCallActivity(String conferenceId) {
-
-    }
-
-    @Override
-    public void goToCallActivityWithResult(String accountId, net.jami.model.Uri conversationUri, net.jami.model.Uri contactRingId, boolean audioOnly) {
-
-    }
-
-    @Override
-    public void goToContactActivity(String accountId, net.jami.model.Uri contactRingId) {
-
-    }
-
-    @Override
-    public void switchToUnknownView(String name) {
-        // todo
-    }
-
-    @Override
-    public void switchToIncomingTrustRequestView(String message) {
-        // todo
-    }
-
-    @Override
-    public void switchToConversationView() {
-        // todo
-    }
-
-    @Override
-    public void openFilePicker() {
-
-    }
-
-    @Override
-    public void acceptFile(String accountId, net.jami.model.Uri conversationUri, DataTransfer transfer) {
-        File cacheDir = requireContext().getCacheDir();
-        long spaceLeft = AndroidFileUtils.getSpaceLeft(cacheDir.toString());
-        if (spaceLeft == -1L || transfer.getTotalSize() > spaceLeft) {
-            presenter.noSpaceLeft();
-            return;
-        }
-        requireActivity().startService(new Intent(DRingService.ACTION_FILE_ACCEPT)
-                .setClass(requireContext(), DRingService.class)
-                .setData(ConversationPath.toUri(accountId, conversationUri))
-                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId())
-                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId()));
-    }
-
-    @Override
-    public void refuseFile(String accountId, net.jami.model.Uri conversationUri, DataTransfer transfer) {
-        requireActivity().startService(new Intent(DRingService.ACTION_FILE_CANCEL)
-                .setClass(requireContext(), DRingService.class)
-                .setData(ConversationPath.toUri(accountId, conversationUri))
-                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId())
-                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId()));
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt
new file mode 100644
index 000000000..59771ccbe
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt
@@ -0,0 +1,729 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors:    AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.conversation
+
+import android.Manifest
+import android.app.Activity
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.media.MediaPlayer
+import android.media.MediaRecorder
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.provider.MediaStore
+import android.speech.RecognizerIntent
+import android.text.TextUtils
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.AlphaAnimation
+import android.view.animation.Animation
+import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.transition.TransitionManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.snackbar.Snackbar
+import cx.ring.R
+import cx.ring.application.JamiApplication
+import cx.ring.client.MediaViewerActivity
+import cx.ring.databinding.FragConversationTvBinding
+import cx.ring.mvp.BaseSupportFragment
+import cx.ring.service.DRingService
+import cx.ring.tv.camera.CustomCameraActivity
+import cx.ring.utils.AndroidFileUtils.copyFileToUri
+import cx.ring.utils.AndroidFileUtils.createAudioFile
+import cx.ring.utils.AndroidFileUtils.getCacheFile
+import cx.ring.utils.AndroidFileUtils.getMimeTypeFromExtension
+import cx.ring.utils.AndroidFileUtils.getSpaceLeft
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ContentUriHandler.getUriForFile
+import cx.ring.utils.ConversationPath
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.conversation.ConversationPresenter
+import net.jami.conversation.ConversationView
+import net.jami.model.*
+import net.jami.model.Account.ComposingStatus
+import net.jami.utils.StringUtils
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+@AndroidEntryPoint
+class TvConversationFragment : BaseSupportFragment<ConversationPresenter, ConversationView>(),
+    ConversationView {
+    private var mConversationPath: ConversationPath? = null
+    private var mSelectedPosition = 0
+    private var fileName: File? = null
+    private var recorder: MediaRecorder? = null
+    private var player: MediaPlayer? = null
+    var mStartRecording = true
+    var mStartPlaying = true
+    private var mAdapter: TvConversationAdapter? = null
+    private var mConversationAvatar: AvatarDrawable? = null
+    private val mParticipantAvatars: MutableMap<String, AvatarDrawable> = HashMap()
+    private val mCompositeDisposable = CompositeDisposable()
+    private var binding: FragConversationTvBinding? = null
+    private var mCurrentFileAbsolutePath: String? = null
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        if (arguments != null) {
+            mConversationPath = ConversationPath.fromBundle(arguments)
+        }
+        savedInstanceState?.getString(KEY_AUDIOFILE)?.let { audioFile -> fileName = File(audioFile) }
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        fileName?.let { file -> outState.putString(KEY_AUDIOFILE, file.absolutePath) }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        binding = FragConversationTvBinding.inflate(inflater, container, false)
+        return binding!!.root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        binding = null
+        mCompositeDisposable.dispose()
+    }
+
+    // Create an intent that can start the Speech Recognizer activity
+    private fun displaySpeechRecognizer() {
+        if (!checkAudioPermission()) return
+        try {
+            val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
+                .putExtra(
+                    RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
+                )
+                .putExtra(
+                    RecognizerIntent.EXTRA_PROMPT,
+                    getText(R.string.conversation_input_speech_hint)
+                )
+            startActivityForResult(intent, REQUEST_SPEECH_CODE)
+        } catch (e: Exception) {
+            Snackbar.make(requireView(), "Can't get voice input", Snackbar.LENGTH_SHORT).show()
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        //ConversationPath path = ConversationPath.fromIntent(requireActivity().getIntent());
+        presenter.init(mConversationPath!!.conversationUri, mConversationPath!!.accountId)
+        mAdapter = TvConversationAdapter(this, presenter)
+
+        binding!!.let { binding ->
+            binding.buttonText.setOnClickListener { displaySpeechRecognizer() }
+            binding.buttonVideo.setOnClickListener {
+                val intent = Intent(activity, CustomCameraActivity::class.java)
+                    .setAction(MediaStore.ACTION_VIDEO_CAPTURE)
+                startActivityForResult(intent, REQUEST_CODE_PHOTO)
+            }
+
+            binding.buttonAudio.setOnClickListener {
+                onRecord(mStartRecording)
+                mStartRecording = !mStartRecording
+            }
+            binding.buttonText.onFocusChangeListener =
+                View.OnFocusChangeListener { _, hasFocus: Boolean ->
+                    TransitionManager.beginDelayedTransition(binding.textContainer)
+                    binding.textText.visibility = if (hasFocus) View.VISIBLE else View.GONE
+                }
+            binding.buttonAudio.onFocusChangeListener =
+                View.OnFocusChangeListener { _, hasFocus: Boolean ->
+                    TransitionManager.beginDelayedTransition(binding.audioContainer)
+                    binding.textAudio.visibility = if (hasFocus) View.VISIBLE else View.GONE
+                }
+            binding.buttonVideo.onFocusChangeListener =
+                View.OnFocusChangeListener { _, hasFocus: Boolean ->
+                    TransitionManager.beginDelayedTransition(binding.videoContainer)
+                    binding.textVideo.visibility = if (hasFocus) View.VISIBLE else View.GONE
+                }
+            val linearLayoutManager = LinearLayoutManager(context)
+            linearLayoutManager.reverseLayout = true
+            linearLayoutManager.stackFromEnd = true
+            binding.recyclerView.layoutManager = linearLayoutManager
+            binding.recyclerView.adapter = mAdapter
+        }
+    }
+
+    private fun checkAudioPermission(): Boolean {
+        if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO)
+            != PackageManager.PERMISSION_GRANTED) {
+            requestPermissions(permissions, REQUEST_RECORD_AUDIO_PERMISSION)
+            return false
+        }
+        return true
+    }
+
+    override fun onContextItemSelected(item: MenuItem): Boolean {
+        return if (mAdapter!!.onContextItemSelected(item)) true
+        else super.onContextItemSelected(item)
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            REQUEST_CODE_PHOTO -> if (resultCode == Activity.RESULT_OK && data != null) {
+                val media = data.extras!![MediaStore.EXTRA_OUTPUT] as Uri?
+                val type = data.type
+                createMediaDialog(media, type)
+            }
+            REQUEST_SPEECH_CODE -> if (resultCode == Activity.RESULT_OK && data != null) {
+                val results: List<String>? =
+                    data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
+                val spokenText = results!![0]
+                createTextDialog(spokenText)
+            }
+            REQUEST_CODE_SAVE_FILE -> {
+                if (resultCode == Activity.RESULT_OK) {
+                    data?.data?.let { writeToFile(it) }
+                }
+                super.onActivityResult(requestCode, resultCode, data)
+            }
+            else -> super.onActivityResult(requestCode, resultCode, data)
+        }
+    }
+
+    private fun writeToFile(data: Uri) {
+        val path = mCurrentFileAbsolutePath ?: return
+        val cr = context?.contentResolver ?: return
+        val input = File(path)
+        mCompositeDisposable.add(
+            copyFileToUri(cr, input, data)
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ Toast.makeText(context, R.string.file_saved_successfully, Toast.LENGTH_SHORT).show() })
+                { Toast.makeText(context, R.string.generic_error, Toast.LENGTH_SHORT).show() })
+    }
+
+    private fun createTextDialog(spokenText: String) {
+        if (StringUtils.isEmpty(spokenText)) {
+            return
+        }
+        val alertDialog =
+            MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
+                .setTitle(spokenText)
+                .setMessage("")
+                .setPositiveButton(R.string.tv_dialog_send) { dialog: DialogInterface?, whichButton: Int ->
+                    presenter!!.sendTextMessage(
+                        spokenText
+                    )
+                }
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
+        alertDialog.window!!
+            .setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
+        alertDialog.setOwnerActivity(requireActivity())
+        alertDialog.setOnShowListener { dialog: DialogInterface? ->
+            val positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+            positive.isFocusable = true
+            positive.isFocusableInTouchMode = true
+            positive.requestFocus()
+        }
+        alertDialog.show()
+    }
+
+    private fun createMediaDialog(media: Uri?, type: String?) {
+        if (media == null) {
+            return
+        }
+        val activity = activity ?: return
+        val file = getCacheFile(activity, media)
+        val alertDialog =
+            MaterialAlertDialogBuilder(activity, R.style.Theme_MaterialComponents_Dialog)
+                .setTitle(if (type == CustomCameraActivity.TYPE_IMAGE) R.string.tv_send_image_dialog_message else R.string.tv_send_video_dialog_message)
+                .setMessage("")
+                .setPositiveButton(R.string.tv_dialog_send) { dialog: DialogInterface?, whichButton: Int ->
+                    startFileSend(
+                        file.flatMapCompletable { file: File -> sendFile(file) })
+                }
+                .setNegativeButton(android.R.string.cancel, null)
+                .setNeutralButton(R.string.tv_media_preview, null)
+                .create()
+        alertDialog.window!!
+            .setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
+        alertDialog.setOwnerActivity(activity)
+        alertDialog.setOnShowListener { dialog: DialogInterface? ->
+            val positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+            positive.isFocusable = true
+            positive.isFocusableInTouchMode = true
+            positive.requestFocus()
+            val button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
+            button.setOnClickListener { v: View? ->
+                if (type == CustomCameraActivity.TYPE_IMAGE) {
+                    val i = Intent(context, MediaViewerActivity::class.java)
+                    i.setAction(Intent.ACTION_VIEW).setDataAndType(media, "image/*").flags =
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    startActivity(i)
+                } else {
+                    val intent = Intent(Intent.ACTION_VIEW, media)
+                    intent.setDataAndType(media, "video/*").flags =
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    startActivity(intent)
+                }
+            }
+        }
+        alertDialog.show()
+    }
+
+    private fun createAudioDialog() {
+        val alertDialog =
+            MaterialAlertDialogBuilder(requireContext(), R.style.Theme_MaterialComponents_Dialog)
+                .setTitle(R.string.tv_send_audio_dialog_message)
+                .setMessage("")
+                .setPositiveButton(R.string.tv_dialog_send) { dialog: DialogInterface?, whichButton: Int -> sendAudio() }
+                .setNegativeButton(android.R.string.cancel, null)
+                .setNeutralButton(R.string.tv_audio_play, null)
+                .create()
+        alertDialog.window!!
+            .setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
+        alertDialog.setOwnerActivity(requireActivity())
+        alertDialog.setOnShowListener { dialog: DialogInterface? ->
+            val positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
+            positive.isFocusable = true
+            positive.isFocusableInTouchMode = true
+            positive.requestFocus()
+            val button = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
+            button.setOnClickListener { v: View? ->
+                onPlay(mStartPlaying)
+                if (mStartPlaying) {
+                    button.setText(R.string.tv_audio_pause)
+                    if (player != null) {
+                        player!!.setOnCompletionListener { mp: MediaPlayer? ->
+                            button.setText(R.string.tv_audio_play)
+                            mStartPlaying = true
+                        }
+                    }
+                } else {
+                    button.setText(R.string.tv_audio_play)
+                }
+                mStartPlaying = !mStartPlaying
+            }
+        }
+        alertDialog.show()
+    }
+
+    override fun addElement(element: Interaction) {
+        mAdapter!!.add(element)
+        scrollToTop()
+    }
+
+    override fun shareFile(path: File, displayName: String) {
+        val c = context ?: return
+        try {
+            val fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path)
+            if (fileUri != null) {
+                val sendIntent = Intent()
+                sendIntent.action = Intent.ACTION_SEND
+                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                val type = c.contentResolver.getType(fileUri)
+                sendIntent.setDataAndType(fileUri, type)
+                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
+                startActivity(Intent.createChooser(sendIntent, null))
+            }
+        } catch (e: Exception) {
+            Snackbar.make(
+                requireView(),
+                "Error sharing file: " + e.localizedMessage,
+                Snackbar.LENGTH_SHORT
+            ).show()
+        }
+    }
+
+    override fun openFile(path: File, displayName: String) {
+        val c = context ?: return
+        try {
+            val fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path)
+            if (fileUri != null) {
+                val sendIntent = Intent()
+                sendIntent.action = Intent.ACTION_VIEW
+                sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                val type = c.contentResolver.getType(fileUri)
+                sendIntent.setDataAndType(fileUri, type)
+                sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
+                startActivity(Intent.createChooser(sendIntent, null))
+            }
+        } catch (e: IllegalArgumentException) {
+            Snackbar.make(
+                requireView(),
+                "Error opening file: " + e.localizedMessage,
+                Snackbar.LENGTH_SHORT
+            ).show()
+        }
+    }
+
+    /**
+     * Creates an intent using Android Storage Access Framework
+     * This intent is then received by applications that can handle it like
+     * Downloads or Google drive
+     * @param file DataTransfer of the file that is going to be stored
+     * @param currentFileAbsolutePath absolute path of the file we want to save
+     */
+    override fun startSaveFile(file: DataTransfer, currentFileAbsolutePath: String) {
+        mCurrentFileAbsolutePath = currentFileAbsolutePath
+        try {
+            // Use Android Storage File Access to download the file
+            val downloadFileIntent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+            downloadFileIntent.type = getMimeTypeFromExtension(file.extension)
+            downloadFileIntent.addCategory(Intent.CATEGORY_OPENABLE)
+            downloadFileIntent.putExtra(Intent.EXTRA_TITLE, file.displayName)
+            startActivityForResult(downloadFileIntent, REQUEST_CODE_SAVE_FILE)
+        } catch (e: Exception) {
+            Log.i(TAG, "No app detected for saving files.")
+            val directory =
+                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+            if (!directory.exists()) {
+                directory.mkdirs()
+            }
+            writeToFile(Uri.fromFile(File(directory, file.displayName)))
+        }
+    }
+
+    override fun refreshView(interactions: List<Interaction>) {
+        if (mAdapter != null) {
+            mAdapter!!.updateDataset(interactions)
+        }
+        requireActivity().invalidateOptionsMenu()
+    }
+
+    override fun onStop() {
+        releaseRecorder()
+        super.onStop()
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
+            // NOOP
+        }
+    }
+
+    private fun onRecord(start: Boolean) {
+        if (start) {
+            startRecording()
+        } else {
+            stopRecording()
+        }
+    }
+
+    private fun onPlay(start: Boolean) {
+        if (start) {
+            startPlaying()
+        } else {
+            stopPlaying()
+        }
+    }
+
+    private fun startPlaying() {
+        if (fileName == null) return
+        player = MediaPlayer()
+        try {
+            player!!.setDataSource(fileName!!.absolutePath)
+            player!!.prepare()
+            player!!.start()
+        } catch (e: IOException) {
+            Log.e(TAG, "prepare() failed")
+        }
+    }
+
+    private fun stopPlaying() {
+        player!!.release()
+        player = null
+    }
+
+    private fun startRecording() {
+        if (!checkAudioPermission()) return
+        if (recorder != null) {
+            return
+        }
+        try {
+            fileName = createAudioFile(requireContext())
+            recorder = MediaRecorder()
+            recorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
+            recorder!!.setOutputFile(fileName!!.absolutePath)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                recorder!!.setOutputFormat(MediaRecorder.OutputFormat.OGG)
+                recorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS)
+            } else {
+                recorder!!.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
+                recorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
+            }
+            recorder!!.prepare()
+            recorder!!.start()
+        } catch (e: Exception) {
+            Toast.makeText(
+                requireContext(),
+                "Error starting recording: " + e.localizedMessage,
+                Toast.LENGTH_LONG
+            ).show()
+            recorder?.let { rec ->
+                rec.release()
+                recorder = null
+            }
+            return
+        }
+        binding!!.buttonAudio.setImageResource(R.drawable.lb_ic_stop)
+        binding!!.textAudio.setText(R.string.tv_audio_recording)
+        val anim: Animation = AlphaAnimation(0.0f, 1.0f)
+        anim.duration = 500
+        anim.startOffset = 100
+        anim.repeatMode = Animation.REVERSE
+        anim.repeatCount = Animation.INFINITE
+        binding!!.textAudio.startAnimation(anim)
+    }
+
+    private fun releaseRecorder() {
+        if (recorder != null) {
+            try {
+                recorder!!.stop()
+            } catch (e: Exception) {
+                Log.w(TAG, "Exception stopping recorder")
+            }
+            recorder!!.release()
+            recorder = null
+        }
+    }
+
+    private fun stopRecording() {
+        releaseRecorder()
+        binding!!.buttonAudio.setImageResource(R.drawable.baseline_androidtv_message_audio)
+        binding!!.textAudio.setText(R.string.tv_send_audio)
+        binding!!.textAudio.clearAnimation()
+        createAudioDialog()
+    }
+
+    private fun sendAudio() {
+        val file = fileName
+        if (file != null) {
+            val singleFile = Single.just(file)
+            fileName = null
+            startFileSend(singleFile.flatMapCompletable { f -> sendFile(f) })
+        }
+    }
+
+    private fun startFileSend(op: Completable) {
+        op.observeOn(AndroidSchedulers.mainThread())
+            .subscribe({}) { e: Throwable? ->
+                Log.e(TAG, "startFileSend: not able to create cache file", e)
+                displayErrorToast(Error.INVALID_FILE)
+            }
+    }
+
+    private fun sendFile(file: File): Completable {
+        return Completable.fromAction { presenter!!.sendFile(file) }
+    }
+
+    fun updatePosition(position: Int) {
+        mSelectedPosition = position
+    }
+
+    fun updateAdapterItem() {
+        if (mSelectedPosition != -1) {
+            mAdapter!!.notifyItemChanged(mSelectedPosition)
+            mSelectedPosition = -1
+        }
+    }
+
+    private fun scrollToTop() {
+        if (mAdapter!!.itemCount > 0) {
+            binding!!.recyclerView.scrollToPosition(mAdapter!!.itemCount - 1)
+        }
+    }
+
+    override fun displayContact(conversation: Conversation) {
+        val contacts = conversation.contacts
+        mCompositeDisposable.clear()
+        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true)
+            .doOnSuccess { d: Drawable? ->
+                mConversationAvatar = d as AvatarDrawable?
+                mParticipantAvatars[contacts[0].primaryNumber] = AvatarDrawable(d!!)
+            }
+            .flatMapObservable { d: Drawable? -> contacts[0].updatesSubject }
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { c: Contact ->
+                val id = c.ringUsername
+                val displayName = c.displayName
+                if (binding != null) {
+                    binding!!.title.text = displayName
+                    if (TextUtils.isEmpty(displayName) || displayName != id) binding!!.subtitle.text =
+                        id else binding!!.subtitle.visibility = View.GONE
+                }
+                mConversationAvatar!!.update(c)
+                val uri = contacts[0].primaryNumber
+                val a = mParticipantAvatars[uri]
+                a?.update(c)
+                if (mAdapter != null) mAdapter!!.setPhoto()
+            })
+    }
+
+    override fun updateElement(element: Interaction) {
+        mAdapter!!.update(element)
+    }
+
+    override fun removeElement(element: Interaction) {
+        mAdapter!!.remove(element)
+    }
+
+    fun getConversationAvatar(uri: String): AvatarDrawable? {
+        return mParticipantAvatars[uri]
+    }
+
+    override fun askWriteExternalStoragePermission() {
+        requestPermissions(
+            arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
+            JamiApplication.PERMISSIONS_REQUEST
+        )
+    }
+
+    override fun scrollToEnd() {}
+    override fun updateContact(contact: Contact) {
+        mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), contact, true)
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { avatar: Drawable ->
+                mParticipantAvatars[contact.primaryNumber] = avatar as AvatarDrawable
+                mAdapter!!.setPhoto()
+            })
+    }
+
+    override fun setComposingStatus(composingStatus: ComposingStatus) {}
+    override fun setLastDisplayed(interaction: Interaction) {}
+    override fun setConversationColor(integer: Int) {}
+    override fun setConversationSymbol(symbol: CharSequence) {}
+    override fun startShareLocation(accountId: String, contactId: String) {}
+    override fun showMap(accountId: String, contactId: String, open: Boolean) {}
+    override fun hideMap() {}
+    override fun showPluginListHandlers(accountId: String, contactId: String) {}
+    override fun hideErrorPanel() {}
+    override fun displayNetworkErrorPanel() {}
+    override fun displayAccountOfflineErrorPanel() {}
+    override fun setReadIndicatorStatus(show: Boolean) {}
+    override fun updateLastRead(last: String) {}
+    override fun displayOnGoingCallPane(display: Boolean) {}
+    override fun displayNumberSpinner(conversation: Conversation, number: net.jami.model.Uri) {}
+    override fun hideNumberSpinner() {}
+    override fun clearMsgEdit() {}
+    override fun goToHome() {}
+    override fun goToAddContact(contact: Contact) {}
+    override fun goToCallActivity(conferenceId: String) {}
+    override fun goToCallActivityWithResult(
+        accountId: String,
+        conversationUri: net.jami.model.Uri,
+        contactRingId: net.jami.model.Uri,
+        audioOnly: Boolean
+    ) {
+    }
+
+    override fun goToContactActivity(accountId: String, contactRingId: net.jami.model.Uri) {}
+    override fun switchToUnknownView(name: String) {
+        // todo
+    }
+
+    override fun switchToIncomingTrustRequestView(message: String) {
+        // todo
+    }
+
+    override fun switchToConversationView() {
+        // todo
+    }
+
+    override fun switchToSyncingView() {
+        // todo
+    }
+
+    override fun switchToEndedView() {
+        // todo
+    }
+
+    override fun openFilePicker() {}
+    override fun acceptFile(
+        accountId: String,
+        conversationUri: net.jami.model.Uri,
+        transfer: DataTransfer
+    ) {
+        val cacheDir = requireContext().cacheDir
+        val spaceLeft = getSpaceLeft(cacheDir.toString())
+        if (spaceLeft == -1L || transfer.totalSize > spaceLeft) {
+            presenter!!.noSpaceLeft()
+            return
+        }
+        requireActivity().startService(
+            Intent(DRingService.ACTION_FILE_ACCEPT)
+                .setClass(requireContext(), DRingService::class.java)
+                .setData(ConversationPath.toUri(accountId, conversationUri))
+                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
+                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
+        )
+    }
+
+    override fun refuseFile(
+        accountId: String,
+        conversationUri: net.jami.model.Uri,
+        transfer: DataTransfer
+    ) {
+        requireActivity().startService(
+            Intent(DRingService.ACTION_FILE_CANCEL)
+                .setClass(requireContext(), DRingService::class.java)
+                .setData(ConversationPath.toUri(accountId, conversationUri))
+                .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId)
+                .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)
+        )
+    }
+
+    companion object {
+        private val TAG = TvConversationFragment::class.java.simpleName
+        private const val ARG_MODEL = "model"
+        private const val KEY_AUDIOFILE = "audiofile"
+        private const val REQUEST_CODE_PHOTO = 101
+        private const val REQUEST_SPEECH_CODE = 102
+        private const val REQUEST_CODE_SAVE_FILE = 103
+        private const val DIALOG_WIDTH = 900
+        private const val DIALOG_HEIGHT = 400
+        private val permissions = arrayOf(Manifest.permission.RECORD_AUDIO)
+        private const val REQUEST_RECORD_AUDIO_PERMISSION = 200
+
+        fun newInstance(args: Bundle?): TvConversationFragment {
+            return TvConversationFragment().apply {
+                arguments = args
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
index 67bfe3b32..55cb16974 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
@@ -20,6 +20,8 @@
 package cx.ring.tv.main;
 
 import android.os.Bundle;
+
+import androidx.annotation.NonNull;
 import androidx.leanback.app.BrowseSupportFragment;
 import android.view.View;
 import android.widget.Toast;
@@ -39,7 +41,7 @@ public class BaseBrowseFragment<T extends RootPresenter> extends BrowseSupportFr
     protected T presenter;
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         //Be sure to do the injection in onCreateView method
         presenter.bindView(this);
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.java
index d1b351edc..61368718e 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.java
@@ -4,6 +4,7 @@
  *  Author: Michel Schmit <michel.schmit@savoirfairelinux.com>
  *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
  *
  *  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
@@ -20,9 +21,24 @@
  */
 package cx.ring.tv.main;
 
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.renderscript.Type;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 
+import androidx.core.content.ContextCompat;
 import androidx.fragment.app.FragmentActivity;
 import androidx.leanback.app.BackgroundManager;
 import androidx.leanback.app.GuidedStepSupportFragment;
@@ -34,28 +50,111 @@ import javax.inject.Inject;
 import cx.ring.R;
 import cx.ring.application.JamiApplication;
 import cx.ring.tv.account.TVAccountWizard;
+import dagger.hilt.android.AndroidEntryPoint;
+import cx.ring.tv.camera.CameraPreview;
+import cx.ring.tv.contact.TVContactFragment;
+import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
 
+@AndroidEntryPoint
 public class HomeActivity extends FragmentActivity {
-    private BackgroundManager mBackgroundManager;
-    private final CompositeDisposable mDisposable = new CompositeDisposable();
+
+    private static final String TAG = HomeActivity.class.getSimpleName();
+
+    private static final float BITMAP_SCALE = 0.4f;
+    private static final float BLUR_RADIUS = 7.5f;
+
+    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
+
     @Inject
     AccountService mAccountService;
 
+    private BackgroundManager mBackgroundManager;
+    private ImageView mBlurImage;
+    private FrameLayout mPreviewView;
+    private View mFadeView;
+    private Camera mCamera;
+    private CameraPreview mCameraPreview;
+    private CameraManager mCameraManager;
+
+    private Bitmap mBlurOutputBitmap;
+    private RenderScript rs;
+    private ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic;
+    private ScriptIntrinsicBlur blurIntrinsic;
+    private Allocation in, out, mBlurOut;
+
+    private final Camera.ErrorCallback mErrorCallback = new Camera.ErrorCallback() {
+        @Override
+        public void onError(int error, Camera camera) {
+            mBlurImage.setVisibility(View.INVISIBLE);
+            mBackgroundManager.setDrawable(ContextCompat.getDrawable(HomeActivity.this, R.drawable.tv_background));
+        }
+    };
+
+    private final Object mCameraAvailabilityCallback = new CameraManager.AvailabilityCallback() {
+        @Override
+        public void onCameraAvailable(String cameraId) {
+            if (mBlurImage.getVisibility() == View.INVISIBLE) {
+                setUpCamera();
+            }
+        }
+    };
+
+    private final Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
+        @Override
+        public void onPreviewFrame(byte[] data, Camera camera) {
+            if (getSupportFragmentManager().findFragmentByTag(TVContactFragment.TAG) != null) {
+                mBlurImage.setVisibility(View.GONE);
+                mFadeView.setVisibility(View.GONE);
+                mPreviewView.setVisibility(View.VISIBLE);
+                return;
+            }
+            if (mBlurOutputBitmap == null) {
+                Camera.Size size = camera.getParameters().getPreviewSize();
+                rs = RenderScript.create(HomeActivity.this);
+                Type yuvType = new Type.Builder(rs, Element.U8(rs)).setX(data.length).create();
+                in = Allocation.createTyped(rs, yuvType, Allocation.USAGE_SCRIPT);
+                yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));
+                yuvToRgbIntrinsic.setInput(in);
+                Type rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(size.width).setY(size.height).create();
+                out = Allocation.createTyped(rs, rgbaType, Allocation.USAGE_SCRIPT);
+                blurIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
+                blurIntrinsic.setRadius(BLUR_RADIUS * size.width / 1080);
+                blurIntrinsic.setInput(out);
+                mBlurOutputBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
+                mBlurOut = Allocation.createFromBitmap(rs, mBlurOutputBitmap);
+            }
+            in.copyFrom(data);
+            yuvToRgbIntrinsic.forEach(out);
+            blurIntrinsic.forEach(mBlurOut);
+            mBlurOut.copyTo(mBlurOutputBitmap);
+            mBlurImage.setImageBitmap(mBlurOutputBitmap);
+            if (mBlurImage.getVisibility() == View.GONE) {
+                mPreviewView.setVisibility(View.INVISIBLE);
+                mBlurImage.setVisibility(View.VISIBLE);
+                mFadeView.setVisibility(View.VISIBLE);
+            }
+        }
+    };
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         JamiApplication.getInstance().startDaemon();
-        JamiApplication.getInstance().getInjectionComponent().inject(this);
         setContentView(R.layout.tv_activity_home);
         mBackgroundManager = BackgroundManager.getInstance(this);
         mBackgroundManager.attach(getWindow());
+        mPreviewView = findViewById(R.id.previewView);
+        mBlurImage = findViewById(R.id.blur);
+        mFadeView = findViewById(R.id.fade);
     }
 
     @Override
     public void onBackPressed() {
         if (GuidedStepSupportFragment.getCurrentGuidedStepSupportFragment(getSupportFragmentManager()) != null) {
+//            mIsContactFragmentVisible = false;
             getSupportFragmentManager().popBackStack();
         } else {
             super.onBackPressed();
@@ -65,9 +164,8 @@ public class HomeActivity extends FragmentActivity {
     @Override
     protected void onResume() {
         super.onResume();
-        mBackgroundManager.setDrawable(getDrawable(R.drawable.tv_background));
-        mDisposable.clear();
-        mDisposable.add(mAccountService.getObservableAccountList()
+        //mDisposable.clear();
+        mDisposableBag.add(mAccountService.getObservableAccountList()
                 .observeOn(AndroidSchedulers.mainThread())
                 .firstElement()
                 .subscribe(accounts -> {
@@ -76,4 +174,94 @@ public class HomeActivity extends FragmentActivity {
                     }
                 }));
     }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        setUpCamera();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mCameraPreview != null) {
+            mCamera.setPreviewCallback(null);
+            mCameraPreview.stop();
+            mCameraPreview = null;
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDisposableBag.dispose();
+        if (mCameraManager != null) {
+            mCameraManager.unregisterAvailabilityCallback((CameraManager.AvailabilityCallback) mCameraAvailabilityCallback);
+        }
+        if (mCamera != null) {
+            mCamera.release();
+            mCamera = null;
+        }
+        if (mBlurOutputBitmap != null){
+            in.destroy();
+            in = null;
+            out.destroy();
+            out = null;
+            blurIntrinsic.destroy();
+            blurIntrinsic = null;
+            mBlurOut.destroy();
+            mBlurOut = null;
+            yuvToRgbIntrinsic.destroy();
+            yuvToRgbIntrinsic = null;
+            mBlurOutputBitmap.recycle();
+            mBlurOutputBitmap = null;
+            rs.destroy();
+            rs = null;
+        }
+    }
+
+    private Camera getCameraInstance() {
+        try {
+            int currentCamera = 0;
+            mCamera = Camera.open(currentCamera);
+        }
+        catch (RuntimeException e) {
+            Log.e(TAG, "failed to open camera");
+        }
+        return mCamera;
+    }
+
+    private void setUpCamera() {
+        mDisposableBag.add(Single.fromCallable(this::getCameraInstance)
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe(camera -> {
+                    Log.w(TAG, "setUpCamera()");
+                    Camera.Parameters params = camera.getParameters();
+                    Camera.Size selectSize = null;
+                    for (Camera.Size size :  params.getSupportedPictureSizes()) {
+                        if (size.width == 1280 && size.height == 720) {
+                            selectSize = size;
+                            break;
+                        }
+                    }
+                    if (selectSize == null)
+                        throw new IllegalStateException("No supported size");
+                    Log.w(TAG, "setUpCamera() selectSize " + selectSize.width + "x" + selectSize.height);
+                    params.setPictureSize(selectSize.width, selectSize.height);
+                    params.setPreviewSize(selectSize.width, selectSize.height);
+                    camera.setParameters(params);
+                    mBlurImage.setVisibility(View.VISIBLE);
+                    if (mCameraManager == null) {
+                        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+                        mCameraManager.registerAvailabilityCallback((CameraManager.AvailabilityCallback) mCameraAvailabilityCallback, null);
+                    }
+                    mCameraPreview = new CameraPreview(this, camera);
+                    mPreviewView.removeAllViews();
+                    mPreviewView.addView(mCameraPreview, 0);
+                    camera.setErrorCallback(mErrorCallback);
+                    camera.setPreviewCallback(mPreviewCallback);
+                }, e -> mBackgroundManager.setDrawable(ContextCompat.getDrawable(HomeActivity.this, R.drawable.tv_background))));
+    }
+
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
deleted file mode 100644
index 93a909e58..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>s
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.main;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.ActivityOptionsCompat;
-import androidx.core.content.FileProvider;
-import androidx.leanback.app.GuidedStepSupportFragment;
-import androidx.leanback.widget.ArrayObjectAdapter;
-import androidx.leanback.widget.HeaderItem;
-import androidx.leanback.widget.ImageCardView;
-import androidx.leanback.widget.ListRow;
-import androidx.leanback.widget.OnItemViewClickedListener;
-import androidx.leanback.widget.Presenter;
-import androidx.leanback.widget.Row;
-import androidx.leanback.widget.RowPresenter;
-import androidx.tvprovider.media.tv.Channel;
-import androidx.tvprovider.media.tv.ChannelLogoUtils;
-import androidx.tvprovider.media.tv.PreviewProgram;
-import androidx.tvprovider.media.tv.TvContractCompat;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.views.AvatarFactory;
-
-import net.jami.model.Account;
-import net.jami.navigation.HomeNavigationViewModel;
-import cx.ring.services.VCardServiceImpl;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.tv.about.AboutActivity;
-import cx.ring.tv.account.TVAccountExport;
-import cx.ring.tv.account.TVProfileEditingFragment;
-import cx.ring.tv.account.TVSettingsActivity;
-import cx.ring.tv.account.TVShareActivity;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.tv.cards.Card;
-import cx.ring.tv.cards.CardListRow;
-import cx.ring.tv.cards.CardPresenterSelector;
-import cx.ring.tv.cards.CardRow;
-import cx.ring.tv.cards.ShadowRowPresenterSelector;
-import cx.ring.tv.cards.contacts.ContactCard;
-import cx.ring.tv.cards.iconcards.IconCard;
-import cx.ring.tv.cards.iconcards.IconCardHelper;
-import cx.ring.tv.contact.TVContactActivity;
-import cx.ring.tv.search.SearchActivity;
-import cx.ring.tv.views.CustomTitleView;
-import cx.ring.utils.AndroidFileUtils;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.utils.ContentUriHandler;
-import cx.ring.utils.ConversationPath;
-import net.jami.utils.QRCodeUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class MainFragment extends BaseBrowseFragment<MainPresenter> implements MainView {
-
-    private static final String TAG = MainFragment.class.getSimpleName();
-    // Sections headers ids
-    private static final long HEADER_CONTACTS = 0;
-    private static final long HEADER_MISC = 1;
-    private static final int TRUST_REQUEST_ROW_POSITION = 1;
-    private static final int QR_ITEM_POSITION = 2;
-
-    private static final String PREFERENCES_CHANNELS = "channels";
-    private static final String KEY_CHANNEL_CONVERSATIONS = "conversations";
-
-    private static final Uri HOME_URI = new Uri.Builder()
-            .scheme(ContentUriHandler.SCHEME_TV)
-            .authority(ContentUriHandler.AUTHORITY)
-            .appendPath(ContentUriHandler.PATH_TV_HOME)
-            .build();
-
-    private SpinnerFragment mSpinnerFragment;
-    private ArrayObjectAdapter mRowsAdapter;
-    private ArrayObjectAdapter cardRowAdapter;
-    private ArrayObjectAdapter contactRequestRowAdapter;
-    private CustomTitleView titleView;
-    private CardListRow requestsRow;
-    private CardPresenterSelector selector;
-    private IconCard qrCard = null;
-    private ListRow myAccountRow;
-    private final CompositeDisposable mDisposable = new CompositeDisposable();
-    private final CompositeDisposable mHomeChannelDisposable = new CompositeDisposable();
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        return super.onCreateView(inflater, container, savedInstanceState);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        titleView = view.findViewById(R.id.browse_title_group);
-        super.onViewCreated(view, savedInstanceState);
-        setupUIElements(requireActivity());
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mDisposable.clear();
-    }
-
-    private void setupUIElements(@NonNull Activity activity) {
-        selector = new CardPresenterSelector(activity);
-        // over title
-        setHeadersState(HEADERS_ENABLED);
-        setHeadersTransitionOnBackEnabled(true);
-
-        // set fastLane (or headers) background color
-        setBrandColor(getResources().getColor(R.color.color_primary_dark));
-        // set search icon color
-        setSearchAffordanceColor(getResources().getColor(R.color.color_primary_light));
-
-        mRowsAdapter = new ArrayObjectAdapter(new ShadowRowPresenterSelector());
-
-        /* Contact Presenter */
-        CardRow contactRow = new CardRow(
-                CardRow.TYPE_DEFAULT,
-                true,
-                getString(R.string.tv_contact_row_header),
-                new ArrayList<>());
-        HeaderItem cardPresenterHeader = new HeaderItem(HEADER_CONTACTS, getString(R.string.tv_contact_row_header));
-        cardRowAdapter = new ArrayObjectAdapter(selector);
-
-        CardListRow contactListRow = new CardListRow(cardPresenterHeader, cardRowAdapter, contactRow);
-
-        /* CardPresenter */
-        mRowsAdapter.add(contactListRow);
-        myAccountRow = createMyAccountRow(activity);
-        mRowsAdapter.add(myAccountRow);
-        mRowsAdapter.add(createAboutCardRow(activity));
-        setAdapter(mRowsAdapter);
-
-        // listeners
-        setOnSearchClickedListener(view -> startActivity(new Intent(getActivity(), SearchActivity.class)));
-        setOnItemViewClickedListener(new ItemViewClickedListener());
-    }
-
-    private ListRow createRow(String titleSection, List<Card> cards, boolean shadow) {
-        CardRow row = new CardRow(
-                CardRow.TYPE_DEFAULT,
-                shadow,
-                titleSection,
-                cards);
-
-        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(selector);
-        for (Card card : cards) {
-            listRowAdapter.add(card);
-        }
-
-        return new CardListRow(new HeaderItem(HEADER_MISC, titleSection), listRowAdapter, row);
-    }
-
-    private ListRow createMyAccountRow(@NonNull Context context) {
-        qrCard = IconCardHelper.getAccountShareCard(context, null);
-        List<Card> cards = new ArrayList<>(4);
-        cards.add(IconCardHelper.getAccountAddDeviceCard(context));
-        cards.add(IconCardHelper.getAccountManagementCard(context));
-        cards.add(qrCard);
-        cards.add(IconCardHelper.getAccountSettingsCard(context));
-        return createRow(getString(R.string.ring_account), cards, false);
-    }
-
-    private CardListRow createContactRequestRow() {
-        CardRow contactRequestRow = new CardRow(
-                CardRow.TYPE_DEFAULT,
-                true,
-                getString(R.string.menu_item_contact_request),
-                new ArrayList<ContactCard>());
-
-        contactRequestRowAdapter = new ArrayObjectAdapter(selector);
-
-        return new CardListRow(new HeaderItem(HEADER_MISC, getString(R.string.menu_item_contact_request)),
-                contactRequestRowAdapter,
-                contactRequestRow);
-    }
-
-    private Row createAboutCardRow(@NonNull Context context) {
-        List<Card> cards = new ArrayList<>(3);
-        cards.add(IconCardHelper.getVersionCard(context));
-        cards.add(IconCardHelper.getLicencesCard(context));
-        cards.add(IconCardHelper.getContributorCard(context));
-        return createRow(getString(R.string.menu_item_about), cards, false);
-    }
-
-    @Override
-    public void showLoading(final boolean show) {
-        if (show) {
-            mSpinnerFragment = new SpinnerFragment();
-            getParentFragmentManager().beginTransaction().replace(R.id.main_browse_fragment, mSpinnerFragment).commitAllowingStateLoss();
-        } else {
-            getParentFragmentManager().beginTransaction().remove(mSpinnerFragment).commitAllowingStateLoss();
-        }
-    }
-
-    @Override
-    public void refreshContact(final int index, final SmartListViewModel contact) {
-        ContactCard contactCard = (ContactCard) cardRowAdapter.get(index);
-        contactCard.setModel(contact);
-        cardRowAdapter.replace(index, contactCard);
-    }
-
-    @Override
-    public void showContacts(final List<SmartListViewModel> contacts) {
-        List<ContactCard> cards = new ArrayList<>(contacts.size());
-        for (SmartListViewModel contact : contacts)
-            cards.add(new ContactCard(contact));
-        cardRowAdapter.setItems(cards, null);
-        buildHomeChannel(requireContext().getApplicationContext(), contacts);
-    }
-
-    private static long createHomeChannel(Context context)  {
-        Channel channel = new Channel.Builder()
-                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
-                .setDisplayName(context.getString(R.string.navigation_item_conversation))
-                .setAppLinkIntentUri(HOME_URI)
-                .build();
-        ContentResolver cr = context.getContentResolver();
-        SharedPreferences sharedPref = context.getSharedPreferences(PREFERENCES_CHANNELS, Context.MODE_PRIVATE);
-        long channelId = sharedPref.getLong(KEY_CHANNEL_CONVERSATIONS, -1);
-        if (channelId == -1) {
-            Uri channelUri = cr.insert(TvContractCompat.Channels.CONTENT_URI, channel.toContentValues());
-            channelId = ContentUris.parseId(channelUri);
-            sharedPref.edit().putLong(KEY_CHANNEL_CONVERSATIONS, channelId).apply();
-            int targetSize = (int) (AvatarFactory.SIZE_NOTIF * context.getResources().getDisplayMetrics().density);
-            int targetPaddingSize = (int) (AvatarFactory.SIZE_PADDING * context.getResources().getDisplayMetrics().density);
-            ChannelLogoUtils.storeChannelLogo(context, channelId, BitmapUtils.drawableToBitmap(context.getDrawable(R.drawable.ic_jami_48), targetSize, targetPaddingSize));
-            TvContractCompat.requestChannelBrowsable(context, channelId);
-        } else {
-            cr.update(TvContractCompat.buildChannelUri(channelId), channel.toContentValues(), null, null);
-        }
-        return channelId;
-    }
-
-    private static Single<PreviewProgram> buildProgram(Context context, SmartListViewModel vm, String launcherName, long channelId) {
-        return new AvatarDrawable.Builder()
-                .withViewModel(vm)
-                .withPresence(false)
-                .buildAsync(context)
-                .map(avatar -> {
-                    File file = AndroidFileUtils.createImageFile(context);
-                    Bitmap bitmapAvatar = BitmapUtils.drawableToBitmap(avatar, 256);
-                    try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
-                        bitmapAvatar.compress(Bitmap.CompressFormat.PNG, 100, os);
-                    }
-                    bitmapAvatar.recycle();
-                    Uri uri = FileProvider.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file);
-
-                    // Grant permission to launcher
-                    if (launcherName != null)
-                        context.grantUriPermission(launcherName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-
-                    PreviewProgram.Builder contactBuilder = new PreviewProgram.Builder()
-                            .setChannelId(channelId)
-                            .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
-                            .setTitle(vm.getContactName())
-                            .setAuthor(vm.getContacts().get(0).getRingUsername())
-                            .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_1_1)
-                            .setPosterArtUri(uri)
-                            .setIntentUri(new Uri.Builder()
-                                    .scheme(ContentUriHandler.SCHEME_TV)
-                                    .authority(ContentUriHandler.AUTHORITY)
-                                    .appendPath(ContentUriHandler.PATH_TV_CONVERSATION)
-                                    .appendPath(vm.getAccountId())
-                                    .appendPath(vm.getUri().getUri())
-                                    .build())
-                            .setInternalProviderId(vm.getUuid());
-                    return contactBuilder.build();
-                });
-    }
-
-    private void buildHomeChannel(Context context, List<SmartListViewModel> contacts) {
-        if (contacts.isEmpty())
-            return;
-
-        // Get launcher package name
-        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
-                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), PackageManager.MATCH_DEFAULT_ONLY);
-        String launcherName = resolveInfo == null ? null : resolveInfo.activityInfo.packageName;
-
-        ContentResolver cr = context.getContentResolver();
-
-        mHomeChannelDisposable.clear();
-        mHomeChannelDisposable.add(Single.fromCallable(() -> createHomeChannel(context))
-                .doOnSuccess(channelId -> cr.delete(TvContractCompat.buildPreviewProgramsUriForChannel(channelId), null, null))
-                .flatMapObservable(channelId -> Observable.fromIterable(contacts)
-                        .concatMapEager(contact -> buildProgram(context, contact, launcherName, channelId)
-                                .toObservable()
-                                .subscribeOn(Schedulers.io()), 8, 1))
-                .subscribeOn(Schedulers.io())
-                .subscribe(program -> cr.insert(TvContractCompat.PreviewPrograms.CONTENT_URI, program.toContentValues()),
-                        e -> Log.w(TAG, "Error updating home channel", e)));
-    }
-
-    @Override
-    public void showContactRequests(final List<SmartListViewModel> contacts) {
-        CardListRow row = (CardListRow) mRowsAdapter.get(TRUST_REQUEST_ROW_POSITION);
-        boolean isRowDisplayed = row != null && row == requestsRow;
-
-        List<ContactCard> cards = new ArrayList<>(contacts.size());
-        for (SmartListViewModel contact : contacts)
-            cards.add(new ContactCard(contact));
-
-        if (isRowDisplayed && contacts.isEmpty()) {
-            mRowsAdapter.removeItems(TRUST_REQUEST_ROW_POSITION, 1);
-        } else if (!contacts.isEmpty()) {
-            if (requestsRow == null)
-                requestsRow = createContactRequestRow();
-            contactRequestRowAdapter.setItems(cards, null);
-            if (!isRowDisplayed)
-                mRowsAdapter.add(TRUST_REQUEST_ROW_POSITION, requestsRow);
-        }
-    }
-
-    @Override
-    public void callContact(String accountID, String number) {
-        Intent intent = new Intent(Intent.ACTION_CALL, ConversationPath.toUri(accountID, number), getActivity(), TVCallActivity.class);
-        startActivity(intent, null);
-    }
-
-    static private BitmapDrawable prepareAccountQr(Context context, String accountId) {
-        Log.w(TAG, "prepareAccountQr " + accountId);
-        if (TextUtils.isEmpty(accountId))
-            return null;
-        int pad = 16;
-        QRCodeUtils.QRCodeData qrCodeData = QRCodeUtils.encodeStringAsQRCodeData(accountId, 0X00000000, 0xFFFFFFFF);
-        Bitmap bitmap = Bitmap.createBitmap(qrCodeData.getWidth() + 2 * pad, qrCodeData.getHeight() + 2 * pad, Bitmap.Config.ARGB_8888);
-        bitmap.setPixels(qrCodeData.getData(), 0, qrCodeData.getWidth(), pad, pad, qrCodeData.getWidth(), qrCodeData.getHeight());
-        return new BitmapDrawable(context.getResources(), bitmap);
-    }
-
-    @Override
-    public void displayAccountInfos(final HomeNavigationViewModel viewModel) {
-        Account account = viewModel.getAccount();
-        if (account != null)
-            updateModel(account);
-    }
-
-    @Override
-    public void updateModel(Account account) {
-        Context context = requireContext();
-        String address = account.getDisplayUsername();
-        mDisposable.clear();
-        mDisposable.add(VCardServiceImpl
-                .loadProfile(context, account)
-                .observeOn(AndroidSchedulers.mainThread())
-                .doOnSuccess(profile -> {
-                    if (profile.first != null && !profile.first.isEmpty()) {
-                        titleView.setAlias(profile.first);
-                        if (address != null) {
-                            setTitle(address);
-                        } else {
-                            setTitle("");
-                        }
-                    } else {
-                        titleView.setAlias(address);
-                    }
-                })
-                .flatMap(p -> AvatarDrawable.load(context, account))
-                .subscribe(a -> {
-                    titleView.getLogoView().setVisibility(View.VISIBLE);
-                    titleView.getLogoView().setImageDrawable(a);
-                }));
-        qrCard.setDrawable(prepareAccountQr(context, account.getUri()));
-        myAccountRow.getAdapter().notifyItemRangeChanged(QR_ITEM_POSITION, 1);
-    }
-
-    @Override
-    public void showExportDialog(String pAccountID, boolean hasPassword) {
-        GuidedStepSupportFragment wizard = TVAccountExport.createInstance(pAccountID, hasPassword);
-        GuidedStepSupportFragment.add(getParentFragmentManager(), wizard, R.id.main_browse_fragment);
-    }
-
-    @Override
-    public void showProfileEditing() {
-        GuidedStepSupportFragment.add(getParentFragmentManager(), new TVProfileEditingFragment(), R.id.main_browse_fragment);
-    }
-
-    @Override
-    public void showAccountShare() {
-        Intent intent = new Intent(getActivity(), TVShareActivity.class);
-        startActivity(intent);
-    }
-
-    @Override
-    public void showLicence(int aboutType) {
-        Intent intent = new Intent(getActivity(), AboutActivity.class);
-        intent.putExtra("abouttype", aboutType);
-        startActivity(intent);
-    }
-
-    @Override
-    public void showSettings() {
-        startActivity(new Intent(getActivity(), TVSettingsActivity.class));
-    }
-
-    private final class ItemViewClickedListener implements OnItemViewClickedListener {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
-
-            if (item instanceof ContactCard) {
-                SmartListViewModel model = ((ContactCard) item).getModel();
-                if (row == requestsRow) {
-                    Intent intent = new Intent(Intent.ACTION_VIEW, null, requireContext(), TVContactActivity.class)
-                            .setDataAndType(ConversationPath.toUri(model.getAccountId(), model.getUri()), TVContactActivity.TYPE_CONTACT_REQUEST_INCOMING);
-                    Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
-                            requireActivity(),
-                            ((ImageCardView) itemViewHolder.view).getMainImageView(),
-                            TVContactActivity.SHARED_ELEMENT_NAME).toBundle();
-                    startActivity(intent, bundle);
-                } else {
-                    Intent intent = new Intent(Intent.ACTION_VIEW, ConversationPath.toUri(model.getAccountId(), model.getUri()), getActivity(), TVContactActivity.class);
-
-                    Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(),
-                            ((ImageCardView) itemViewHolder.view).getMainImageView(),
-                            TVContactActivity.SHARED_ELEMENT_NAME).toBundle();
-                    startActivity(intent, bundle);
-                }
-            } else if (item instanceof IconCard) {
-                IconCard card = (IconCard) item;
-                switch (card.getType()) {
-                    case ABOUT_CONTRIBUTOR:
-                    case ABOUT_LICENCES:
-                        presenter.onLicenceClicked(card.getType().ordinal());
-                        break;
-                    case ACCOUNT_ADD_DEVICE:
-                        presenter.onExportClicked();
-                        break;
-                    case ACCOUNT_EDIT_PROFILE:
-                        presenter.onEditProfileClicked();
-                        break;
-                    case ACCOUNT_SHARE_ACCOUNT:
-                        ImageView view = ((ImageCardView) itemViewHolder.view).getMainImageView();
-                        Intent intent = new Intent(getActivity(), TVShareActivity.class);
-                        Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, TVShareActivity.SHARED_ELEMENT_NAME).toBundle();
-                        requireActivity().startActivity(intent, bundle);
-                        break;
-                    case ACCOUNT_SETTINGS:
-                        presenter.onSettingsClicked();
-                        break;
-                    default:
-                        break;
-                }
-            }
-        }
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
new file mode 100644
index 000000000..44eceaf33
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt
@@ -0,0 +1,423 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>s
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.main
+
+import android.content.*
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.content.FileProvider
+import androidx.leanback.app.GuidedStepSupportFragment
+import androidx.leanback.widget.*
+import androidx.tvprovider.media.tv.Channel
+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.services.VCardServiceImpl.Companion.loadProfile
+import cx.ring.tv.account.TVAccountExport
+import cx.ring.tv.account.TVProfileEditingFragment
+import cx.ring.tv.account.TVShareActivity
+import cx.ring.tv.call.TVCallActivity
+import cx.ring.tv.cards.*
+import cx.ring.tv.cards.contacts.ContactCard
+import cx.ring.tv.cards.iconcards.IconCard
+import cx.ring.tv.cards.iconcards.IconCardHelper
+import cx.ring.tv.contact.TVContactActivity
+import cx.ring.tv.contact.TVContactFragment
+import cx.ring.tv.search.SearchActivity
+import cx.ring.tv.settings.TVSettingsActivity
+import cx.ring.tv.views.CustomTitleView
+import cx.ring.utils.AndroidFileUtils.createImageFile
+import cx.ring.utils.BitmapUtils.drawableToBitmap
+import cx.ring.utils.ContentUriHandler
+import cx.ring.utils.ConversationPath
+import cx.ring.views.AvatarDrawable
+import cx.ring.views.AvatarFactory
+import dagger.hilt.android.AndroidEntryPoint
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Account
+import net.jami.navigation.HomeNavigationViewModel
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.QRCodeUtils
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.util.*
+import cx.ring.tv.cards.ShadowRowPresenterSelector
+
+import androidx.leanback.widget.ArrayObjectAdapter
+
+@AndroidEntryPoint
+class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView {
+    //private TVContactFragment mContactFragment;
+    private var mSpinnerFragment: SpinnerFragment? = null
+    private var cardRowAdapter: ArrayObjectAdapter? = null
+    private var contactRequestRowAdapter: ArrayObjectAdapter? = null
+    private var mTitleView: CustomTitleView? = null
+    private var requestsRow: CardListRow? = null
+    private var selector: CardPresenterSelector? = null
+    private var qrCard: IconCard? = null
+    private var accountSettingsRow: ListRow? = null
+    private val mDisposable = CompositeDisposable()
+    private val mHomeChannelDisposable = CompositeDisposable()
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        headersState = HEADERS_DISABLED
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        mTitleView = view.findViewById(R.id.browse_title_group)
+        super.onViewCreated(view, savedInstanceState)
+        setupUIElements(requireActivity())
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        mDisposable.clear()
+    }
+
+    private fun setupUIElements(context: Context) {
+        selector = CardPresenterSelector(context)
+        cardRowAdapter = ArrayObjectAdapter(selector)
+
+        /* Contact Presenter */
+        val contactRow = CardRow(CardRow.TYPE_DEFAULT, false, getString(R.string.tv_contact_row_header), ArrayList())
+        val cardPresenterHeader = HeaderItem(HEADER_CONTACTS, getString(R.string.tv_contact_row_header))
+        val contactListRow = CardListRow(cardPresenterHeader, cardRowAdapter, contactRow)
+        accountSettingsRow = createAccountSettingsRow(context)
+        adapter = ArrayObjectAdapter(ShadowRowPresenterSelector()).apply {
+            add(contactListRow)
+            add(accountSettingsRow)
+        }
+
+        // listeners
+        setOnSearchClickedListener { startActivity(Intent(context, SearchActivity::class.java)) }
+        onItemViewClickedListener = ItemViewClickedListener()
+        mTitleView!!.settingsButton.setOnClickListener { presenter.onSettingsClicked() }
+    }
+
+    private fun createRow(titleSection: String, cards: List<Card>, shadow: Boolean): ListRow {
+        val row = CardRow(CardRow.TYPE_DEFAULT, shadow, titleSection, cards)
+        val listRowAdapter = ArrayObjectAdapter(selector)
+        for (card in cards) {
+            listRowAdapter.add(card)
+        }
+        return CardListRow(HeaderItem(HEADER_MISC, titleSection), listRowAdapter, row)
+    }
+
+    private fun createAccountSettingsRow(context: Context): ListRow {
+        val cards = ArrayList<Card>(3).apply {
+            add(IconCardHelper.getAccountManagementCard(context))
+            add(IconCardHelper.getAccountAddDeviceCard(context))
+            add(IconCardHelper.getAccountShareCard(context, null).apply { qrCard = this })
+        }
+        return createRow(getString(R.string.account_tv_settings_header), cards, false)
+    }
+
+    private fun createContactRequestRow(): CardListRow {
+        val contactRequestRow = CardRow(CardRow.TYPE_DEFAULT, false, getString(R.string.menu_item_contact_request), ArrayList<ContactCard>())
+        contactRequestRowAdapter = ArrayObjectAdapter(selector)
+        return CardListRow(HeaderItem(HEADER_MISC, getString(R.string.menu_item_contact_request)), contactRequestRowAdapter, contactRequestRow)
+    }
+
+    override fun showLoading(show: Boolean) {
+        if (show) {
+            mSpinnerFragment = SpinnerFragment()
+            parentFragmentManager.beginTransaction()
+                .replace(R.id.main_browse_fragment, mSpinnerFragment!!).commitAllowingStateLoss()
+        } else {
+            parentFragmentManager.beginTransaction().remove(mSpinnerFragment!!)
+                .commitAllowingStateLoss()
+        }
+    }
+
+    override fun refreshContact(index: Int, contact: SmartListViewModel) {
+        val contactCard = cardRowAdapter!![index] as ContactCard
+        contactCard.model = contact
+        cardRowAdapter!!.replace(index, contactCard)
+    }
+
+    override fun showContacts(contacts: List<SmartListViewModel>) {
+        val cards: MutableList<Card?> = ArrayList(contacts.size + 1)
+        cards.add(IconCardHelper.getAddContactCard(requireContext()))
+        for (contact in contacts) cards.add(ContactCard(contact))
+        cardRowAdapter!!.setItems(cards, null)
+        buildHomeChannel(requireContext().applicationContext, contacts)
+    }
+
+    private fun buildHomeChannel(context: Context, contacts: List<SmartListViewModel>) {
+        if (contacts.isEmpty()) return
+
+        // Get launcher package name
+        val resolveInfo = context.packageManager.resolveActivity(
+            Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+            PackageManager.MATCH_DEFAULT_ONLY
+        )
+        val launcherName = resolveInfo?.activityInfo?.packageName
+        val cr = context.contentResolver
+        mHomeChannelDisposable.clear()
+        mHomeChannelDisposable.add(Single.fromCallable { createHomeChannel(context) }
+            .doOnEvent { channelId: Long, error: Throwable? ->
+                if (error != null) {
+                    Log.w(TAG, "Error creating home channel", error)
+                } else {
+                    cr.delete(TvContractCompat.buildPreviewProgramsUriForChannel(channelId), null, null)
+                }
+            }
+            .flatMapObservable { channelId: Long ->
+                Observable.fromIterable(contacts)
+                    .concatMapEager({ contact: SmartListViewModel ->
+                        buildProgram(context, contact, launcherName, channelId)
+                            .toObservable()
+                            .subscribeOn(Schedulers.io())
+                    }, 8, 1)
+            }
+            .subscribeOn(Schedulers.io())
+            .subscribe({ program -> cr.insert(TvContractCompat.PreviewPrograms.CONTENT_URI, program.toContentValues()) }
+            ) { e: Throwable? -> Log.w(TAG, "Error updating home channel", e) })
+    }
+
+    override fun showContactRequests(contacts: List<SmartListViewModel>) {
+        val adapter = adapter as ArrayObjectAdapter
+        val row = adapter[TRUST_REQUEST_ROW_POSITION] as CardListRow?
+        val isRowDisplayed = row === requestsRow
+        val cards: MutableList<ContactCard> = ArrayList(contacts.size)
+        for (contact in contacts)
+            cards.add(ContactCard(contact))
+        if (isRowDisplayed && contacts.isEmpty()) {
+            adapter.removeItems(TRUST_REQUEST_ROW_POSITION, 1)
+        } else if (contacts.isNotEmpty()) {
+            if (requestsRow == null)
+                requestsRow = createContactRequestRow()
+            contactRequestRowAdapter!!.setItems(cards, null)
+            if (!isRowDisplayed)
+                adapter.add(TRUST_REQUEST_ROW_POSITION, requestsRow)
+        }
+    }
+
+    override fun callContact(accountID: String, number: String) {
+        val intent = Intent(Intent.ACTION_CALL, ConversationPath.toUri(accountID, number), activity, TVCallActivity::class.java)
+        startActivity(intent, null)
+    }
+
+    override fun displayAccountInfo(viewModel: HomeNavigationViewModel) {
+        updateModel(viewModel.account)
+    }
+
+    override fun updateModel(account: Account) {
+        val context = requireContext()
+        val address = account.displayUsername
+        mDisposable.clear()
+        mDisposable.add(loadProfile(context, account)
+                .observeOn(AndroidSchedulers.mainThread())
+                .doOnNext { profile ->
+                    val name = profile.first
+                    if (name != null && name.isNotEmpty()) {
+                        mTitleView?.setAlias(name)
+                        title = address ?: ""
+                    } else {
+                        mTitleView?.setAlias(address)
+                    }
+                }
+            .map { p -> AvatarDrawable.build(context, account, p, true) }
+            .subscribe { a ->
+                mTitleView?.apply {
+                    settingsButton.visibility = View.VISIBLE
+                    logoView.visibility = View.VISIBLE
+                    logoView.setImageDrawable(a)
+                }
+            })
+        qrCard!!.setDrawable(prepareAccountQr(context, account.uri))
+        accountSettingsRow!!.adapter.notifyItemRangeChanged(QR_ITEM_POSITION, 1)
+    }
+
+    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 showProfileEditing() {
+        GuidedStepSupportFragment.add(
+            parentFragmentManager,
+            TVProfileEditingFragment(),
+            R.id.main_browse_fragment
+        )
+    }
+
+    override fun showAccountShare() {
+        val intent = Intent(activity, TVShareActivity::class.java)
+        startActivity(intent)
+    }
+
+    override fun showSettings() {
+        startActivity(Intent(activity, TVSettingsActivity::class.java))
+    }
+
+    private inner class ItemViewClickedListener : OnItemViewClickedListener {
+        override fun onItemClicked(itemViewHolder: Presenter.ViewHolder, item: Any, rowViewHolder: RowPresenter.ViewHolder, row: Row) {
+            if (item is ContactCard) {
+                val model = item.model
+                val bundle = ConversationPath.toBundle(model.accountId, model.uri)
+                if (row === requestsRow) {
+                    bundle.putString("type", TVContactActivity.TYPE_CONTACT_REQUEST_INCOMING)
+                }
+                val mContactFragment = TVContactFragment()
+                mContactFragment.arguments = bundle
+                parentFragmentManager.beginTransaction()
+                    .hide(this@MainFragment)
+                    .add(R.id.fragment_container, mContactFragment, TVContactFragment.TAG)
+                    .addToBackStack(TVContactFragment.TAG)
+                    .commit()
+            } else if (item is IconCard) {
+                when (item.type) {
+                    Card.Type.ACCOUNT_ADD_DEVICE -> presenter!!.onExportClicked()
+                    Card.Type.ACCOUNT_EDIT_PROFILE -> presenter!!.onEditProfileClicked()
+                    Card.Type.ACCOUNT_SHARE_ACCOUNT -> {
+                        val view = (itemViewHolder.view as CardView).mainImageView
+                        val intent = Intent(activity, TVShareActivity::class.java)
+                        val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                            requireActivity(),
+                            view,
+                            TVShareActivity.SHARED_ELEMENT_NAME
+                        ).toBundle()
+                        requireActivity().startActivity(intent, bundle)
+                    }
+                    Card.Type.ADD_CONTACT -> startActivity(Intent(activity, SearchActivity::class.java))
+                    else -> {
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val TAG = MainFragment::class.java.simpleName
+
+        // Sections headers ids
+        private const val HEADER_CONTACTS: Long = 0
+        private const val HEADER_MISC: Long = 1
+        private const val TRUST_REQUEST_ROW_POSITION = 1
+        private const val QR_ITEM_POSITION = 2
+        private const val PREFERENCES_CHANNELS = "channels"
+        private const val KEY_CHANNEL_CONVERSATIONS = "conversations"
+        private val HOME_URI = Uri.Builder()
+            .scheme(ContentUriHandler.SCHEME_TV)
+            .authority(ContentUriHandler.AUTHORITY)
+            .appendPath(ContentUriHandler.PATH_TV_HOME)
+            .build()
+
+        private fun createHomeChannel(context: Context): Long {
+            val channel = Channel.Builder()
+                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
+                .setDisplayName(context.getString(R.string.navigation_item_conversation))
+                .setAppLinkIntentUri(HOME_URI)
+                .build()
+            val cr = context.contentResolver
+            val sharedPref = context.getSharedPreferences(PREFERENCES_CHANNELS, Context.MODE_PRIVATE)
+            var channelId = sharedPref.getLong(KEY_CHANNEL_CONVERSATIONS, -1)
+            if (channelId == -1L) {
+                val channelUri = cr.insert(TvContractCompat.Channels.CONTENT_URI, channel.toContentValues())
+                channelId = ContentUris.parseId(channelUri!!)
+                sharedPref.edit().putLong(KEY_CHANNEL_CONVERSATIONS, channelId).apply()
+                val targetSize = (AvatarFactory.SIZE_NOTIF * context.resources.displayMetrics.density).toInt()
+                val targetPaddingSize = (AvatarFactory.SIZE_PADDING * context.resources.displayMetrics.density).toInt()
+                ChannelLogoUtils.storeChannelLogo(
+                    context, channelId, drawableToBitmap(context.getDrawable(R.drawable.ic_jami_48)!!, targetSize, targetPaddingSize))
+                TvContractCompat.requestChannelBrowsable(context, channelId)
+            } else {
+                cr.update(TvContractCompat.buildChannelUri(channelId), channel.toContentValues(), null, null)
+            }
+            return channelId
+        }
+
+        private fun buildProgram(
+            context: Context,
+            vm: SmartListViewModel,
+            launcherName: String?,
+            channelId: Long
+        ): Single<PreviewProgram> {
+            return AvatarDrawable.Builder()
+                .withViewModel(vm)
+                .withPresence(false)
+                .buildAsync(context)
+                .map { avatar: AvatarDrawable? ->
+                    val file = createImageFile(context)
+                    val bitmapAvatar = drawableToBitmap(avatar!!, 256)
+                    BufferedOutputStream(FileOutputStream(file)).use { os ->
+                        bitmapAvatar.compress(Bitmap.CompressFormat.PNG, 100, os)
+                    }
+                    bitmapAvatar.recycle()
+                    val uri = FileProvider.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file)
+
+                    // Grant permission to launcher
+                    if (launcherName != null) context.grantUriPermission(
+                        launcherName,
+                        uri,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                    )
+                    val contactBuilder = PreviewProgram.Builder()
+                        .setChannelId(channelId)
+                        .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
+                        .setTitle(vm.contactName)
+                        .setAuthor(vm.contacts[0].ringUsername)
+                        .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_1_1)
+                        .setPosterArtUri(uri)
+                        .setIntentUri(
+                            Uri.Builder()
+                                .scheme(ContentUriHandler.SCHEME_TV)
+                                .authority(ContentUriHandler.AUTHORITY)
+                                .appendPath(ContentUriHandler.PATH_TV_CONVERSATION)
+                                .appendPath(vm.accountId)
+                                .appendPath(vm.uri.uri)
+                                .build()
+                        )
+                        .setInternalProviderId(vm.uuid)
+                    contactBuilder.build()
+                }
+        }
+
+        private fun prepareAccountQr(context: Context, accountId: String?): BitmapDrawable? {
+            Log.w(TAG, "prepareAccountQr $accountId")
+            if (accountId == null || accountId.isEmpty()) return null
+            val pad = 16
+            val qrCodeData = QRCodeUtils.encodeStringAsQRCodeData(accountId, 0X00000000, -0x1)
+            val bitmap = Bitmap.createBitmap(qrCodeData.width + 2 * pad, qrCodeData.height + 2 * pad, Bitmap.Config.ARGB_8888)
+            bitmap.setPixels(
+                qrCodeData.data,
+                0,
+                qrCodeData.width,
+                pad,
+                pad,
+                qrCodeData.width,
+                qrCodeData.height
+            )
+            return BitmapDrawable(context.resources, bitmap)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
index c3515f2a8..98951ac65 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
@@ -29,7 +29,7 @@ import java.util.concurrent.TimeUnit;
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import net.jami.facades.ConversationFacade;
+import net.jami.services.ConversationFacade;
 import net.jami.mvp.RootPresenter;
 import net.jami.navigation.HomeNavigationViewModel;
 import net.jami.services.AccountService;
@@ -59,7 +59,7 @@ public class MainPresenter extends RootPresenter<MainView> {
     public void bindView(MainView view) {
         super.bindView(view);
         loadConversations();
-        reloadAccountInfos();
+        reloadAccountInfo();
     }
 
     private void loadConversations() {
@@ -97,11 +97,11 @@ public class MainPresenter extends RootPresenter<MainView> {
                 }, e -> Log.w(TAG, "showConversations error ", e)));
     }
 
-    public void reloadAccountInfos() {
+    public void reloadAccountInfo() {
         mCompositeDisposable.add(mAccountService.getCurrentAccountSubject()
                 .observeOn(mUiScheduler)
                 .subscribe(
-                        account -> getView().displayAccountInfos(new HomeNavigationViewModel(account, null)),
+                        account -> getView().displayAccountInfo(new HomeNavigationViewModel(account, null)),
                         e-> Log.d(TAG, "reloadAccountInfos getProfileAccountList onError", e)));
         mCompositeDisposable.add(mAccountService.getObservableAccounts()
                 .observeOn(mUiScheduler)
@@ -116,10 +116,6 @@ public class MainPresenter extends RootPresenter<MainView> {
         getView().showExportDialog(mAccountService.getCurrentAccount().getAccountID(), mAccountService.getCurrentAccount().hasPassword());
     }
 
-    public void onLicenceClicked(int aboutType) {
-        getView().showLicence(aboutType);
-    }
-
     public void onEditProfileClicked() {
         getView().showProfileEditing();
     }
@@ -131,4 +127,5 @@ public class MainPresenter extends RootPresenter<MainView> {
     public void onSettingsClicked() {
         getView().showSettings();
     }
+
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
index 39f1cf2a2..be65e03e8 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
@@ -40,7 +40,8 @@ public interface MainView {
 
     void displayErrorToast(Error error);
 
-    void displayAccountInfos(HomeNavigationViewModel viewModel);
+    void displayAccountInfo(HomeNavigationViewModel viewModel);
+
     void updateModel(Account account);
 
     void showExportDialog(String pAccountID, boolean hasPassword);
@@ -49,7 +50,6 @@ public interface MainView {
 
     void showAccountShare();
 
-    void showLicence(int aboutType);
-
     void showSettings();
+
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.java b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.java
deleted file mode 100644
index 8e92f0a04..000000000
--- a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Michel Schmit <michel.schmit@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.tv.search;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.leanback.app.SearchSupportFragment;
-import androidx.leanback.widget.ArrayObjectAdapter;
-import androidx.leanback.widget.HeaderItem;
-import androidx.leanback.widget.ListRow;
-import androidx.leanback.widget.ListRowPresenter;
-import androidx.leanback.widget.ObjectAdapter;
-import androidx.leanback.widget.SearchBar;
-import androidx.leanback.widget.SearchEditText;
-import androidx.core.content.ContextCompat;
-import android.view.View;
-
-import cx.ring.R;
-import cx.ring.application.JamiApplication;
-import cx.ring.client.CallActivity;
-import net.jami.model.Contact;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.tv.call.TVCallActivity;
-import cx.ring.tv.cards.Card;
-import cx.ring.tv.cards.CardPresenterSelector;
-import cx.ring.tv.cards.contacts.ContactCard;
-import cx.ring.tv.contact.TVContactActivity;
-import cx.ring.utils.ConversationPath;
-
-public class ContactSearchFragment extends BaseSearchFragment<ContactSearchPresenter>
-        implements SearchSupportFragment.SearchResultProvider, ContactSearchView {
-    private SearchEditText mTextEditor;
-    private SearchBar mSearchBar;
-
-    private final ArrayObjectAdapter mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setSearchResultProvider(this);
-
-        // dependency injection
-        ((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this);
-        setOnItemViewClickedListener((itemViewHolder, item, rowViewHolder, row) -> presenter.contactClicked(((ContactCard) item).getModel()));
-        setBadgeDrawable(ContextCompat.getDrawable(getActivity(), R.mipmap.ic_launcher));
-        setSearchQuery("", false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mTextEditor = view.findViewById(R.id.lb_search_text_editor);
-        mSearchBar = view.findViewById(R.id.lb_search_bar);
-        // view injection
-        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
-            @Override
-            public void onSearchQueryChange(String query) {
-                onQueryTextChange(query);
-            }
-
-            @Override
-            public void onSearchQuerySubmit(String query) {
-                onQueryTextSubmit(query);
-            }
-
-            @Override
-            public void onKeyboardDismiss(String query) {
-                mSearchBar.postDelayed(()-> {
-                    getRowsSupportFragment().getVerticalGridView().requestFocus();
-                }, 200);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        if (mTextEditor != null) {
-            mTextEditor.requestFocus();
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-    }
-
-    @Override
-    public ObjectAdapter getResultsAdapter() {
-        return mRowsAdapter;
-    }
-
-    @Override
-    public boolean onQueryTextChange(String newQuery) {
-        presenter.queryTextChanged(newQuery);
-        return true;
-    }
-
-    @Override
-    public boolean onQueryTextSubmit(String query) {
-        presenter.queryTextChanged(query);
-        return true;
-    }
-
-    @Override
-    public void displayContact(String accountId, final Contact contact) {
-        mRowsAdapter.clear();
-        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenterSelector(getActivity()));
-        listRowAdapter.add(new ContactCard(accountId, contact, Card.Type.SEARCH_RESULT));
-        HeaderItem header = new HeaderItem(getActivity().getResources().getString(R.string.search_results));
-        mRowsAdapter.add(new ListRow(header, listRowAdapter));
-    }
-
-    @Override
-    public void clearSearch() {
-        mRowsAdapter.clear();
-    }
-
-    @Override
-    public void startCall(String accountID, String number) {
-        Intent intent = new Intent(CallActivity.ACTION_CALL, ConversationPath.toUri(accountID, number), getActivity(), TVCallActivity.class);
-        intent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
-        startActivity(intent);
-        getActivity().finish();
-    }
-
-    @Override
-    public void displayContactDetails(SmartListViewModel model) {
-        Intent intent = new Intent(getActivity(), TVContactActivity.class);
-        //intent.putExtra(TVContactActivity.CONTACT_REQUEST_URI, model.getContact().getPrimaryUri());
-        intent.setDataAndType(ConversationPath.toUri(model.getAccountId(), model.getUri()), TVContactActivity.TYPE_CONTACT_REQUEST_OUTGOING);
-        startActivity(intent);
-        getActivity().finish();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt
new file mode 100644
index 000000000..fe5843efc
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt
@@ -0,0 +1,125 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Michel Schmit <michel.schmit@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.tv.search
+
+import cx.ring.utils.ConversationPath.Companion.toUri
+import dagger.hilt.android.AndroidEntryPoint
+import cx.ring.tv.search.BaseSearchFragment
+import cx.ring.tv.search.ContactSearchPresenter
+import androidx.leanback.app.SearchSupportFragment
+import cx.ring.tv.search.ContactSearchView
+import android.os.Bundle
+import cx.ring.tv.cards.contacts.ContactCard
+import androidx.core.content.ContextCompat
+import cx.ring.R
+import androidx.leanback.widget.SearchBar.SearchBarListener
+import net.jami.model.Contact
+import cx.ring.tv.cards.CardPresenterSelector
+import android.content.Intent
+import android.view.View
+import androidx.leanback.widget.*
+import cx.ring.client.CallActivity
+import cx.ring.utils.ConversationPath
+import cx.ring.tv.call.TVCallActivity
+import cx.ring.tv.cards.Card
+import net.jami.smartlist.SmartListViewModel
+import cx.ring.tv.contact.TVContactActivity
+
+@AndroidEntryPoint
+class ContactSearchFragment : BaseSearchFragment<ContactSearchPresenter>(),
+    SearchSupportFragment.SearchResultProvider, ContactSearchView {
+
+    private var mTextEditor: SearchEditText? = null
+    private val mRowsAdapter = ArrayObjectAdapter(ListRowPresenter())
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setSearchResultProvider(this)
+        setOnItemViewClickedListener { _, item: Any, _, _ ->
+            presenter.contactClicked((item as ContactCard).model)
+        }
+        badgeDrawable = ContextCompat.getDrawable(requireContext(), R.mipmap.ic_launcher)
+        setSearchQuery("", false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        mTextEditor = view.findViewById(R.id.lb_search_text_editor)
+        val mSearchBar: SearchBar = view.findViewById(R.id.lb_search_bar)
+        mSearchBar.setSearchBarListener(object : SearchBarListener {
+            override fun onSearchQueryChange(query: String) {
+                onQueryTextChange(query)
+            }
+
+            override fun onSearchQuerySubmit(query: String) {
+                onQueryTextSubmit(query)
+            }
+
+            override fun onKeyboardDismiss(query: String) {
+                mSearchBar.postDelayed({ rowsSupportFragment.verticalGridView.requestFocus() }, 200)
+            }
+        })
+    }
+
+    override fun onResume() {
+        super.onResume()
+        mTextEditor?.requestFocus()
+    }
+
+    override fun getResultsAdapter(): ObjectAdapter {
+        return mRowsAdapter
+    }
+
+    override fun onQueryTextChange(newQuery: String): Boolean {
+        presenter.queryTextChanged(newQuery)
+        return true
+    }
+
+    override fun onQueryTextSubmit(query: String): Boolean {
+        presenter.queryTextChanged(query)
+        return true
+    }
+
+    override fun displayContact(accountId: String, contact: Contact) {
+        mRowsAdapter.clear()
+        val listRowAdapter = ArrayObjectAdapter(CardPresenterSelector(activity))
+        listRowAdapter.add(ContactCard(accountId, contact, Card.Type.SEARCH_RESULT))
+        val header = HeaderItem(getString(R.string.search_results))
+        mRowsAdapter.add(ListRow(header, listRowAdapter))
+    }
+
+    override fun clearSearch() {
+        mRowsAdapter.clear()
+    }
+
+    override fun startCall(accountID: String, number: String) {
+        val intent = Intent(CallActivity.ACTION_CALL, toUri(accountID, number), activity, TVCallActivity::class.java)
+        intent.putExtra(Intent.EXTRA_PHONE_NUMBER, number)
+        startActivity(intent)
+        activity?.finish()
+    }
+
+    override fun displayContactDetails(model: SmartListViewModel) {
+        val intent = Intent(activity, TVContactActivity::class.java)
+        //intent.putExtra(TVContactActivity.CONTACT_REQUEST_URI, model.getContact().getPrimaryUri());
+        intent.setDataAndType(toUri(model.accountId, model.uri), TVContactActivity.TYPE_CONTACT_REQUEST_OUTGOING)
+        startActivity(intent)
+        activity?.finish()
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java
index ba0994e23..3e875738d 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java
@@ -40,8 +40,6 @@ import io.reactivex.rxjava3.subjects.PublishSubject;
 public class ContactSearchPresenter extends RootPresenter<ContactSearchView> {
 
     private final AccountService mAccountService;
-    private final HardwareService mHardwareService;
-    private final VCardService mVCardService;
 
     private Contact mContact;
     @Inject
@@ -51,12 +49,8 @@ public class ContactSearchPresenter extends RootPresenter<ContactSearchView> {
     private final PublishSubject<String> contactQuery = PublishSubject.create();
 
     @Inject
-    public ContactSearchPresenter(AccountService accountService,
-                                  HardwareService hardwareService,
-                                  VCardService vCardService) {
+    public ContactSearchPresenter(AccountService accountService) {
         mAccountService = accountService;
-        mHardwareService = hardwareService;
-        mVCardService = vCardService;
     }
 
     @Override
@@ -66,7 +60,7 @@ public class ContactSearchPresenter extends RootPresenter<ContactSearchView> {
                 .debounce(350, TimeUnit.MILLISECONDS)
                 .switchMapSingle(q -> mAccountService.findRegistrationByName(mAccountService.getCurrentAccount().getAccountID(), "", q))
                 .observeOn(mUiScheduler)
-                .subscribe(q -> parseEventState(mAccountService.getAccount(q.accountId), q.name, q.address, q.state)));
+                .subscribe(q -> parseEventState(mAccountService.getAccount(q.getAccountId()), q.getName(), q.getAddress(), q.getState())));
     }
 
     @Override
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/SearchActivity.java b/ring-android/app/src/main/java/cx/ring/tv/search/SearchActivity.java
index df4282fb1..8624d5dea 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/SearchActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/SearchActivity.java
@@ -24,7 +24,9 @@ import android.os.Bundle;
 import androidx.fragment.app.FragmentActivity;
 
 import cx.ring.R;
+import dagger.hilt.android.AndroidEntryPoint;
 
+@AndroidEntryPoint
 public class SearchActivity extends FragmentActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
diff --git a/ring-android/app/src/main/java/cx/ring/tv/settings/TVAboutFragment.java b/ring-android/app/src/main/java/cx/ring/tv/settings/TVAboutFragment.java
new file mode 100644
index 000000000..1e9958f1e
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/settings/TVAboutFragment.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2004-2020 Savoir-faire Linux Inc.
+ *
+ * Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.settings;
+
+import android.os.Bundle;
+import android.text.Html;
+import android.text.SpannableString;
+import android.text.style.UnderlineSpan;
+
+import androidx.leanback.preference.LeanbackPreferenceFragmentCompat;
+import androidx.preference.Preference;
+
+import net.jami.model.ConfigKey;
+
+import cx.ring.BuildConfig;
+import cx.ring.R;
+
+public class TVAboutFragment extends LeanbackPreferenceFragmentCompat {
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        setPreferencesFromResource(R.xml.tv_about_pref, rootKey);
+        Preference version = findPreference("About.version");
+        Preference license = findPreference("About.license");
+        Preference rights = findPreference("About.rights");
+        Preference credits = findPreference("About.credits");
+        version.setTitle(getVersion());
+        license.setTitle(getLicense());
+        rights.setTitle(getRights());
+        credits.setTitle(getCredits());
+    }
+
+    public static TVAboutFragment newInstance() {
+        return new TVAboutFragment();
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(Preference preference) {
+        if (preference.getKey().equals(ConfigKey.ACCOUNT_AUTOANSWER.key())) {
+
+        } else if (preference.getKey().equals(ConfigKey.ACCOUNT_ISRENDEZVOUS.key())) {
+
+        }
+        return super.onPreferenceTreeClick(preference);
+    }
+
+    private CharSequence getVersion() {
+        SpannableString version = new SpannableString(requireContext().getResources().getString(R.string.version_section));
+        version.setSpan(new UnderlineSpan(), 0, version.length(), 0);
+        return requireContext().getResources().getString(R.string.app_release, BuildConfig.VERSION_NAME);
+    }
+
+    private CharSequence getLicense() {
+        SpannableString licence = new SpannableString(requireContext().getResources().getString(R.string.section_license));
+        licence.setSpan(new UnderlineSpan(), 0, licence.length(), 0);
+        return requireContext().getResources().getString(R.string.license);
+    }
+
+    private CharSequence getRights() {
+        SpannableString licence = new SpannableString(requireContext().getResources().getString(R.string.copyright_section));
+        licence.setSpan(new UnderlineSpan(), 0, licence.length(), 0);
+        return requireContext().getResources().getString(R.string.copyright);
+    }
+
+    private CharSequence getCredits() {
+        SpannableString developedby = new SpannableString(requireContext().getResources().getString(R.string.developed_by));
+        developedby.setSpan(new UnderlineSpan(), 0, developedby.length(), 0);
+        CharSequence developed = requireContext().getResources().getString(R.string.credits_developer).replaceAll("\n", "<br/>");
+        return Html.fromHtml("<b><u>" + developedby + "</u></b><br/>" + developed);
+    }
+
+}
diff --git a/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsActivity.kt b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsActivity.kt
new file mode 100644
index 000000000..9830d0e31
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsActivity.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.tv.settings
+
+import android.os.Bundle
+import androidx.fragment.app.FragmentActivity
+import cx.ring.R
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class TVSettingsActivity : FragmentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.tv_activity_settings)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java
similarity index 87%
rename from ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java
rename to ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java
index 022fc9ca6..365daf3f5 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/account/TVSettingsFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package cx.ring.tv.account;
+package cx.ring.tv.settings;
 
 import android.content.Context;
 
@@ -29,6 +29,7 @@ import androidx.fragment.app.Fragment;
 import androidx.leanback.preference.LeanbackSettingsFragmentCompat;
 import androidx.preference.ListPreference;
 import androidx.preference.Preference;
+import androidx.preference.PreferenceDialogFragmentCompat;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceScreen;
@@ -43,9 +44,13 @@ import cx.ring.fragments.GeneralAccountPresenter;
 import cx.ring.fragments.GeneralAccountView;
 import net.jami.model.Account;
 import net.jami.model.ConfigKey;
-import cx.ring.services.SharedPreferencesServiceImpl;
 import net.jami.utils.Tuple;
+import dagger.hilt.android.AndroidEntryPoint;
+
+import cx.ring.services.SharedPreferencesServiceImpl;
+import cx.ring.tv.account.JamiPreferenceFragment;
 
+@AndroidEntryPoint
 public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
 
     @Override
@@ -55,7 +60,18 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
 
     @Override
     public boolean onPreferenceStartFragment(PreferenceFragmentCompat preferenceFragment, Preference preference) {
-        return false;
+        final Bundle args = preference.getExtras();
+        final Fragment f = getChildFragmentManager().getFragmentFactory().instantiate(
+                requireActivity().getClassLoader(), preference.getFragment());
+        f.setArguments(args);
+        f.setTargetFragment(preferenceFragment, 0);
+        if (f instanceof PreferenceFragmentCompat
+                || f instanceof PreferenceDialogFragmentCompat) {
+            startPreferenceFragment(f);
+        } else {
+            startImmersiveFragment(f);
+        }
+        return true;
     }
 
     @Override
@@ -68,6 +84,7 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
         return true;
     }
 
+    @AndroidEntryPoint
     public static class PrefsFragment extends JamiPreferenceFragment<GeneralAccountPresenter> implements GeneralAccountView {
         private boolean autoAnswer;
         private boolean rendezvousMode;
@@ -78,7 +95,6 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
 
         @Override
         public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-            ((JamiApplication) requireActivity().getApplication()).getInjectionComponent().inject(this);
             super.onViewCreated(view, savedInstanceState);
             presenter.init();
         }
@@ -150,7 +166,8 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
 
         @Override
         public boolean onPreferenceTreeClick(Preference preference) {
-            if (preference.getKey().equals(ConfigKey.ACCOUNT_AUTOANSWER.key())) {
+            if (preference.getKey().equals("Account.about")) {
+            } else if (preference.getKey().equals(ConfigKey.ACCOUNT_AUTOANSWER.key())) {
                 presenter.twoStatePreferenceChanged(ConfigKey.ACCOUNT_AUTOANSWER, !autoAnswer);
                 autoAnswer = !autoAnswer;
             } else if (preference.getKey().equals(ConfigKey.ACCOUNT_ISRENDEZVOUS.key())) {
@@ -159,5 +176,6 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat {
             }
             return super.onPreferenceTreeClick(preference);
         }
+
     }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java b/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
index 61bdffd3c..a4ddddffd 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/views/CustomTitleView.java
@@ -18,8 +18,10 @@ import android.graphics.drawable.Drawable;
 import androidx.leanback.widget.TitleViewAdapter;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
@@ -37,6 +39,7 @@ public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.
     private final TextView mTitleView;
     private final ImageView mLogoView;
     private final View mSearchOrbView;
+    private final ImageButton mSettingsButton;
 
     private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
         @Override
@@ -82,8 +85,20 @@ public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.
         mAliasView = root.findViewById(R.id.account_alias);
         mTitleView = root.findViewById(R.id.title_text);
         mLogoView = root.findViewById(R.id.title_photo_contact);
+        mSettingsButton = root.findViewById(R.id.title_settings);
         mSearchOrbView = root.findViewById(R.id.title_orb);
 
+        mSearchOrbView.setOnKeyListener(new OnKeyListener() {
+            @Override
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+                    mSettingsButton.requestFocus();
+                    return true;
+                }
+                return false;
+            }
+        });
+
         setClipChildren(false);
         setClipToPadding(false);
     }
@@ -105,12 +120,17 @@ public class CustomTitleView extends RelativeLayout implements TitleViewAdapter.
         mTitleView.setText(title);
         mTitleView.setVisibility(View.VISIBLE);
         mLogoView.setVisibility(View.VISIBLE);
+        mSettingsButton.setVisibility(View.VISIBLE);
     }
 
     public ImageView getLogoView() {
         return mLogoView;
     }
 
+    public ImageButton getSettingsButton() {
+        return mSettingsButton;
+    }
+
     @Override
     public TitleViewAdapter getTitleViewAdapter() {
         return mTitleViewAdapter;
diff --git a/ring-android/app/src/main/java/cx/ring/tv/views/NonOverlappingFrameLayout.java b/ring-android/app/src/main/java/cx/ring/tv/views/NonOverlappingFrameLayout.java
new file mode 100644
index 000000000..3c5cd3bf5
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/tv/views/NonOverlappingFrameLayout.java
@@ -0,0 +1,23 @@
+package cx.ring.tv.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+public class NonOverlappingFrameLayout extends FrameLayout {
+    public NonOverlappingFrameLayout(Context context) {
+        this(context, null);
+    }
+    public NonOverlappingFrameLayout(Context context, AttributeSet attrs) {
+        super(context, attrs, 0);
+    }
+    public NonOverlappingFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+    /**
+     * Avoid creating hardware layer when Transition is animating alpha.
+     */
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java
deleted file mode 100644
index 8e0299a44..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.utils;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-import android.media.ExifInterface;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.StatFs;
-import android.provider.DocumentsContract;
-import android.provider.MediaStore;
-import android.provider.OpenableColumns;
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-import androidx.annotation.NonNull;
-
-import net.jami.model.Conversation;
-import net.jami.utils.FileUtils;
-
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public class AndroidFileUtils {
-
-    private static final String TAG = AndroidFileUtils.class.getSimpleName();
-    private final static int ORIENTATION_LEFT = 270;
-    private final static int ORIENTATION_RIGHT = 90;
-    private final static int MAX_IMAGE_DIMENSION = 1024;
-
-    /**
-     * Copy assets from a folder recursively ( files and subfolder)
-     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
-     * @param fromAssetPath path to the assets folder we want to copy
-     * @param toPath a directory in internal storage
-     * @return true if success
-     */
-    public static boolean copyAssetFolder( @NonNull AssetManager assetManager, String fromAssetPath, File toPath) {
-        try {
-            boolean res = true;
-
-            // mkdirs checks if the folder exists and if not creates it
-            toPath.mkdirs();
-
-            // List the files of this asset directory
-            String[] files = assetManager.list(fromAssetPath);
-
-            if (files != null) {
-                for (String file : files) {
-                    String subAsset = fromAssetPath + File.separator + file;
-                    if (isAssetDirectory(assetManager, subAsset)) {
-                        String destination = toPath.getAbsolutePath() + File.separator + file;
-                        File newDir = new File(destination);
-                        copyAssetFolder(assetManager, subAsset, newDir);
-                        Log.d(TAG, "Copied folder: " + subAsset + " to " + newDir);
-                    } else {
-                        File newFile = new File(toPath, file);
-                        res &= copyAsset(assetManager, fromAssetPath + File.separator + file, newFile);
-                        Log.d(TAG, "Copied file: " + subAsset + " to " + newFile);
-                    }
-                }
-            }
-
-            return res;
-        } catch (IOException e) {
-            Log.e(TAG, "Error while copying asset folder", e);
-            return false;
-        }
-    }
-
-    /**
-     * Checks whether an asset is a file or a directory
-     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
-     * @param fromAssetPath  asset path, if just a file in  assets root folder then it should be
-     *                       the file name, otherwise, folder/filename
-     * @return boolean directory or not
-     */
-    public static boolean isAssetDirectory( @NonNull AssetManager assetManager, String fromAssetPath) {
-        try {
-            String[] files = assetManager.list(fromAssetPath);
-            if (files != null && files.length > 0) {
-                return true;
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Error while reading an asset ", e);
-        }
-        return false;
-    }
-
-    /**
-     * Prints assets tree
-     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
-     * @param rootPath default empty, sub folder otherwise
-     * @param fileName the name of the file or folder
-     * @param level default 0, should be 0
-     */
-    public static void assetTree( @NonNull AssetManager assetManager, String rootPath, String fileName,  int level) {
-        try {
-            String fromAssetPath;
-            if(TextUtils.isEmpty(rootPath)) {
-                fromAssetPath = fileName;
-            } else {
-                fromAssetPath = rootPath + File.separator+ fileName;
-            }
-
-            String repeated = new String(new char[level]).replace("\0", "\t|");
-
-            String[] files = assetManager.list(fromAssetPath);
-
-            if (files != null) {
-                Log.d(TAG, "|"+ repeated + "-- " + fileName);
-                for(String file : files) {
-                    assetTree(assetManager,fromAssetPath,file,level+1);
-                }
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Error while reading asset ", e);
-        }
-    }
-
-    public static boolean copyAsset(AssetManager assetManager, String fromAssetPath, File toPath) {
-        try (InputStream in = assetManager.open(fromAssetPath);
-             OutputStream out = new FileOutputStream(toPath)) {
-            net.jami.utils.FileUtils.copyFile(in, out);
-            out.flush();
-            return true;
-        } catch (IOException e) {
-            Log.e(TAG, "Error while copying asset", e);
-            return false;
-        }
-    }
-
-    public static String getRealPathFromURI(Context context, Uri uri) {
-        String path = null;
-        if (DocumentsContract.isDocumentUri(context, uri)) {
-            if (isExternalStorageDocument(uri)) {
-                final String docId = DocumentsContract.getDocumentId(uri);
-                final String[] split = docId.split(":");
-                final String type = split[0];
-                if ("primary".equalsIgnoreCase(type)) {
-                    path = Environment.getExternalStorageDirectory() + "/" + split[1];
-                }
-            } else if (isDownloadsDocument(uri)) {
-                final String id = DocumentsContract.getDocumentId(uri);
-                final Uri contentUri = ContentUris.withAppendedId(
-                        Uri.parse("content://downloads/public_downloads"), Long.parseLong(id));
-                path = getDataColumn(context, contentUri, null, null);
-            } else if (isMediaDocument(uri)) {
-                final String docId = DocumentsContract.getDocumentId(uri);
-                final String[] split = docId.split(":");
-                final String type = split[0];
-                Uri contentUri = null;
-                if ("image".equals(type)) {
-                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-                } else if ("video".equals(type)) {
-                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
-                } else if ("audio".equals(type)) {
-                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-                }
-                final String selection = "_id=?";
-                final String[] selectionArgs = new String[]{split[1]};
-                path = getDataColumn(context, contentUri, selection, selectionArgs);
-            }
-        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
-            path = getDataColumn(context, uri, null, null);
-        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
-            path = uri.getPath();
-        }
-        return path;
-    }
-
-    private static String getFilename(ContentResolver cr, Uri uri) {
-        String result = null;
-        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
-            try (Cursor cursor = cr.query(uri, null, null, null, null)) {
-                if (cursor != null && cursor.moveToFirst()) {
-                    result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
-                }
-            }
-        }
-        if (result == null) {
-            result = uri.getPath();
-            int cut = result.lastIndexOf('/');
-            if (cut != -1) {
-                result = result.substring(cut + 1);
-            }
-        }
-        if (result.lastIndexOf('.') == -1) {
-            String mimeType = getMimeType(cr, uri);
-            String extensionFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
-            if (extensionFromMimeType != null) {
-                result += '.' + extensionFromMimeType;
-            }
-        }
-        return result;
-    }
-
-    private static String getMimeType(ContentResolver cr, Uri uri) {
-        String mimeType;
-        if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
-            mimeType = cr.getType(uri);
-        } else {
-            mimeType = getMimeType(uri.toString());
-        }
-        return mimeType;
-    }
-
-    public static String getMimeType(String filename) {
-        int pos = filename.lastIndexOf(".");
-        String fileExtension = null;
-        if (pos >= 0) {
-            fileExtension = MimeTypeMap.getFileExtensionFromUrl(filename.substring(pos));
-        }
-        return getMimeTypeFromExtension(fileExtension);
-    }
-
-    public static String getMimeTypeFromExtension(String ext) {
-        if (!TextUtils.isEmpty(ext)) {
-            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase());
-            if (!TextUtils.isEmpty(mimeType))
-                return mimeType;
-            if (ext.contentEquals("gz")) {
-                return "application/gzip";
-            }
-        }
-        return "application/octet-stream";
-    }
-
-    public static File getTempShareDir(@NonNull Context context) {
-        File tmp = new File(context.getCacheDir(), "tmp");
-        tmp.mkdir();
-        return tmp;
-    }
-
-    public static File createImageFile(@NonNull Context context) throws IOException {
-        // Create an image file name
-        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
-        String imageFileName = "img_" + timeStamp + "_";
-
-        // Save a file: path for use with ACTION_VIEW intents
-        return File.createTempFile(imageFileName, ".jpg", getTempShareDir(context));
-    }
-
-    public static File createAudioFile(@NonNull Context context) throws IOException {
-        // Create an image file name
-        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
-        String imageFileName = "audio_" + timeStamp + "_";
-
-        // Save a file: path for use with ACTION_VIEW intents
-        return File.createTempFile(imageFileName, ".mp3", getTempShareDir(context));
-    }
-    public static File createVideoFile(@NonNull Context context) throws IOException {
-        // Create an image file name
-        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
-        String imageFileName = "video_" + timeStamp + "_";
-
-        // Save a file: path for use with ACTION_VIEW intents
-        return File.createTempFile(imageFileName, ".webm", getTempShareDir(context));
-    }
-    public static File createLogFile(@NonNull Context context) throws IOException {
-        // Create an image file name
-        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
-        String imageFileName = "log_" + timeStamp + "_";
-
-        // Save a file: path for use with ACTION_VIEW intents
-        return File.createTempFile(imageFileName, ".log", getTempShareDir(context));
-    }
-
-    /**
-     * Copies a file from a uri whether locally on a remote location to the local cache
-     * @param context Context to get access to cache directory
-     * @param uri uri of the
-     * @return Single<File> which points to the newly created copy in the cache
-     */
-    public static @NonNull
-    Single<File> getCacheFile(@NonNull Context context, @NonNull Uri uri) {
-        ContentResolver contentResolver = context.getContentResolver();
-        File cacheDir = context.getCacheDir();
-        return Single.fromCallable(() -> {
-            File file = new File(cacheDir, getFilename(contentResolver, uri));
-            try  (InputStream inputStream = contentResolver.openInputStream(uri);
-                  FileOutputStream output = new FileOutputStream(file)) {
-                if (inputStream == null)
-                    throw new FileNotFoundException();
-                net.jami.utils.FileUtils.copyFile(inputStream, output);
-                output.flush();
-            }
-            return file;
-        }).subscribeOn(Schedulers.io());
-    }
-
-    public static @NonNull Single<File> getFileToSend(@NonNull Context context, @NonNull Conversation conversation, @NonNull Uri uri) {
-        ContentResolver contentResolver = context.getContentResolver();
-        File cacheDir = context.getCacheDir();
-        return Single.fromCallable(() -> {
-            File file = new File(cacheDir, getFilename(contentResolver, uri));
-            try (InputStream inputStream = contentResolver.openInputStream(uri);
-                 FileOutputStream output = new FileOutputStream(file)) {
-                if (inputStream == null)
-                    throw new FileNotFoundException();
-                net.jami.utils.FileUtils.copyFile(inputStream, output);
-                output.flush();
-            }
-            return file;
-        }).subscribeOn(Schedulers.io());
-    }
-
-    public static Completable moveToUri(@NonNull ContentResolver cr, @NonNull File input, @NonNull Uri outUri) {
-        return Completable.fromAction(() -> {
-            try (InputStream inputStream = new FileInputStream(input);
-                 OutputStream output = cr.openOutputStream(outUri)) {
-                if (output == null)
-                    throw new FileNotFoundException();
-                FileUtils.copyFile(inputStream, output);
-            }
-            input.delete();
-        }).subscribeOn(Schedulers.io());
-    }
-
-    /**
-     * Copies a file to a predefined Uri destination
-     * Uses the underlying copyFile(InputStream,OutputStream)
-     * @param cr content resolver
-     * @param input the file we want to copy
-     * @param outUri the uri destination
-     * @return success value
-     */
-    public static Completable copyFileToUri(ContentResolver cr, File input, Uri outUri){
-        return Completable.fromAction(() -> {
-            try (InputStream inputStream = new FileInputStream(input); OutputStream outputStream = cr.openOutputStream(outUri)) {
-                net.jami.utils.FileUtils.copyFile(inputStream, outputStream);
-            }
-        }).subscribeOn(Schedulers.io());
-    }
-
-    public static File getConversationFile(Context context, Uri uri, String conversationId, String name) throws IOException {
-        File file = getConversationPath(context, conversationId, name);
-        FileOutputStream output = new FileOutputStream(file);
-        InputStream inputStream = context.getContentResolver().openInputStream(uri);
-        net.jami.utils.FileUtils.copyFile(inputStream, output);
-        return file;
-    }
-
-    public static File getCachePath(Context context, String filename) {
-        return new File(context.getCacheDir(), filename);
-    }
-
-    public static File getFilePath(Context context, String filename) {
-        return context.getFileStreamPath(filename);
-    }
-
-    public static File getConversationDir(Context context, String conversationId) {
-        File conversationsDir = getFilePath(context, "conversation_data");
-        if (!conversationsDir.exists())
-            conversationsDir.mkdir();
-
-        File conversationDir = new File(conversationsDir, conversationId);
-        if (!conversationDir.exists())
-            conversationDir.mkdir();
-
-        return conversationDir;
-    }
-
-    public static File getConversationDir(Context context, String accountId, String conversationId) {
-        File conversationsDir = getFilePath(context, "conversation_data");
-        if (!conversationsDir.exists())
-            conversationsDir.mkdir();
-
-        File accountDir = new File(conversationsDir, accountId);
-        if (!accountDir.exists())
-            accountDir.mkdir();
-
-        File conversationDir = new File(accountDir, conversationId);
-        if (!conversationDir.exists())
-            conversationDir.mkdir();
-
-        return conversationDir;
-    }
-
-    public static File getConversationPath(Context context, String conversationId, String name) {
-        return new File(getConversationDir(context, conversationId), name);
-    }
-    public static File getConversationPath(Context context, String accountId, String conversationId, String name) {
-        return new File(getConversationDir(context, accountId, conversationId), name);
-    }
-
-    public static File getTempPath(Context context, String conversationId, String name) {
-        File conversationsDir = getCachePath(context, "conversation_data");
-
-        if (!conversationsDir.exists())
-            conversationsDir.mkdir();
-
-        File conversationDir = new File(conversationsDir, conversationId);
-        if (!conversationDir.exists())
-            conversationDir.mkdir();
-
-        return new File(conversationDir, name);
-    }
-
-    public static String writeCacheFileToExtStorage(Context context, Uri cacheFile, String targetFilename) throws IOException {
-        File downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
-
-        int fileCount = 0;
-        File finalFile = new File(downloadsDirectory, targetFilename);
-        int lastDotIndex = targetFilename.lastIndexOf('.');
-        String filename = targetFilename.substring(0, lastDotIndex);
-        String extension = targetFilename.substring(lastDotIndex + 1);
-        while (finalFile.exists()) {
-            finalFile = new File(downloadsDirectory, filename + "_" + fileCount + '.' + extension);
-            fileCount++;
-        }
-
-        Log.d(TAG, "writeCacheFileToExtStorage: finalFile=" + finalFile + ",exists=" + finalFile.exists());
-        try (InputStream inputStream = context.getContentResolver().openInputStream(cacheFile);
-             FileOutputStream output = new FileOutputStream(finalFile)) {
-            net.jami.utils.FileUtils.copyFile(inputStream, output);
-        }
-        return finalFile.toString();
-    }
-
-    public static boolean isExternalStorageWritable() {
-        String state = Environment.getExternalStorageState();
-        return Environment.MEDIA_MOUNTED.equals(state);
-    }
-
-    private static boolean isExternalStorageDocument(Uri uri) {
-        return "com.android.externalstorage.documents".equals(uri.getAuthority());
-    }
-
-    private static boolean isDownloadsDocument(Uri uri) {
-        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
-    }
-
-    private static boolean isMediaDocument(Uri uri) {
-        return "com.android.providers.media.documents".equals(uri.getAuthority());
-    }
-
-    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
-        String path = null;
-        final String column = "_data";
-        final String[] projection = {column};
-        try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {
-            if (cursor != null && cursor.moveToFirst()) {
-                final int column_index = cursor.getColumnIndexOrThrow(column);
-                path = cursor.getString(column_index);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error while saving the ringtone", e);
-        }
-        return path;
-    }
-
-    public static File ringtonesPath(Context context) {
-        return new File(context.getFilesDir(), "ringtones");
-    }
-
-    /**
-     * Get space left in a specific path
-     *
-     * @return -1L if an error occurred, size otherwise
-     */
-    public static long getSpaceLeft(String path) {
-        try {
-            StatFs statfs = new StatFs(path);
-            return statfs.getAvailableBytes();
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "getSpaceLeft: not able to access path on " + path);
-            return -1L;
-        }
-    }
-
-    public static Single<Bitmap> loadBitmap(Context context, Uri uriImage) {
-        return Single.fromCallable(() -> {
-            BitmapFactory.Options dbo = new BitmapFactory.Options();
-            dbo.inJustDecodeBounds = true;
-
-            try  (InputStream is = context.getContentResolver().openInputStream(uriImage)) {
-                BitmapFactory.decodeStream(is, null, dbo);
-            }
-
-            int rotatedWidth, rotatedHeight;
-            int orientation = getOrientation(context, uriImage);
-
-            if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {
-                rotatedWidth = dbo.outHeight;
-                rotatedHeight = dbo.outWidth;
-            } else {
-                rotatedWidth = dbo.outWidth;
-                rotatedHeight = dbo.outHeight;
-            }
-
-            Bitmap srcBitmap;
-            try (InputStream is = context.getContentResolver().openInputStream(uriImage)) {
-                if (rotatedWidth > MAX_IMAGE_DIMENSION || rotatedHeight > MAX_IMAGE_DIMENSION) {
-                    float widthRatio = ((float) rotatedWidth) / ((float) MAX_IMAGE_DIMENSION);
-                    float heightRatio = ((float) rotatedHeight) / ((float) MAX_IMAGE_DIMENSION);
-                    float maxRatio = Math.max(widthRatio, heightRatio);
-
-                    // Create the bitmap from file
-                    BitmapFactory.Options options = new BitmapFactory.Options();
-                    options.inSampleSize = (int) maxRatio;
-                    srcBitmap = BitmapFactory.decodeStream(is, null, options);
-                } else {
-                    srcBitmap = BitmapFactory.decodeStream(is);
-                }
-            }
-
-            if (orientation > 0) {
-                Matrix matrix = new Matrix();
-                matrix.postRotate(orientation);
-
-                srcBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(),
-                        srcBitmap.getHeight(), matrix, true);
-            }
-            return srcBitmap;
-        }).subscribeOn(Schedulers.io());
-    }
-
-    private static int getOrientation(@NonNull Context context, @NonNull Uri photoUri) {
-        ContentResolver resolver = context.getContentResolver();
-        if (resolver == null)
-            return 0;
-        try (Cursor cursor = resolver.query(photoUri, new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null)) {
-            cursor.moveToFirst();
-            return cursor.getInt(0);
-        } catch (Exception e) {
-            switch (getExifOrientation(resolver, photoUri)) {
-                case ExifInterface.ORIENTATION_ROTATE_90:
-                    return 90;
-                case ExifInterface.ORIENTATION_ROTATE_180:
-                    return 180;
-                case ExifInterface.ORIENTATION_ROTATE_270:
-                    return 270;
-                default:
-                    return 0;
-            }
-        }
-    }
-
-    private static int getExifOrientation(@NonNull ContentResolver resolver, @NonNull Uri photoUri) {
-        if (Build.VERSION.SDK_INT > 23) {
-            try (InputStream input = resolver.openInputStream(photoUri)) {
-                return new ExifInterface(input)
-                        .getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
-            } catch (Exception e) {
-                return 0;
-            }
-        } else {
-            try {
-                return new ExifInterface(photoUri.getPath())
-                        .getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
-            } catch (Exception e) {
-                return 0;
-            }
-        }
-    }
-
-    public static boolean isImage(String s) {
-        return getMimeType(s).startsWith("image");
-    }
-
-    public static String getFileName(String s) {
-        String[] parts = s.split("\\/");
-        return parts[parts.length - 1];
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt
new file mode 100644
index 000000000..94adcdb0e
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt
@@ -0,0 +1,569 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.utils
+
+import android.content.ContentResolver
+import android.content.ContentUris
+import android.content.Context
+import android.content.res.AssetManager
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Matrix
+import android.media.ExifInterface
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.os.StatFs
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.provider.OpenableColumns
+import android.text.TextUtils
+import android.util.Log
+import android.webkit.MimeTypeMap
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import net.jami.model.Conversation
+import net.jami.utils.FileUtils
+import java.io.*
+import java.text.SimpleDateFormat
+import java.util.*
+
+object AndroidFileUtils {
+    private val TAG = AndroidFileUtils::class.simpleName
+    private const val ORIENTATION_LEFT = 270
+    private const val ORIENTATION_RIGHT = 90
+    private const val MAX_IMAGE_DIMENSION = 1024
+
+    /**
+     * Copy assets from a folder recursively ( files and subfolder)
+     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
+     * @param fromAssetPath path to the assets folder we want to copy
+     * @param toPath a directory in internal storage
+     * @return true if success
+     */
+    fun copyAssetFolder(assetManager: AssetManager, fromAssetPath: String, toPath: File): Boolean {
+        return try {
+            var res = true
+
+            // mkdirs checks if the folder exists and if not creates it
+            toPath.mkdirs()
+
+            // List the files of this asset directory
+            val files = assetManager.list(fromAssetPath)
+            if (files != null) {
+                for (file in files) {
+                    val subAsset = fromAssetPath + File.separator + file
+                    if (isAssetDirectory(assetManager, subAsset)) {
+                        val destination = toPath.absolutePath + File.separator + file
+                        val newDir = File(destination)
+                        copyAssetFolder(assetManager, subAsset, newDir)
+                        Log.d(TAG, "Copied folder: $subAsset to $newDir")
+                    } else {
+                        val newFile = File(toPath, file)
+                        res = res and copyAsset(assetManager, fromAssetPath + File.separator + file, newFile)
+                        Log.d(TAG, "Copied file: $subAsset to $newFile")
+                    }
+                }
+            }
+            res
+        } catch (e: IOException) {
+            Log.e(TAG, "Error while copying asset folder", e)
+            false
+        }
+    }
+
+    /**
+     * Checks whether an asset is a file or a directory
+     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
+     * @param fromAssetPath  asset path, if just a file in  assets root folder then it should be
+     * the file name, otherwise, folder/filename
+     * @return boolean directory or not
+     */
+    private fun isAssetDirectory(assetManager: AssetManager, fromAssetPath: String): Boolean {
+        try {
+            if (assetManager.list(fromAssetPath)?.isNotEmpty() == true) {
+                return true
+            }
+        } catch (e: IOException) {
+            Log.e(TAG, "Error while reading an asset ", e)
+        }
+        return false
+    }
+
+    /**
+     * Prints assets tree
+     * @param assetManager Asset Manager ( you can get it from Context.getAssets() )
+     * @param rootPath default empty, sub folder otherwise
+     * @param fileName the name of the file or folder
+     * @param level default 0, should be 0
+     */
+    fun assetTree(assetManager: AssetManager, rootPath: String, fileName: String, level: Int) {
+        try {
+            val fromAssetPath: String = if (TextUtils.isEmpty(rootPath)) {
+                fileName
+            } else {
+                rootPath + File.separator + fileName
+            }
+            val repeated = String(CharArray(level)).replace("\u0000", "\t|")
+            val files = assetManager.list(fromAssetPath)
+            if (files != null) {
+                Log.d(TAG, "|$repeated-- $fileName")
+                for (file in files) {
+                    assetTree(assetManager, fromAssetPath, file, level + 1)
+                }
+            }
+        } catch (e: IOException) {
+            Log.e(TAG, "Error while reading asset ", e)
+        }
+    }
+
+    fun copyAsset(assetManager: AssetManager, fromAssetPath: String, toPath: File): Boolean {
+        try {
+            assetManager.open(fromAssetPath).use { input ->
+                FileOutputStream(toPath).use { out ->
+                    FileUtils.copyFile(input, out)
+                    out.flush()
+                    return true
+                }
+            }
+        } catch (e: IOException) {
+            Log.e(TAG, "Error while copying asset", e)
+            return false
+        }
+    }
+
+    @JvmStatic
+    fun getRealPathFromURI(context: Context, uri: Uri): String? {
+        var path: String? = null
+        if (DocumentsContract.isDocumentUri(context, uri)) {
+            if (isExternalStorageDocument(uri)) {
+                val docId = DocumentsContract.getDocumentId(uri)
+                val split = docId.split(":".toRegex()).toTypedArray()
+                val type = split[0]
+                if ("primary".equals(type, ignoreCase = true)) {
+                    path = Environment.getExternalStorageDirectory().toString() + "/" + split[1]
+                }
+            } else if (isDownloadsDocument(uri)) {
+                val id = DocumentsContract.getDocumentId(uri)
+                val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), id.toLong())
+                path = getDataColumn(context, contentUri, null, null)
+            } else if (isMediaDocument(uri)) {
+                val docId = DocumentsContract.getDocumentId(uri)
+                val split = docId.split(":".toRegex()).toTypedArray()
+                val type = split[0]
+                var contentUri: Uri? = null
+                when (type) {
+                    "image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+                    "video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+                    "audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+                }
+                val selection = "_id=?"
+                val selectionArgs = arrayOf(split[1])
+                path = getDataColumn(context, contentUri, selection, selectionArgs)
+            }
+        } else if ("content".equals(uri.scheme, ignoreCase = true)) {
+            path = getDataColumn(context, uri, null, null)
+        } else if ("file".equals(uri.scheme, ignoreCase = true)) {
+            path = uri.path
+        }
+        return path
+    }
+
+    private fun getFilename(cr: ContentResolver, uri: Uri): String {
+        var result: String? = null
+        if (ContentResolver.SCHEME_CONTENT == uri.scheme) {
+            cr.query(uri, null, null, null, null).use { cursor ->
+                if (cursor != null && cursor.moveToFirst()) {
+                    result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+                }
+            }
+        }
+        if (result == null) {
+            result = uri.path
+            val cut = result!!.lastIndexOf('/')
+            if (cut != -1) {
+                result = result!!.substring(cut + 1)
+            }
+        }
+        if (result!!.lastIndexOf('.') == -1) {
+            val mimeType = getMimeType(cr, uri)
+            val extensionFromMimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
+            if (extensionFromMimeType != null) {
+                result += ".$extensionFromMimeType"
+            }
+        }
+        return result ?: uri.lastPathSegment!!
+    }
+
+    private fun getMimeType(cr: ContentResolver, uri: Uri): String? {
+        return if (ContentResolver.SCHEME_CONTENT == uri.scheme)
+            cr.getType(uri)
+        else
+            getMimeType(uri.toString())
+    }
+
+    fun getMimeType(filename: String): String? {
+        val pos = filename.lastIndexOf(".")
+        var fileExtension: String? = null
+        if (pos >= 0) {
+            fileExtension = MimeTypeMap.getFileExtensionFromUrl(filename.substring(pos))
+        }
+        return getMimeTypeFromExtension(fileExtension)
+    }
+
+    fun getMimeTypeFromExtension(ext: String?): String {
+        if (ext != null && ext.isNotEmpty()) {
+            val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.lowercase(Locale.getDefault()))
+            if (mimeType != null && mimeType.isNotEmpty()) return mimeType
+            if (ext == "gz") {
+                return "application/gzip"
+            }
+        }
+        return "application/octet-stream"
+    }
+
+    fun getTempShareDir(context: Context): File {
+        val tmp = File(context.cacheDir, "tmp")
+        tmp.mkdir()
+        return tmp
+    }
+
+    @Throws(IOException::class)
+    fun createImageFile(context: Context): File {
+        // Create an image file name
+        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
+        val imageFileName = "img_" + timeStamp + "_"
+
+        // Save a file: path for use with ACTION_VIEW intents
+        return File.createTempFile(imageFileName, ".jpg", getTempShareDir(context))
+    }
+
+    @Throws(IOException::class)
+    fun createAudioFile(context: Context): File {
+        // Create an image file name
+        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
+        val imageFileName = "audio_" + timeStamp + "_"
+
+        // Save a file: path for use with ACTION_VIEW intents
+        return File.createTempFile(imageFileName, ".mp3", getTempShareDir(context))
+    }
+
+    @Throws(IOException::class)
+    fun createVideoFile(context: Context): File {
+        // Create an image file name
+        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
+        val imageFileName = "video_" + timeStamp + "_"
+
+        // Save a file: path for use with ACTION_VIEW intents
+        return File.createTempFile(imageFileName, ".webm", getTempShareDir(context))
+    }
+
+    @Throws(IOException::class)
+    fun createLogFile(context: Context): File {
+        // Create an image file name
+        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
+        val imageFileName = "log_" + timeStamp + "_"
+
+        // Save a file: path for use with ACTION_VIEW intents
+        return File.createTempFile(imageFileName, ".log", getTempShareDir(context))
+    }
+
+    /**
+     * Copies a file from a uri whether locally on a remote location to the local cache
+     * @param context Context to get access to cache directory
+     * @param uri uri of the
+     * @return Single<File> which points to the newly created copy in the cache
+    </File> */
+    @JvmStatic
+    fun getCacheFile(context: Context, uri: Uri): Single<File> {
+        val contentResolver = context.contentResolver
+        val cacheDir = context.cacheDir
+        return Single.fromCallable {
+            val file = File(cacheDir, getFilename(contentResolver, uri))
+            contentResolver.openInputStream(uri).use { inputStream ->
+                FileOutputStream(file).use { output ->
+                    if (inputStream == null) throw FileNotFoundException()
+                    FileUtils.copyFile(inputStream, output)
+                    output.flush()
+                }
+            }
+            file
+        }.subscribeOn(Schedulers.io())
+    }
+
+    fun getFileToSend(context: Context, conversation: Conversation, uri: Uri): Single<File> {
+        val contentResolver = context.contentResolver
+        val cacheDir = context.cacheDir
+        return Single.fromCallable {
+            val file = File(cacheDir, getFilename(contentResolver, uri))
+            contentResolver.openInputStream(uri).use { inputStream ->
+                FileOutputStream(file).use { output ->
+                    if (inputStream == null) throw FileNotFoundException()
+                    FileUtils.copyFile(inputStream, output)
+                    output.flush()
+                }
+            }
+            file
+        }.subscribeOn(Schedulers.io())
+    }
+
+    fun moveToUri(cr: ContentResolver, input: File, outUri: Uri): Completable {
+        return Completable.fromAction {
+            FileInputStream(input).use { inputStream ->
+                cr.openOutputStream(outUri).use { output ->
+                    if (output == null) throw FileNotFoundException()
+                    FileUtils.copyFile(inputStream, output)
+                }
+            }
+            input.delete()
+        }.subscribeOn(Schedulers.io())
+    }
+
+    /**
+     * Copies a file to a predefined Uri destination
+     * Uses the underlying copyFile(InputStream,OutputStream)
+     * @param cr content resolver
+     * @param input the file we want to copy
+     * @param outUri the uri destination
+     * @return success value
+     */
+    fun copyFileToUri(cr: ContentResolver, input: File?, outUri: Uri): Completable {
+        return Completable.fromAction {
+            FileInputStream(input).use { inputStream ->
+                cr.openOutputStream(outUri)?.use { outputStream ->
+                    FileUtils.copyFile(inputStream, outputStream)
+                } }
+        }.subscribeOn(Schedulers.io())
+    }
+
+    @Throws(IOException::class)
+    fun getConversationFile(context: Context, uri: Uri, conversationId: String, name: String): File {
+        val file = getConversationPath(context, conversationId, name)
+        context.contentResolver.openInputStream(uri)?.use { inputStream ->
+            FileOutputStream(file).use { output ->
+                FileUtils.copyFile(inputStream, output)
+            } }
+        return file
+    }
+
+    fun getCachePath(context: Context, filename: String): File {
+        return File(context.cacheDir, filename)
+    }
+
+    fun getFilePath(context: Context, filename: String?): File {
+        return context.getFileStreamPath(filename)
+    }
+
+    fun getConversationDir(context: Context, conversationId: String): File {
+        val conversationsDir = getFilePath(context, "conversation_data")
+        if (!conversationsDir.exists()) conversationsDir.mkdir()
+        val conversationDir = File(conversationsDir, conversationId)
+        if (!conversationDir.exists()) conversationDir.mkdir()
+        return conversationDir
+    }
+
+    fun getConversationDir(context: Context, accountId: String, conversationId: String): File {
+        val conversationsDir = getFilePath(context, "conversation_data")
+        if (!conversationsDir.exists()) conversationsDir.mkdir()
+        val accountDir = File(conversationsDir, accountId)
+        if (!accountDir.exists()) accountDir.mkdir()
+        val conversationDir = File(accountDir, conversationId)
+        if (!conversationDir.exists()) conversationDir.mkdir()
+        return conversationDir
+    }
+
+    fun getConversationPath(context: Context, conversationId: String, name: String): File {
+        return File(getConversationDir(context, conversationId), name)
+    }
+
+    fun getConversationPath(context: Context, accountId: String, conversationId: String, name: String): File {
+        return File(getConversationDir(context, accountId, conversationId), name)
+    }
+
+    fun getTempPath(context: Context, conversationId: String, name: String): File {
+        val conversationsDir = getCachePath(context, "conversation_data")
+        if (!conversationsDir.exists()) conversationsDir.mkdir()
+        val conversationDir = File(conversationsDir, conversationId)
+        if (!conversationDir.exists()) conversationDir.mkdir()
+        return File(conversationDir, name)
+    }
+
+    @Throws(IOException::class)
+    fun writeCacheFileToExtStorage(context: Context, cacheFile: Uri, targetFilename: String): String {
+        val downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+        var fileCount = 0
+        var finalFile = File(downloadsDirectory, targetFilename)
+        val lastDotIndex = targetFilename.lastIndexOf('.')
+        val filename = targetFilename.substring(0, lastDotIndex)
+        val extension = targetFilename.substring(lastDotIndex + 1)
+        while (finalFile.exists()) {
+            finalFile = File(downloadsDirectory, filename + "_" + fileCount + '.' + extension)
+            fileCount++
+        }
+        Log.d(TAG, "writeCacheFileToExtStorage: finalFile=" + finalFile + ",exists=" + finalFile.exists())
+        context.contentResolver.openInputStream(cacheFile)?.use { inputStream ->
+            FileOutputStream(finalFile).use { output ->
+                FileUtils.copyFile(inputStream, output)
+            } }
+        return finalFile.toString()
+    }
+
+    val isExternalStorageWritable: Boolean
+        get() {
+            val state = Environment.getExternalStorageState()
+            return Environment.MEDIA_MOUNTED == state
+        }
+
+    private fun isExternalStorageDocument(uri: Uri): Boolean {
+        return "com.android.externalstorage.documents" == uri.authority
+    }
+
+    private fun isDownloadsDocument(uri: Uri): Boolean {
+        return "com.android.providers.downloads.documents" == uri.authority
+    }
+
+    private fun isMediaDocument(uri: Uri): Boolean {
+        return "com.android.providers.media.documents" == uri.authority
+    }
+
+    private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
+        var path: String? = null
+        val column = "_data"
+        val projection = arrayOf(column)
+        try {
+            context.contentResolver.query(uri!!, projection, selection, selectionArgs, null).use { cursor ->
+                if (cursor != null && cursor.moveToFirst()) {
+                    path = cursor.getString(cursor.getColumnIndexOrThrow(column))
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Error while saving the ringtone", e)
+        }
+        return path
+    }
+
+    fun ringtonesPath(context: Context): File {
+        return File(context.filesDir, "ringtones")
+    }
+
+    /**
+     * Get space left in a specific path
+     *
+     * @return -1L if an error occurred, size otherwise
+     */
+    @JvmStatic
+    fun getSpaceLeft(path: String): Long {
+        return try {
+            StatFs(path).availableBytes
+        } catch (e: IllegalArgumentException) {
+            Log.e(TAG, "getSpaceLeft: not able to access path on $path")
+            -1L
+        }
+    }
+
+    fun loadBitmap(context: Context, uriImage: Uri): Single<Bitmap> {
+        return Single.fromCallable<Bitmap> {
+            val dbo = BitmapFactory.Options()
+            dbo.inJustDecodeBounds = true
+            context.contentResolver.openInputStream(uriImage).use { `is` -> BitmapFactory.decodeStream(`is`, null, dbo) }
+            val rotatedWidth: Int
+            val rotatedHeight: Int
+            val orientation = getOrientation(context, uriImage)
+            if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {
+                rotatedWidth = dbo.outHeight
+                rotatedHeight = dbo.outWidth
+            } else {
+                rotatedWidth = dbo.outWidth
+                rotatedHeight = dbo.outHeight
+            }
+            var srcBitmap: Bitmap?
+            context.contentResolver.openInputStream(uriImage).use { `is` ->
+                if (rotatedWidth > MAX_IMAGE_DIMENSION || rotatedHeight > MAX_IMAGE_DIMENSION) {
+                    val widthRatio = rotatedWidth.toFloat() / MAX_IMAGE_DIMENSION.toFloat()
+                    val heightRatio = rotatedHeight.toFloat() / MAX_IMAGE_DIMENSION.toFloat()
+                    val maxRatio = Math.max(widthRatio, heightRatio)
+
+                    // Create the bitmap from file
+                    val options = BitmapFactory.Options()
+                    options.inSampleSize = maxRatio.toInt()
+                    srcBitmap = BitmapFactory.decodeStream(`is`, null, options)
+                } else {
+                    srcBitmap = BitmapFactory.decodeStream(`is`)
+                }
+            }
+            if (orientation > 0) {
+                val matrix = Matrix()
+                matrix.postRotate(orientation.toFloat())
+                srcBitmap = Bitmap.createBitmap(srcBitmap!!, 0, 0, srcBitmap!!.width,
+                        srcBitmap!!.height, matrix, true)
+            }
+            srcBitmap
+        }.subscribeOn(Schedulers.io())
+    }
+
+    private fun getOrientation(context: Context, photoUri: Uri): Int {
+        val resolver = context.contentResolver ?: return 0
+        try {
+            resolver.query(photoUri, arrayOf(MediaStore.Images.ImageColumns.ORIENTATION), null, null, null).use { cursor ->
+                cursor!!.moveToFirst()
+                return cursor.getInt(0)
+            }
+        } catch (e: Exception) {
+            return when (getExifOrientation(resolver, photoUri)) {
+                ExifInterface.ORIENTATION_ROTATE_90 -> 90
+                ExifInterface.ORIENTATION_ROTATE_180 -> 180
+                ExifInterface.ORIENTATION_ROTATE_270 -> 270
+                else -> 0
+            }
+        }
+    }
+
+    private fun getExifOrientation(resolver: ContentResolver, photoUri: Uri): Int {
+        if (Build.VERSION.SDK_INT > 23) {
+            try {
+                resolver.openInputStream(photoUri).use { input ->
+                    return ExifInterface(input!!)
+                            .getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
+                }
+            } catch (e: Exception) {
+                return 0
+            }
+        } else {
+            return try {
+                ExifInterface(photoUri.path!!)
+                        .getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
+            } catch (e: Exception) {
+                0
+            }
+        }
+    }
+
+    @JvmStatic
+    fun isImage(s: String): Boolean {
+        return getMimeType(s)?.startsWith("image") ?: false
+    }
+
+    @JvmStatic
+    fun getFileName(s: String): String {
+        val parts = s.split(Regex("/")).toTypedArray()
+        return parts[parts.size - 1]
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java b/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
deleted file mode 100644
index 23c7e8618..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.utils;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.Base64;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import ezvcard.parameter.ImageType;
-import ezvcard.property.Photo;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Helper calls to manipulates Bitmaps
- */
-public final class BitmapUtils
-{
-    private static final String TAG = BitmapUtils.class.getSimpleName();
-    private BitmapUtils() {}
-
-    public static Photo bitmapToPhoto(@NonNull Bitmap image) {
-        return new Photo(bitmapToPng(image), ImageType.PNG);
-    }
-
-    public static byte[] bitmapToPng(@NonNull Bitmap image) {
-        ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        image.compress(Bitmap.CompressFormat.PNG, 100, stream);
-        return stream.toByteArray();
-    }
-
-    public static byte[] bitmapToBytes(Bitmap bmp) {
-        int bytes = bmp.getByteCount();
-        ByteBuffer buffer = ByteBuffer.allocate(bytes); //Create a new buffer
-        bmp.copyPixelsToBuffer(buffer); //Move the byte data to the buffer
-        return buffer.array();
-    }
-
-    public static Bitmap base64ToBitmap(String base64) {
-        if (base64 == null)
-            return null;
-        try {
-            return bytesToBitmap(Base64.decode(base64, Base64.DEFAULT));
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-    }
-
-    public static Bitmap bytesToBitmap(byte[] imageData) {
-        if (imageData != null && imageData.length > 0) {
-            return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
-        }
-        return null;
-    }
-
-    public static Bitmap bytesToBitmap(byte[] data, int maxSize) {
-        // First decode with inJustDecodeBounds=true to check dimensions
-        final BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inJustDecodeBounds = true;
-        BitmapFactory.decodeByteArray(data, 0, data.length, options);
-        int width = options.outWidth;
-        int height = options.outHeight;
-        int scale = 1;
-        while (3 * width * height > maxSize) {
-            scale *= 2;
-            width /= 2;
-            height /= 2;
-        }
-        options.inSampleSize = scale;
-        options.inJustDecodeBounds = false;
-        return BitmapFactory.decodeByteArray(data, 0, data.length, options);
-    }
-
-    public static Bitmap reduceBitmap(Bitmap bmp, int size) {
-        if (bmp.getByteCount() <= size)
-            return bmp;
-        Log.d(TAG, "reduceBitmap: bitmap size before reduce " + bmp.getByteCount());
-        int height = bmp.getHeight();
-        int width = bmp.getWidth();
-        int minRatio = bmp.getByteCount()/size;
-
-        int ratio = 2;
-        while (ratio*ratio < minRatio)
-            ratio *= 2;
-
-        height /= ratio;
-        width /= ratio;
-        bmp = Bitmap.createScaledBitmap(bmp, width, height, true);
-
-        net.jami.utils.Log.d(TAG, "reduceBitmap: bitmap size after x" + ratio + " reduce " + bmp.getByteCount());
-        return bmp;
-    }
-
-    public static Bitmap createScaledBitmap(Bitmap bitmap, int maxSize) {
-        if (bitmap == null || maxSize < 0) {
-            throw new IllegalArgumentException();
-        }
-        int width = bitmap.getHeight();
-        int height = bitmap.getWidth();
-        if (width != height) {
-            if (width < height) {
-                // portrait
-                height = maxSize;
-                width = (maxSize * bitmap.getWidth()) / bitmap.getHeight();
-            } else {
-                // landscape
-                height = (maxSize * bitmap.getHeight()) / bitmap.getWidth();
-                width = maxSize;
-            }
-        } else {
-            width = maxSize;
-            height = maxSize;
-        }
-        return Bitmap.createScaledBitmap(bitmap, width, height, true);
-    }
-
-    public static Bitmap drawableToBitmap(Drawable drawable) {
-        return drawableToBitmap(drawable, -1);
-    }
-    public static Bitmap drawableToBitmap(Drawable drawable, int size) {
-        return drawableToBitmap(drawable, size, 0);
-    }
-    public static Bitmap drawableToBitmap(Drawable drawable, int size, int padding) {
-        if (drawable instanceof BitmapDrawable) {
-            return ((BitmapDrawable)drawable).getBitmap();
-        }
-
-        int width = drawable.getIntrinsicWidth();
-        width = width > 0 ? width : size;
-        int height = drawable.getIntrinsicHeight();
-        height = height > 0 ? height : size;
-
-        Bitmap bitmap = Bitmap.createBitmap(width + 2*padding, height + 2*padding, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(padding, padding, canvas.getWidth()-padding, canvas.getHeight()-padding);
-        drawable.draw(canvas);
-        return bitmap;
-    }
-
-    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
-        // Raw height and width of image
-        final int height = options.outHeight;
-        final int width = options.outWidth;
-        int inSampleSize = 1;
-
-        if (height > reqHeight || width > reqWidth) {
-
-            final int halfHeight = height / 2;
-            final int halfWidth = width / 2;
-
-            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
-            // height and width larger than the requested height and width.
-            while ((halfHeight / inSampleSize) >= reqHeight
-                    && (halfWidth / inSampleSize) >= reqWidth) {
-                inSampleSize *= 2;
-            }
-        }
-
-        return inSampleSize;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt b/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
new file mode 100644
index 000000000..06e8141de
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/BitmapUtils.kt
@@ -0,0 +1,162 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils
+
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.util.Base64
+import android.util.Log
+import ezvcard.parameter.ImageType
+import ezvcard.property.Photo
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+
+/**
+ * Helper calls to manipulates Bitmaps
+ */
+object BitmapUtils {
+    private val TAG = BitmapUtils::class.simpleName!!
+    @JvmStatic
+    fun bitmapToPhoto(image: Bitmap): Photo {
+        return Photo(bitmapToPng(image), ImageType.PNG)
+    }
+
+    fun bitmapToPng(image: Bitmap): ByteArray {
+        val stream = ByteArrayOutputStream()
+        image.compress(Bitmap.CompressFormat.PNG, 100, stream)
+        return stream.toByteArray()
+    }
+
+    fun bitmapToBytes(bmp: Bitmap): ByteArray {
+        val bytes = bmp.byteCount
+        val buffer = ByteBuffer.allocate(bytes) //Create a new buffer
+        bmp.copyPixelsToBuffer(buffer) //Move the byte data to the buffer
+        return buffer.array()
+    }
+
+    fun base64ToBitmap(base64: String?): Bitmap? {
+        return if (base64 == null) null else try {
+            bytesToBitmap(Base64.decode(base64, Base64.DEFAULT))
+        } catch (e: IllegalArgumentException) {
+            null
+        }
+    }
+
+    fun bytesToBitmap(imageData: ByteArray?): Bitmap? {
+        return if (imageData != null && imageData.isNotEmpty()) {
+            BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
+        } else null
+    }
+
+    fun bytesToBitmap(data: ByteArray, maxSize: Int): Bitmap {
+        // First decode with inJustDecodeBounds=true to check dimensions
+        val options = BitmapFactory.Options()
+        options.inJustDecodeBounds = true
+        BitmapFactory.decodeByteArray(data, 0, data.size, options)
+        var width = options.outWidth
+        var height = options.outHeight
+        var scale = 1
+        while (3 * width * height > maxSize) {
+            scale *= 2
+            width /= 2
+            height /= 2
+        }
+        options.inSampleSize = scale
+        options.inJustDecodeBounds = false
+        return BitmapFactory.decodeByteArray(data, 0, data.size, options)
+    }
+
+    fun reduceBitmap(bmp: Bitmap, size: Int): Bitmap {
+        if (bmp.byteCount <= size) return bmp
+        Log.d(TAG, "reduceBitmap: bitmap size before reduce " + bmp.byteCount)
+        var height = bmp.height
+        var width = bmp.width
+        val minRatio = bmp.byteCount / size
+        var ratio = 2
+        while (ratio * ratio < minRatio) ratio *= 2
+        height /= ratio
+        width /= ratio
+        val ret = Bitmap.createScaledBitmap(bmp, width, height, true)
+        Log.d(TAG, "reduceBitmap: bitmap size after x" + ratio + " reduce " + ret.byteCount)
+        return ret
+    }
+
+    fun createScaledBitmap(bitmap: Bitmap?, maxSize: Int): Bitmap {
+        require(!(bitmap == null || maxSize < 0))
+        var width = bitmap.height
+        var height = bitmap.width
+        if (width != height) {
+            if (width < height) {
+                // portrait
+                height = maxSize
+                width = maxSize * bitmap.width / bitmap.height
+            } else {
+                // landscape
+                height = maxSize * bitmap.height / bitmap.width
+                width = maxSize
+            }
+        } else {
+            width = maxSize
+            height = maxSize
+        }
+        return Bitmap.createScaledBitmap(bitmap, width, height, true)
+    }
+
+    @JvmStatic
+    @JvmOverloads
+    fun drawableToBitmap(drawable: Drawable, size: Int = -1, padding: Int = 0): Bitmap {
+        if (drawable is BitmapDrawable) {
+            return drawable.bitmap
+        }
+        var width = drawable.intrinsicWidth
+        width = if (width > 0) width else size
+        var height = drawable.intrinsicHeight
+        height = if (height > 0) height else size
+        val bitmap =
+            Bitmap.createBitmap(width + 2 * padding, height + 2 * padding, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bitmap)
+        drawable.setBounds(padding, padding, canvas.width - padding, canvas.height - padding)
+        drawable.draw(canvas)
+        return bitmap
+    }
+
+    fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
+        // Raw height and width of image
+        val height = options.outHeight
+        val width = options.outWidth
+        var inSampleSize = 1
+        if (height > reqHeight || width > reqWidth) {
+            val halfHeight = height / 2
+            val halfWidth = width / 2
+
+            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+            // height and width larger than the requested height and width.
+            while (halfHeight / inSampleSize >= reqHeight
+                && halfWidth / inSampleSize >= reqWidth
+            ) {
+                inSampleSize *= 2
+            }
+        }
+        return inSampleSize
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.java b/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.java
deleted file mode 100644
index 73e8b90a4..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.utils;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import cx.ring.BuildConfig;
-import cx.ring.R;
-
-public class ClipboardHelper {
-    public static final String TAG = ClipboardHelper.class.getSimpleName();
-
-    public static void copyToClipboard(final @NonNull Context context,
-                                       final String text) {
-        if (TextUtils.isEmpty(text)) {
-            Log.d(TAG, "copyNumberToClipboard: number is null");
-            return;
-        }
-
-        ClipboardManager clipboard = (ClipboardManager) context
-                .getSystemService(Context.CLIPBOARD_SERVICE);
-        ClipData clip = android.content.ClipData.newPlainText(context.getText(R.string.clip_contact_uri), text);
-        clipboard.setPrimaryClip(clip);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.kt b/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.kt
new file mode 100644
index 000000000..fe99f5375
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/ClipboardHelper.kt
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils
+
+import android.text.TextUtils
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import cx.ring.R
+
+object ClipboardHelper {
+    val TAG = ClipboardHelper::class.simpleName!!
+    fun copyToClipboard(
+        context: Context,
+        text: String?
+    ) {
+        if (TextUtils.isEmpty(text)) {
+            return
+        }
+        val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+        val clip = ClipData.newPlainText(context.getText(R.string.clip_contact_uri), text)
+        clipboard.setPrimaryClip(clip)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java
deleted file mode 100644
index 27f733902..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.utils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.FileProvider;
-
-import net.jami.utils.FileUtils;
-
-import java.io.File;
-
-import cx.ring.BuildConfig;
-
-/**
- * This class distributes content uri used to pass along data in the app
- */
-public class ContentUriHandler {
-    private final static String TAG = ContentUriHandler.class.getSimpleName();
-
-    public static final String AUTHORITY = BuildConfig.APPLICATION_ID;
-    public static final String AUTHORITY_FILES = AUTHORITY + ".file_provider";
-    public static final String SCHEME_TV = "jamitv";
-    public static final String PATH_TV_HOME = "home";
-    public static final String PATH_TV_CONVERSATION = "conversation";
-
-    private static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
-
-    public static final Uri CONVERSATION_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "conversation");
-    public static final Uri ACCOUNTS_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "accounts");
-    public static final Uri CONTACT_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "contact");
-
-    private ContentUriHandler() {
-        // hidden constructor
-    }
-
-    /**
-     * The following is a workaround used to mitigate getUriForFile exceptions
-     * on Huawei devices taken from stackoverflow
-     * https://stackoverflow.com/a/41309223
-     */
-    private static final String HUAWEI_MANUFACTURER = "Huawei";
-
-    public static Uri getUriForResource(@NonNull Context context, int resourceId) {
-        Resources resources = context.getResources();
-        return new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                .authority(resources.getResourcePackageName(resourceId))
-                .appendPath(resources.getResourceTypeName(resourceId))
-                .appendPath(resources.getResourceEntryName(resourceId))
-                .build();
-    }
-
-    public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
-        return getUriForFile(context, authority, file, null);
-    }
-    public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file, @Nullable String displayName) {
-        try {
-            return displayName == null ? FileProvider.getUriForFile(context, authority, file)
-                    : FileProvider.getUriForFile(context, authority, file, displayName);
-        } catch (IllegalArgumentException e) {
-            if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) {
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
-                    Log.w(TAG, "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e);
-                    return Uri.fromFile(file);
-                } else {
-                    Log.w(TAG, "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e);
-                    // Note: Periodically clear this cache
-                    final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER);
-                    final File cacheLocation = new File(cacheFolder, file.getName());
-                    if (FileUtils.copyFile(file, cacheLocation)) {
-                        Log.i(TAG, "Completed Android N+ Huawei file copy. Attempting to return the cached file");
-                        return displayName == null ? FileProvider.getUriForFile(context, authority, cacheLocation)
-                                : FileProvider.getUriForFile(context, authority, cacheLocation, displayName);
-                    }
-                    Log.e(TAG, "Failed to copy the Huawei file. Re-throwing exception");
-                    throw new IllegalArgumentException("Huawei devices are unsupported for Android N");
-                }
-            } else {
-                throw e;
-            }
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt
new file mode 100644
index 000000000..3e6d99dbd
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt
@@ -0,0 +1,108 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils
+
+import android.content.ContentResolver
+import android.content.Context
+import android.net.Uri
+import androidx.core.content.FileProvider
+import android.os.Build
+import android.util.Log
+import cx.ring.BuildConfig
+import net.jami.utils.FileUtils
+import java.io.File
+import java.lang.IllegalArgumentException
+
+/**
+ * This class distributes content uri used to pass along data in the app
+ */
+object ContentUriHandler {
+    private val TAG = ContentUriHandler::class.java.simpleName
+    const val AUTHORITY = BuildConfig.APPLICATION_ID
+    const val AUTHORITY_FILES = "$AUTHORITY.file_provider"
+    const val SCHEME_TV = "jamitv"
+    const val PATH_TV_HOME = "home"
+    const val PATH_TV_CONVERSATION = "conversation"
+    private val AUTHORITY_URI = Uri.parse("content://$AUTHORITY")
+
+    val CONVERSATION_CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, "conversation")
+    val ACCOUNTS_CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, "accounts")
+    val CONTACT_CONTENT_URI: Uri = Uri.withAppendedPath(AUTHORITY_URI, "contact")
+
+    /**
+     * The following is a workaround used to mitigate getUriForFile exceptions
+     * on Huawei devices taken from stackoverflow
+     * https://stackoverflow.com/a/41309223
+     */
+    private const val HUAWEI_MANUFACTURER = "Huawei"
+    fun getUriForResource(context: Context, resourceId: Int): Uri {
+        val resources = context.resources
+        return Uri.Builder()
+            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+            .authority(resources.getResourcePackageName(resourceId))
+            .appendPath(resources.getResourceTypeName(resourceId))
+            .appendPath(resources.getResourceEntryName(resourceId))
+            .build()
+    }
+
+    @JvmStatic
+    fun getUriForFile(context: Context, authority: String, file: File): Uri {
+        return getUriForFile(context, authority, file, null)
+    }
+
+    @JvmStatic
+    fun getUriForFile(context: Context, authority: String, file: File, displayName: String?): Uri {
+        return try {
+            if (displayName == null)
+                FileProvider.getUriForFile(context, authority, file)
+            else
+                FileProvider.getUriForFile(context, authority, file, displayName)
+        } catch (e: IllegalArgumentException) {
+            if (HUAWEI_MANUFACTURER.equals(Build.MANUFACTURER, ignoreCase = true)) {
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+                    Log.w(TAG, "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e)
+                    Uri.fromFile(file)
+                } else {
+                    Log.w(TAG, "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e)
+                    // Note: Periodically clear this cache
+                    val cacheFolder = File(context.cacheDir, HUAWEI_MANUFACTURER)
+                    val cacheLocation = File(cacheFolder, file.name)
+                    if (FileUtils.copyFile(file, cacheLocation)) {
+                        Log.i(TAG, "Completed Android N+ Huawei file copy. Attempting to return the cached file")
+                        return if (displayName == null) FileProvider.getUriForFile(
+                            context,
+                            authority,
+                            cacheLocation
+                        ) else FileProvider.getUriForFile(
+                            context,
+                            authority,
+                            cacheLocation,
+                            displayName
+                        )
+                    }
+                    Log.e(TAG, "Failed to copy the Huawei file. Re-throwing exception")
+                    throw IllegalArgumentException("Huawei devices are unsupported for Android N")
+                }
+            } else {
+                throw e
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.java b/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.java
deleted file mode 100644
index fda742130..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.utils;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.pm.ShortcutManagerCompat;
-
-import net.jami.model.Conversation;
-import net.jami.model.Interaction;
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
-import cx.ring.BuildConfig;
-
-public class ConversationPath {
-    public static final String KEY_CONVERSATION_URI = BuildConfig.APPLICATION_ID + ".conversationUri";
-    public static final String KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + ".accountId";
-
-    private final String accountId;
-    private final String conversationId;
-    public ConversationPath(String account, String contact) {
-        accountId = account;
-        conversationId = contact;
-    }
-    public ConversationPath(String account, net.jami.model.Uri conversationUri) {
-        accountId = account;
-        conversationId = conversationUri.getUri();
-    }
-
-    public ConversationPath(@NonNull Tuple<String, String> path) {
-        accountId = path.first;
-        conversationId = path.second;
-    }
-
-    public ConversationPath(Conversation conversation) {
-        accountId = conversation.getAccountId();
-        conversationId = conversation.getUri().getUri();
-    }
-
-    public String getAccountId() {
-        return accountId;
-    }
-    public String getConversationId() {
-        return conversationId;
-    }
-    public net.jami.model.Uri getConversationUri() {
-        return net.jami.model.Uri.fromString(conversationId);
-    }
-
-    @Deprecated
-    public String getContactId() {
-        return conversationId;
-    }
-
-    public Uri toUri() {
-        return toUri(accountId, conversationId);
-    }
-    public static Uri toUri(String accountId, String contactId) {
-        return ContentUriHandler.CONVERSATION_CONTENT_URI.buildUpon()
-                .appendEncodedPath(accountId)
-                .appendEncodedPath(contactId)
-                .build();
-    }
-    public static Uri toUri(String accountId, @NonNull net.jami.model.Uri conversationUri) {
-        return ContentUriHandler.CONVERSATION_CONTENT_URI.buildUpon()
-                .appendEncodedPath(accountId)
-                .appendEncodedPath(conversationUri.getUri())
-                .build();
-    }
-    public static Uri toUri(@NonNull Conversation conversation) {
-        return toUri(conversation.getAccountId(), conversation.getUri());
-    }
-    public static Uri toUri(@NonNull Interaction interaction) {
-        if (interaction.getConversation() instanceof Conversation)
-            return toUri(interaction.getAccount(), ((Conversation) interaction.getConversation()).getUri());
-        else
-            return toUri(interaction.getAccount(), net.jami.model.Uri.fromString(interaction.getConversation().getParticipant()));
-    }
-
-    public Bundle toBundle() {
-        return toBundle(accountId, conversationId);
-    }
-    public void toBundle(Bundle bundle) {
-        bundle.putString(KEY_CONVERSATION_URI, conversationId);
-        bundle.putString(KEY_ACCOUNT_ID, accountId);
-    }
-
-    public static Bundle toBundle(String accountId, String uri) {
-        Bundle bundle = new Bundle();
-        bundle.putString(KEY_CONVERSATION_URI, uri);
-        bundle.putString(KEY_ACCOUNT_ID, accountId);
-        return bundle;
-    }
-    public static Bundle toBundle(String accountId, net.jami.model.Uri uri) {
-        return toBundle(accountId, uri.getUri());
-    }
-    public static Bundle toBundle(@NonNull Conversation conversation) {
-        return toBundle(conversation.getAccountId(), conversation.getUri());
-    }
-
-    public static String toKey(String accountId, String uri) {
-        return TextUtils.join(",", Arrays.asList(accountId, uri));
-    }
-    public String toKey() {
-        return toKey(accountId, conversationId);
-    }
-    public static ConversationPath fromKey(String key) {
-        if (key != null) {
-            String[] keys = TextUtils.split(key, ",");
-            if (keys.length > 1) {
-                String accountId = keys[0];
-                String contactId = keys[1];
-                if (!StringUtils.isEmpty(accountId) && !StringUtils.isEmpty(contactId)) {
-                    return new ConversationPath(accountId, contactId);
-                }
-            }
-        }
-        return null;
-    }
-
-    public static ConversationPath fromUri(Uri uri) {
-        if (uri == null)
-            return null;
-        if (ContentUriHandler.SCHEME_TV.equals(uri.getScheme()) || uri.toString().startsWith(ContentUriHandler.CONVERSATION_CONTENT_URI.toString())) {
-            List<String> pathSegments = uri.getPathSegments();
-            if (pathSegments.size() > 2) {
-                return new ConversationPath(pathSegments.get(pathSegments.size() - 2), pathSegments.get(pathSegments.size() - 1));
-            }
-        }
-        return null;
-    }
-
-    public static ConversationPath fromBundle(@Nullable Bundle bundle) {
-        if (bundle != null) {
-            String accountId = bundle.getString(KEY_ACCOUNT_ID);
-            String contactId = bundle.getString(KEY_CONVERSATION_URI);
-            if (accountId != null && contactId != null) {
-                return new ConversationPath(accountId, contactId);
-            } else {
-                String shortcutId = bundle.getString(ShortcutManagerCompat.EXTRA_SHORTCUT_ID);
-                if (shortcutId != null)
-                    return fromKey(shortcutId);
-            }
-        }
-        return null;
-    }
-
-    public static ConversationPath fromIntent(@Nullable Intent intent) {
-        if (intent != null) {
-            Uri uri = intent.getData();
-            ConversationPath conversationPath = fromUri(uri);
-            if (conversationPath != null)
-                return conversationPath;
-            return fromBundle(intent.getExtras());
-        }
-        return null;
-    }
-
-    @Override
-    public @NonNull String toString() {
-        return "ConversationPath{" +
-                "accountId='" + accountId + '\'' +
-                ", conversationId='" + conversationId + '\'' +
-                '}';
-    }
-
-    @Override
-    public boolean equals(@Nullable Object obj) {
-        if (obj == this)
-            return true;
-        if (!(obj instanceof ConversationPath))
-            return false;
-        ConversationPath o = (ConversationPath) obj;
-        return Objects.equals(o.accountId, accountId)
-                && Objects.equals(o.conversationId, conversationId);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(accountId, conversationId);
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt b/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt
new file mode 100644
index 000000000..fbd26a1dc
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt
@@ -0,0 +1,190 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.utils
+
+import android.os.Bundle
+import net.jami.model.Interaction
+import android.text.TextUtils
+import androidx.core.content.pm.ShortcutManagerCompat
+import android.content.Intent
+import cx.ring.BuildConfig
+import net.jami.model.Conversation
+import net.jami.model.Uri
+import net.jami.utils.StringUtils
+import net.jami.utils.Tuple
+import java.util.*
+
+class ConversationPath {
+    val accountId: String
+    val conversationId: String
+    val conversationUri: Uri
+        get() = Uri.fromString(conversationId)
+
+    constructor(account: String, contact: String) {
+        accountId = account
+        conversationId = contact
+    }
+
+    constructor(account: String, conversationUri: Uri) {
+        accountId = account
+        conversationId = conversationUri.uri
+    }
+
+    constructor(path: Tuple<String, String>) {
+        accountId = path.first
+        conversationId = path.second
+    }
+
+    constructor(conversation: Conversation) {
+        accountId = conversation.accountId
+        conversationId = conversation.uri.uri
+    }
+
+    fun toBundle(bundle: Bundle) {
+        bundle.putString(KEY_CONVERSATION_URI, conversationId)
+        bundle.putString(KEY_ACCOUNT_ID, accountId)
+    }
+    fun toBundle(): Bundle {
+        val bundle = Bundle()
+        bundle.putString(KEY_CONVERSATION_URI, conversationId)
+        bundle.putString(KEY_ACCOUNT_ID, accountId)
+        return bundle
+    }
+    fun toUri(): android.net.Uri {
+        return ContentUriHandler.CONVERSATION_CONTENT_URI.buildUpon()
+            .appendEncodedPath(accountId)
+            .appendEncodedPath(conversationId)
+            .build()
+    }
+    fun toKey(): String {
+        return TextUtils.join(",", listOf(accountId, conversationId))
+    }
+
+    override fun toString(): String {
+        return "ConversationPath{accountId='$accountId' conversationId='$conversationId'}"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other === this) return true
+        if (other !is ConversationPath) return false
+        return (other.accountId == accountId
+                && other.conversationId == conversationId)
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(accountId, conversationId)
+    }
+
+    companion object {
+        const val KEY_CONVERSATION_URI = BuildConfig.APPLICATION_ID + ".conversationUri"
+        const val KEY_ACCOUNT_ID = BuildConfig.APPLICATION_ID + ".accountId"
+
+        fun toUri(accountId: String, contactId: String): android.net.Uri {
+            return ContentUriHandler.CONVERSATION_CONTENT_URI.buildUpon()
+                .appendEncodedPath(accountId)
+                .appendEncodedPath(contactId)
+                .build()
+        }
+
+        fun toUri(accountId: String?, conversationUri: Uri): android.net.Uri {
+            return ContentUriHandler.CONVERSATION_CONTENT_URI.buildUpon()
+                .appendEncodedPath(accountId)
+                .appendEncodedPath(conversationUri.uri)
+                .build()
+        }
+
+        fun toUri(conversation: Conversation): android.net.Uri {
+            return toUri(conversation.accountId, conversation.uri)
+        }
+
+        fun toUri(interaction: Interaction): android.net.Uri {
+            return if (interaction.conversation is Conversation)
+                toUri(interaction.account, (interaction.conversation as Conversation).uri)
+            else
+                toUri(interaction.account, Uri.fromString(interaction.conversation!!.participant))
+        }
+
+        fun toBundle(accountId: String, uri: String): Bundle {
+            val bundle = Bundle()
+            bundle.putString(KEY_CONVERSATION_URI, uri)
+            bundle.putString(KEY_ACCOUNT_ID, accountId)
+            return bundle
+        }
+
+        fun toBundle(accountId: String, uri: Uri): Bundle {
+            return toBundle(accountId, uri.uri)
+        }
+
+        fun toBundle(conversation: Conversation): Bundle {
+            return toBundle(conversation.accountId, conversation.uri)
+        }
+
+        fun toKey(accountId: String, uri: String): String {
+            return TextUtils.join(",", listOf(accountId, uri))
+        }
+
+        fun fromKey(key: String?): ConversationPath? {
+            if (key != null) {
+                val keys = TextUtils.split(key, ",")
+                if (keys.size > 1) {
+                    val accountId = keys[0]
+                    val contactId = keys[1]
+                    if (!StringUtils.isEmpty(accountId) && !StringUtils.isEmpty(contactId)) {
+                        return ConversationPath(accountId, contactId)
+                    }
+                }
+            }
+            return null
+        }
+
+        fun fromUri(uri: android.net.Uri?): ConversationPath? {
+            if (uri == null) return null
+            if (ContentUriHandler.SCHEME_TV == uri.scheme || uri.toString().startsWith(ContentUriHandler.CONVERSATION_CONTENT_URI.toString())) {
+                val pathSegments = uri.pathSegments
+                if (pathSegments.size > 2) {
+                    return ConversationPath(pathSegments[pathSegments.size - 2], pathSegments[pathSegments.size - 1])
+                }
+            }
+            return null
+        }
+
+        fun fromBundle(bundle: Bundle?): ConversationPath? {
+            if (bundle != null) {
+                val accountId = bundle.getString(KEY_ACCOUNT_ID)
+                val contactId = bundle.getString(KEY_CONVERSATION_URI)
+                if (accountId != null && contactId != null) {
+                    return ConversationPath(accountId, contactId)
+                } else {
+                    val shortcutId = bundle.getString(ShortcutManagerCompat.EXTRA_SHORTCUT_ID)
+                    if (shortcutId != null) return fromKey(shortcutId)
+                }
+            }
+            return null
+        }
+
+        fun fromIntent(intent: Intent?): ConversationPath? {
+            if (intent != null) {
+                val uri = intent.data
+                val conversationPath = fromUri(uri)
+                return conversationPath ?: fromBundle(intent.extras)
+            }
+            return null
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt b/ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
new file mode 100644
index 000000000..15f196329
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils
+
+import android.app.UiModeManager
+import android.content.Context
+import android.content.res.Configuration
+import cx.ring.R
+
+object DeviceUtils {
+    @JvmStatic
+    fun isTv(context: Context): Boolean {
+        val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+        return uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
+    }
+
+    @JvmStatic
+    fun isTablet(context: Context): Boolean {
+        return context.resources.getBoolean(R.bool.isTablet)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.java b/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.java
deleted file mode 100644
index 68315533b..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package cx.ring.utils;
-
-import android.content.Context;
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.GlideBuilder;
-import com.bumptech.glide.annotation.GlideModule;
-import com.bumptech.glide.load.DecodeFormat;
-import com.bumptech.glide.module.AppGlideModule;
-import com.bumptech.glide.request.RequestOptions;
-
-@GlideModule
-public final class JamiGlideModule extends AppGlideModule {
-
-    @Override
-    public void applyOptions(@NonNull Context context, GlideBuilder builder) {
-        builder.setDefaultRequestOptions(
-                new RequestOptions()
-                        .format(DecodeFormat.PREFER_ARGB_8888));
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.kt b/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.kt
new file mode 100644
index 000000000..a49de0ca1
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/JamiGlideModule.kt
@@ -0,0 +1,15 @@
+package cx.ring.utils
+
+import android.content.Context
+import com.bumptech.glide.module.AppGlideModule
+import com.bumptech.glide.GlideBuilder
+import com.bumptech.glide.annotation.GlideModule
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.load.DecodeFormat
+
+@GlideModule
+class JamiGlideModule : AppGlideModule() {
+    override fun applyOptions(context: Context, builder: GlideBuilder) {
+        builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_ARGB_8888))
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.java b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.java
deleted file mode 100644
index 1fe541305..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.utils;
-
-import android.text.InputFilter;
-import android.text.SpannableString;
-import android.text.Spanned;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class RegisteredNameFilter implements InputFilter {
-    private static final Pattern REGISTERED_NAME_CHAR_PATTERN = Pattern.compile("[a-z0-9_\\-]", Pattern.CASE_INSENSITIVE);
-    private final Matcher nameCharMatcher = REGISTERED_NAME_CHAR_PATTERN.matcher("");
-
-    @Override
-    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
-        boolean keepOriginal = true;
-        StringBuilder sb = new StringBuilder(end - start);
-        for (int i = start; i < end; i++) {
-            char c = source.charAt(i);
-            if (isCharAllowed(c)) {
-                sb.append(c);
-            } else {
-                keepOriginal = false;
-            }
-        }
-        if (keepOriginal) {
-            return null;
-        } else {
-            if (source instanceof Spanned) {
-                return new SpannableString(sb);
-            } else {
-                return sb;
-            }
-        }
-    }
-
-    private boolean isCharAllowed(char c) {
-        return nameCharMatcher.reset(String.valueOf(c)).matches();
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.kt b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.kt
new file mode 100644
index 000000000..49e39688d
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameFilter.kt
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.utils
+
+import android.text.InputFilter
+import android.text.Spanned
+import android.text.SpannableString
+import java.lang.StringBuilder
+import java.util.regex.Pattern
+
+class RegisteredNameFilter : InputFilter {
+    private val nameCharMatcher = REGISTERED_NAME_CHAR_PATTERN.matcher("")
+    override fun filter(
+        source: CharSequence,
+        start: Int,
+        end: Int,
+        dest: Spanned,
+        dstart: Int,
+        dend: Int
+    ): CharSequence? {
+        var keepOriginal = true
+        val sb = StringBuilder(end - start)
+        for (i in start until end) {
+            val c = source[i]
+            if (isCharAllowed(c)) {
+                sb.append(c)
+            } else {
+                keepOriginal = false
+            }
+        }
+        return if (keepOriginal) {
+            null
+        } else {
+            if (source is Spanned) {
+                SpannableString(sb)
+            } else {
+                sb
+            }
+        }
+    }
+
+    private fun isCharAllowed(c: Char): Boolean {
+        return nameCharMatcher.reset(c.toString()).matches()
+    }
+
+    companion object {
+        private val REGISTERED_NAME_CHAR_PATTERN =
+            Pattern.compile("[a-z0-9_\\-]", Pattern.CASE_INSENSITIVE)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.java b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.java
deleted file mode 100644
index 58f195e4f..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package cx.ring.utils;
-
-import android.content.Context;
-import com.google.android.material.textfield.TextInputLayout;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.widget.EditText;
-
-import java.lang.ref.WeakReference;
-
-import cx.ring.R;
-import net.jami.services.AccountService;
-import net.jami.utils.NameLookupInputHandler;
-
-public class RegisteredNameTextWatcher implements TextWatcher {
-
-    private WeakReference<TextInputLayout> mInputLayout;
-    private WeakReference<EditText> mInputText;
-    private NameLookupInputHandler mNameLookupInputHandler;
-    private String mLookingForAvailability;
-
-    public RegisteredNameTextWatcher(Context context, AccountService accountService, String accountId, TextInputLayout inputLayout, EditText inputText) {
-        mInputLayout = new WeakReference<>(inputLayout);
-        mInputText = new WeakReference<>(inputText);
-        mLookingForAvailability = context.getString(R.string.looking_for_username_availability);
-        mNameLookupInputHandler = new net.jami.utils.NameLookupInputHandler(accountService, accountId);
-    }
-
-    @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-
-    }
-
-    @Override
-    public void onTextChanged(CharSequence s, int start, int before, int count) {
-        if (mInputText.get() != null) {
-            mInputText.get().setError(null);
-        }
-    }
-
-    @Override
-    public void afterTextChanged(final Editable txt) {
-        final String name = txt.toString();
-
-        if (mInputLayout.get() == null || mInputText.get() == null) {
-            return;
-        }
-
-        if (TextUtils.isEmpty(name)) {
-            mInputLayout.get().setErrorEnabled(false);
-            mInputLayout.get().setError(null);
-        } else {
-            mInputLayout.get().setErrorEnabled(true);
-            mInputLayout.get().setError(mLookingForAvailability);
-        }
-
-        if (!TextUtils.isEmpty(name)) {
-            mNameLookupInputHandler.enqueueNextLookup(name);
-        }
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.kt b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.kt
new file mode 100644
index 000000000..0a952af76
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/RegisteredNameTextWatcher.kt
@@ -0,0 +1,65 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.utils
+
+import android.content.Context
+import net.jami.services.AccountService
+import com.google.android.material.textfield.TextInputLayout
+import android.widget.EditText
+import android.text.TextWatcher
+import net.jami.utils.NameLookupInputHandler
+import android.text.Editable
+import android.text.TextUtils
+import cx.ring.R
+import java.lang.ref.WeakReference
+
+class RegisteredNameTextWatcher(
+    context: Context,
+    accountService: AccountService,
+    accountId: String,
+    inputLayout: TextInputLayout,
+    inputText: EditText
+) : TextWatcher {
+    private val mInputLayout: WeakReference<TextInputLayout> = WeakReference(inputLayout)
+    private val mInputText: WeakReference<EditText> = WeakReference(inputText)
+    private val mNameLookupInputHandler = NameLookupInputHandler(accountService, accountId)
+    private val mLookingForAvailability = context.getString(R.string.looking_for_username_availability)
+
+    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+        mInputText.get()?.apply { error = null }
+    }
+
+    override fun afterTextChanged(txt: Editable) {
+        val name = txt.toString()
+        mInputLayout.get()?.let { inputLayout ->
+            if (TextUtils.isEmpty(name)) {
+                inputLayout.isErrorEnabled = false
+                inputLayout.error = null
+            } else {
+                inputLayout.isErrorEnabled = true
+                inputLayout.error = mLookingForAvailability
+            }
+        }
+        if (!TextUtils.isEmpty(name)) {
+            mNameLookupInputHandler.enqueueNextLookup(name)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/Ringer.java b/ring-android/app/src/main/java/cx/ring/utils/Ringer.java
deleted file mode 100644
index 6c10af37b..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/Ringer.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.utils;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Build;
-import android.os.Vibrator;
-import android.util.Log;
-
-/**
- * Ringer manager
- */
-public class Ringer {
-    private static final String TAG = Ringer.class.getSimpleName();
-    private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
-
-    @SuppressLint("NewApi")
-    private static final AudioAttributes VIBRATE_ATTRIBUTES = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ?
-            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build() : null;
-
-    private final Context context;
-    private final Vibrator vibrator;
-
-    public Ringer(Context aContext) {
-        context = aContext;
-        vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-    }
-
-    /**
-     * Starts the ringtone and/or vibrator.
-     */
-    public void ring() {
-        Log.d(TAG, "ring: called...");
-
-        AudioManager audioManager =
-                (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
-        vibrator.cancel();
-
-        int ringerMode = audioManager.getRingerMode();
-        if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
-            //No ring no vibrate
-            Log.d(TAG, "ring: skipping ring and vibrate because profile is Silent");
-        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_NORMAL) {
-            // Vibrate
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                vibrator.vibrate(VIBRATE_PATTERN, 0, VIBRATE_ATTRIBUTES);
-            } else {
-                vibrator.vibrate(VIBRATE_PATTERN, 0);
-            }
-        }
-    }
-
-    /**
-     * Stops the ringtone and/or vibrator if any of these are actually
-     * ringing/vibrating.
-     */
-    public void stopRing() {
-        Log.d(TAG, "stopRing: called...");
-        vibrator.cancel();
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/Ringer.kt b/ring-android/app/src/main/java/cx/ring/utils/Ringer.kt
new file mode 100644
index 000000000..54a565686
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/Ringer.kt
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.utils
+
+import android.content.Context
+import android.os.Vibrator
+import android.media.AudioManager
+import android.media.AudioAttributes
+import android.util.Log
+
+/**
+ * Ringer manager
+ */
+class Ringer(private val context: Context) {
+    private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+
+    /**
+     * Starts the ringtone and/or vibrator.
+     */
+    fun ring() {
+        Log.d(TAG, "ring: called...")
+        vibrator.cancel()
+        val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+        val ringerMode = audioManager.ringerMode
+        if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+            //No ring no vibrate
+            Log.d(TAG, "ring: skipping ring and vibrate because profile is Silent")
+        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_NORMAL) {
+            // Vibrate
+            vibrator.vibrate(VIBRATE_PATTERN, 0, VIBRATE_ATTRIBUTES)
+        }
+    }
+
+    /**
+     * Stops the ringtone and/or vibrator if any of these are actually
+     * ringing/vibrating.
+     */
+    fun stopRing() {
+        Log.d(TAG, "stopRing: called...")
+        vibrator.cancel()
+    }
+
+    companion object {
+        private val TAG = Ringer::class.simpleName!!
+        private val VIBRATE_PATTERN = longArrayOf(0, 1000, 1000)
+        private val VIBRATE_ATTRIBUTES = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build()
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/ShadowScrollBehavior.java b/ring-android/app/src/main/java/cx/ring/utils/ShadowScrollBehavior.java
deleted file mode 100644
index e4b785474..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/ShadowScrollBehavior.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package cx.ring.utils;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.view.ViewCompat;
-import androidx.transition.TransitionManager;
-
-import com.google.android.material.appbar.AppBarLayout;
-
-public class ShadowScrollBehavior extends AppBarLayout.ScrollingViewBehavior
-        implements View.OnLayoutChangeListener {
-
-    int totalDy = 0;
-    boolean isElevated;
-    View child;
-
-    public ShadowScrollBehavior(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public boolean layoutDependsOn(CoordinatorLayout parent, View child,
-                                   View dependency) {
-        parent.addOnLayoutChangeListener(this);
-        this.child = child;
-        return super.layoutDependsOn(parent, child, dependency);
-    }
-
-    @Override
-    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                                       @NonNull View child, @NonNull View directTargetChild,
-                                       @NonNull View target, int axes, int type) {
-        // Ensure we react to vertical scrolling
-        return axes == ViewCompat.SCROLL_AXIS_VERTICAL ||
-                super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
-                        target, axes, type);
-    }
-
-    @Override
-    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
-                                  @NonNull View child, @NonNull View target,
-                                  int dx, int dy, @NonNull int[] consumed, int type) {
-        totalDy += dy;
-        if (totalDy <= 0) {
-            if (isElevated) {
-                ViewGroup parent = (ViewGroup) child.getParent();
-                if (parent != null) {
-                    TransitionManager.beginDelayedTransition(parent);
-                    ViewCompat.setElevation(child, 0);
-                }
-            }
-            totalDy = 0;
-            isElevated = false;
-        } else {
-            if (!isElevated) {
-                ViewGroup parent = (ViewGroup) child.getParent();
-                if (parent != null) {
-                    TransitionManager.beginDelayedTransition(parent);
-                    ViewCompat.setElevation(child, dp2px(child.getContext(), 4));
-                }
-            }
-            if (totalDy > target.getBottom())
-                totalDy = target.getBottom();
-            isElevated = true;
-        }
-        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
-    }
-
-
-    private float dp2px(Context context, int dp) {
-        Resources r = context.getResources();
-        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
-        return px;
-    }
-
-
-    @Override
-    public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
-        totalDy = 0;
-        isElevated = false;
-        ViewCompat.setElevation(child, 0);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.java b/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.java
deleted file mode 100644
index a5c079b80..000000000
--- a/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package cx.ring.utils;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-
-public class TouchClickListener implements GestureDetector.OnGestureListener, View.OnTouchListener {
-    private final View.OnClickListener onClickListener;
-    private final GestureDetector mGestureDetector;
-    private View view;
-
-    public TouchClickListener(Context c, View.OnClickListener l) {
-        onClickListener = l;
-        mGestureDetector = new GestureDetector(c, this);
-    }
-
-    @Override
-    public boolean onDown(MotionEvent e) {
-        return false;
-    }
-
-    @Override
-    public void onShowPress(MotionEvent e) {}
-
-    @Override
-    public boolean onSingleTapUp(MotionEvent e) {
-        onClickListener.onClick(view);
-        return true;
-    }
-
-    @Override
-    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
-        return false;
-    }
-
-    @Override
-    public void onLongPress(MotionEvent e) {}
-
-    @Override
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        return false;
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        view = v;
-        mGestureDetector.onTouchEvent(event);
-        view = null;
-        return true;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.kt b/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.kt
new file mode 100644
index 000000000..206a80e4c
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/TouchClickListener.kt
@@ -0,0 +1,69 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package cx.ring.utils
+
+import android.view.GestureDetector
+import android.view.View.OnTouchListener
+import android.view.MotionEvent
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.View
+
+class TouchClickListener(c: Context?, private val onClickListener: View.OnClickListener) :
+    GestureDetector.OnGestureListener, OnTouchListener {
+    private val mGestureDetector: GestureDetector = GestureDetector(c, this)
+    private var view: View? = null
+    override fun onDown(e: MotionEvent): Boolean {
+        return false
+    }
+
+    override fun onShowPress(e: MotionEvent) {}
+    override fun onSingleTapUp(e: MotionEvent): Boolean {
+        onClickListener.onClick(view)
+        return true
+    }
+
+    override fun onScroll(
+        e1: MotionEvent,
+        e2: MotionEvent,
+        distanceX: Float,
+        distanceY: Float
+    ): Boolean {
+        return false
+    }
+
+    override fun onLongPress(e: MotionEvent) {}
+    override fun onFling(
+        e1: MotionEvent,
+        e2: MotionEvent,
+        velocityX: Float,
+        velocityY: Float
+    ): Boolean {
+        return false
+    }
+
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouch(v: View, event: MotionEvent): Boolean {
+        view = v
+        mGestureDetector.onTouchEvent(event)
+        view = null
+        return true
+    }
+
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java
deleted file mode 100644
index 6dea324b2..000000000
--- a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.viewholders;
-
-import android.content.Context;
-import android.graphics.Typeface;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import net.jami.model.Call;
-import net.jami.model.ContactEvent;
-import net.jami.model.Interaction;
-import net.jami.smartlist.SmartListViewModel;
-
-import cx.ring.R;
-import cx.ring.databinding.ItemSmartlistBinding;
-import cx.ring.databinding.ItemSmartlistHeaderBinding;
-import cx.ring.utils.ResourceMapper;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-
-public class SmartListViewHolder extends RecyclerView.ViewHolder {
-    public final ItemSmartlistBinding binding;
-    public final ItemSmartlistHeaderBinding headerBinding;
-    private final CompositeDisposable compositeDisposable = new CompositeDisposable();
-
-    public SmartListViewHolder(@NonNull ItemSmartlistBinding b, @NonNull CompositeDisposable parentDisposable) {
-        super(b.getRoot());
-        binding = b;
-        headerBinding = null;
-        parentDisposable.add(compositeDisposable);
-    }
-
-    public SmartListViewHolder(@NonNull ItemSmartlistHeaderBinding b, @NonNull CompositeDisposable parentDisposable) {
-        super(b.getRoot());
-        binding = null;
-        headerBinding = b;
-        parentDisposable.add(compositeDisposable);
-    }
-
-    public void bind(final SmartListListeners clickListener, final SmartListViewModel smartListViewModel) {
-        //Log.w("SmartListViewHolder", "bind " + smartListViewModel.getContact() + " " +smartListViewModel.showPresence());
-        compositeDisposable.clear();
-
-        if (binding != null) {
-            itemView.setOnClickListener(v -> clickListener.onItemClick(smartListViewModel));
-            Observable<Boolean> selected = smartListViewModel.getSelected();
-            if (selected != null) {
-                compositeDisposable.add(smartListViewModel.getSelected().subscribe(binding.itemLayout::setActivated));
-            }
-            itemView.setOnLongClickListener(v -> {
-                clickListener.onItemLongClick(smartListViewModel);
-                return true;
-            });
-
-            binding.convParticipant.setText(smartListViewModel.getContactName());
-
-            long lastInteraction = smartListViewModel.getLastInteractionTime();
-            String lastInteractionStr = lastInteraction == 0 ?
-                    "" : DateUtils.getRelativeTimeSpanString(lastInteraction, System.currentTimeMillis(), 0L, DateUtils.FORMAT_ABBREV_ALL).toString();
-
-            binding.convLastTime.setText(lastInteractionStr);
-            if (smartListViewModel.hasOngoingCall()) {
-                binding.convLastItem.setVisibility(View.VISIBLE);
-                binding.convLastItem.setText(itemView.getContext().getString(R.string.ongoing_call));
-            } else if (smartListViewModel.getLastEvent() != null) {
-                binding.convLastItem.setVisibility(View.VISIBLE);
-                binding.convLastItem.setText(getLastEventSummary(smartListViewModel.getLastEvent(), itemView.getContext()));
-            } else {
-                binding.convLastItem.setVisibility(View.GONE);
-            }
-
-            if (smartListViewModel.hasUnreadTextMessage()) {
-                binding.convParticipant.setTypeface(null, Typeface.BOLD);
-                binding.convLastTime.setTypeface(null, Typeface.BOLD);
-                binding.convLastItem.setTypeface(null, Typeface.BOLD);
-            } else {
-                binding.convParticipant.setTypeface(null, Typeface.NORMAL);
-                binding.convLastTime.setTypeface(null, Typeface.NORMAL);
-                binding.convLastItem.setTypeface(null, Typeface.NORMAL);
-            }
-
-            binding.photo.setImageDrawable(new AvatarDrawable.Builder()
-                    .withViewModel(smartListViewModel)
-                    .withCircleCrop(true)
-                    .build(binding.photo.getContext()));
-
-        } else if (headerBinding != null) {
-            headerBinding.headerTitle.setText(smartListViewModel.getHeaderTitle() == SmartListViewModel.Title.Conversations
-                    ? R.string.navigation_item_conversation : R.string.search_results_public_directory);
-        }
-    }
-
-    public void unbind() {
-        compositeDisposable.clear();
-    }
-
-    private String getLastEventSummary(Interaction e, Context context) {
-        if (e.getType() == (Interaction.InteractionType.TEXT)) {
-            if (e.isIncoming()) {
-                return e.getBody();
-            } else {
-                return context.getText(R.string.you_txt_prefix) + " " + e.getBody();
-            }
-        } else if (e.getType() == (Interaction.InteractionType.CALL)) {
-            Call call = (Call) e;
-            if (call.isMissed())
-                return call.isIncoming() ?
-                        context.getString(R.string.notif_missed_incoming_call) :
-                        context.getString(R.string.notif_missed_outgoing_call);
-            else
-                return call.isIncoming() ?
-                        String.format(context.getString(R.string.hist_in_call), call.getDurationString()) :
-                        String.format(context.getString(R.string.hist_out_call), call.getDurationString());
-        } else if (e.getType() == (Interaction.InteractionType.CONTACT)) {
-            ContactEvent contactEvent = (ContactEvent) e;
-            if (contactEvent.event == ContactEvent.Event.ADDED) {
-                return context.getString(R.string.hist_contact_added);
-            } else if (contactEvent.event == ContactEvent.Event.INCOMING_REQUEST) {
-                return context.getString(R.string.hist_invitation_received);
-            }
-        } else if (e.getType() == (Interaction.InteractionType.DATA_TRANSFER)) {
-            if (e.getStatus() == Interaction.InteractionStatus.TRANSFER_FINISHED) {
-                if (!e.isIncoming()) {
-                    return context.getString(R.string.hist_file_sent);
-                } else {
-                    return context.getString(R.string.hist_file_received);
-                }
-            }
-            return ResourceMapper.getReadableFileTransferStatus(context, e.getStatus());
-        }
-        return null;
-    }
-
-    public interface SmartListListeners {
-        void onItemClick(SmartListViewModel smartListViewModel);
-
-        void onItemLongClick(SmartListViewModel smartListViewModel);
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt
new file mode 100644
index 000000000..6ecfaab13
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt
@@ -0,0 +1,152 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.viewholders
+
+import android.content.Context
+import android.graphics.Typeface
+import android.text.format.DateUtils
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+import cx.ring.R
+import cx.ring.databinding.ItemSmartlistBinding
+import cx.ring.databinding.ItemSmartlistHeaderBinding
+import cx.ring.utils.ResourceMapper
+import cx.ring.views.AvatarDrawable
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import net.jami.model.Call
+import net.jami.model.ContactEvent
+import net.jami.model.Interaction
+import net.jami.smartlist.SmartListViewModel
+
+class SmartListViewHolder : RecyclerView.ViewHolder {
+    val binding: ItemSmartlistBinding?
+    val headerBinding: ItemSmartlistHeaderBinding?
+    private val compositeDisposable = CompositeDisposable()
+
+    constructor(b: ItemSmartlistBinding, parentDisposable: CompositeDisposable) : super(b.root) {
+        binding = b
+        headerBinding = null
+        parentDisposable.add(compositeDisposable)
+    }
+
+    constructor(b: ItemSmartlistHeaderBinding, parentDisposable: CompositeDisposable) : super(b.root) {
+        binding = null
+        headerBinding = b
+        parentDisposable.add(compositeDisposable)
+    }
+
+    fun bind(clickListener: SmartListListeners, smartListViewModel: SmartListViewModel) {
+        //Log.w("SmartListViewHolder", "bind " + smartListViewModel.getContact() + " " +smartListViewModel.showPresence());
+        compositeDisposable.clear()
+        if (binding != null) {
+            itemView.setOnClickListener { clickListener.onItemClick(smartListViewModel) }
+            smartListViewModel.selected?.let { selected ->
+                compositeDisposable.add(selected.observeOn(AndroidSchedulers.mainThread()).subscribe { activated ->
+                    binding.itemLayout.isActivated = activated
+                })
+            }
+            itemView.setOnLongClickListener {
+                clickListener.onItemLongClick(smartListViewModel)
+                true
+            }
+            binding.convParticipant.text = smartListViewModel.contactName
+            val lastInteraction = smartListViewModel.lastInteractionTime
+            val lastInteractionStr =
+                if (lastInteraction == 0L) "" else DateUtils.getRelativeTimeSpanString(
+                    lastInteraction,
+                    System.currentTimeMillis(),
+                    0L,
+                    DateUtils.FORMAT_ABBREV_ALL
+                ).toString()
+            binding.convLastTime.text = lastInteractionStr
+            if (smartListViewModel.hasOngoingCall()) {
+                binding.convLastItem.visibility = View.VISIBLE
+                binding.convLastItem.text = itemView.context.getString(R.string.ongoing_call)
+            } else if (smartListViewModel.lastEvent != null) {
+                binding.convLastItem.visibility = View.VISIBLE
+                binding.convLastItem.text =
+                    getLastEventSummary(smartListViewModel.lastEvent, itemView.context)
+            } else {
+                binding.convLastItem.visibility = View.GONE
+            }
+            if (smartListViewModel.hasUnreadTextMessage()) {
+                binding.convParticipant.setTypeface(null, Typeface.BOLD)
+                binding.convLastTime.setTypeface(null, Typeface.BOLD)
+                binding.convLastItem.setTypeface(null, Typeface.BOLD)
+            } else {
+                binding.convParticipant.setTypeface(null, Typeface.NORMAL)
+                binding.convLastTime.setTypeface(null, Typeface.NORMAL)
+                binding.convLastItem.setTypeface(null, Typeface.NORMAL)
+            }
+            binding.photo.setImageDrawable(
+                AvatarDrawable.Builder()
+                    .withViewModel(smartListViewModel)
+                    .withCircleCrop(true)
+                    .build(binding.photo.context)
+            )
+        } else headerBinding?.headerTitle?.setText(
+            if (smartListViewModel.headerTitle == SmartListViewModel.Title.Conversations) R.string.navigation_item_conversation else R.string.search_results_public_directory
+        )
+    }
+
+    fun unbind() {
+        compositeDisposable.clear()
+    }
+
+    private fun getLastEventSummary(e: Interaction, context: Context): String? {
+        if (e.type == Interaction.InteractionType.TEXT) {
+            return if (e.isIncoming) {
+                e.body
+            } else {
+                context.getText(R.string.you_txt_prefix).toString() + " " + e.body
+            }
+        } else if (e.type == Interaction.InteractionType.CALL) {
+            val call = e as Call
+            return if (call.isMissed) if (call.isIncoming) context.getString(R.string.notif_missed_incoming_call) else context.getString(
+                R.string.notif_missed_outgoing_call
+            ) else if (call.isIncoming) String.format(
+                context.getString(R.string.hist_in_call),
+                call.durationString
+            ) else String.format(context.getString(R.string.hist_out_call), call.durationString)
+        } else if (e.type == Interaction.InteractionType.CONTACT) {
+            val contactEvent = e as ContactEvent
+            if (contactEvent.event == ContactEvent.Event.ADDED) {
+                return context.getString(R.string.hist_contact_added)
+            } else if (contactEvent.event == ContactEvent.Event.INCOMING_REQUEST) {
+                return context.getString(R.string.hist_invitation_received)
+            }
+        } else if (e.type == Interaction.InteractionType.DATA_TRANSFER) {
+            return if (e.status == Interaction.InteractionStatus.TRANSFER_FINISHED) {
+                if (!e.isIncoming) {
+                    context.getString(R.string.hist_file_sent)
+                } else {
+                    context.getString(R.string.hist_file_received)
+                }
+            } else ResourceMapper.getReadableFileTransferStatus(context, e.status)
+        }
+        return null
+    }
+
+    interface SmartListListeners {
+        fun onItemClick(smartListViewModel: SmartListViewModel)
+        fun onItemLongClick(smartListViewModel: SmartListViewModel)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java
deleted file mode 100644
index b8ec20818..000000000
--- a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.java
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package cx.ring.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.VectorDrawable;
-import android.text.TextUtils;
-import android.util.TypedValue;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.smartlist.SmartListViewModel;
-import net.jami.utils.HashUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import cx.ring.R;
-import cx.ring.services.VCardServiceImpl;
-import cx.ring.utils.DeviceUtils;
-import io.reactivex.rxjava3.core.Single;
-
-public class AvatarDrawable extends Drawable {
-    private static final String TAG = AvatarDrawable.class.getSimpleName();
-
-    private static final int SIZE_AB = 36;
-    private static final float SIZE_BORDER = 2f;
-
-    private static final float DEFAULT_TEXT_SIZE_PERCENTAGE = 0.5f;
-    private static final int PLACEHOLDER_ICON = R.drawable.baseline_account_crop_24;
-    private static final int PLACEHOLDER_ICON_GROUP = R.drawable.baseline_group_24;
-    private static final int CHECKED_ICON = R.drawable.baseline_check_circle_24;
-    private static final int PRESENCE_COLOR = R.color.green_A700;
-
-    private static final int[] contactColors = {
-            R.color.red_500, R.color.pink_500,
-            R.color.purple_500, R.color.deep_purple_500,
-            R.color.indigo_500, R.color.blue_500,
-            R.color.cyan_500, R.color.teal_500,
-            R.color.green_500, R.color.light_green_500,
-            R.color.grey_500, R.color.lime_500,
-            R.color.amber_500, R.color.deep_orange_500,
-            R.color.brown_500, R.color.blue_grey_500
-    };
-
-    private static class PresenceIndicatorInfo {
-        int cx, cy, radius;
-    }
-
-    ;
-    private final PresenceIndicatorInfo presence = new PresenceIndicatorInfo();
-
-    private boolean update = true;
-    private final boolean isGroup;
-    private int inSize = -1;
-
-    private final int minSize;
-    private final List<Bitmap> workspace;
-    private final List<Bitmap> bitmaps;
-    private VectorDrawable placeholder;
-    private VectorDrawable checkedIcon;
-    private final List<RectF> backgroundBounds;
-    private final List<Rect> inBounds;
-    private String avatarText;
-    private float textStartXPoint;
-    private float textStartYPoint;
-    private int color;
-
-    private final List<Paint> clipPaint;
-    private final Paint textPaint = new Paint();
-    private final Paint presenceFillPaint;
-    private final Paint presenceStrokePaint;
-    private final Paint checkedPaint;
-    private static final Paint drawPaint = new Paint();
-
-    static {
-        drawPaint.setAntiAlias(true);
-        drawPaint.setFilterBitmap(true);
-    }
-
-    private final boolean cropCircle;
-    private boolean isOnline;
-    private boolean isChecked;
-    private boolean showPresence;
-
-    public static class Builder {
-
-        private List<Bitmap> photos = null;
-        private String name = null;
-        private String id = null;
-        private boolean circleCrop = false;
-        private boolean isOnline = false;
-        private boolean showPresence = true;
-        private boolean isChecked = false;
-        private boolean isGroup = false;
-
-        public Builder() {
-        }
-
-        public Builder withId(String id) {
-            this.id = id;
-            return this;
-        }
-
-        public Builder withPhoto(Bitmap photo) {
-            this.photos = photo == null ? null : Arrays.asList(photo); // list elements must be mutable
-            return this;
-        }
-
-        public Builder withPhotos(List<Bitmap> photos) {
-            this.photos = photos.isEmpty() ? null : photos;
-            return this;
-        }
-
-        public Builder withName(String name) {
-            this.name = name;
-            return this;
-        }
-
-        public Builder withCircleCrop(boolean crop) {
-            this.circleCrop = crop;
-            return this;
-        }
-
-        public Builder withOnlineState(boolean isOnline) {
-            this.isOnline = isOnline;
-            return this;
-        }
-
-        public Builder withPresence(boolean showPresence) {
-            this.showPresence = showPresence;
-            return this;
-        }
-
-        public Builder withCheck(boolean checked) {
-            this.isChecked = checked;
-            return this;
-        }
-
-        public Builder withNameData(String profileName, String username) {
-            return withName(TextUtils.isEmpty(profileName) ? username : profileName);
-        }
-
-        public Builder withContact(Contact contact) {
-            if (contact == null)
-                return this;
-            return withPhoto((Bitmap) contact.getPhoto())
-                    .withId(contact.getPrimaryNumber())
-                    .withOnlineState(contact.isOnline())
-                    .withNameData(contact.getProfileName(), contact.getUsername());
-        }
-
-        public Builder withContacts(List<Contact> contacts) {
-            List<Bitmap> bitmaps = new ArrayList<>(contacts.size());
-            int notTheUser = 0;
-            for (Contact contact : contacts) {
-                if (contact.isUser())
-                    continue;
-                notTheUser++;
-                Bitmap bitmap = (Bitmap) contact.getPhoto();
-                if (bitmap != null) {
-                    bitmaps.add(bitmap);
-                }
-                if (bitmaps.size() == 4)
-                    break;
-            }
-            if (notTheUser == 1) {
-                for (Contact contact : contacts) {
-                    if (!contact.isUser())
-                        return withContact(contact);
-                }
-            }
-            if (bitmaps.isEmpty()) {
-                // Fallback to the user avatar
-                for (Contact contact : contacts)
-                    return withContact(contact);
-            } else {
-                return withPhotos(bitmaps);
-            }
-            return this;
-        }
-
-        public Builder withConversation(Conversation conversation) {
-            return conversation.isSwarm()
-                    ? withContacts(conversation.getContacts()).setGroup()
-                    : withContact(conversation.getContact());
-        }
-
-        private Builder setGroup() {
-            isGroup = true;
-            return this;
-        }
-
-        public Builder withViewModel(SmartListViewModel vm) {
-            boolean isSwarm = vm.getUri().isSwarm();
-            return (isSwarm
-                    ? withContacts(vm.getContacts()).setGroup()
-                    : withContact(vm.getContacts().isEmpty() ? null : vm.getContacts().get(vm.getContacts().size() - 1)))
-                    .withPresence(vm.showPresence())
-                    .withOnlineState(vm.isOnline())
-                    .withCheck(vm.isChecked());
-        }
-
-        public AvatarDrawable build(Context context) {
-            AvatarDrawable avatarDrawable = new AvatarDrawable(
-                    context, photos, name, id, circleCrop, isGroup);
-            avatarDrawable.setOnline(isOnline);
-            avatarDrawable.setChecked(isChecked);
-            avatarDrawable.showPresence = this.showPresence;
-            return avatarDrawable;
-        }
-
-        public Single<AvatarDrawable> buildAsync(Context context) {
-            return Single.fromCallable(() -> build(context));
-        }
-    }
-
-    public static Single<AvatarDrawable> load(Context context, Account account, boolean crop) {
-        return VCardServiceImpl.loadProfile(context, account)
-                .map(data -> new Builder()
-                        .withPhoto((Bitmap) data.second)
-                        .withNameData(data.first, account.getRegisteredName())
-                        .withId(account.getUri())
-                        .withCircleCrop(crop)
-                        .build(context));
-    }
-
-    public static Single<AvatarDrawable> load(Context context, Account account) {
-        return load(context, account, true);
-    }
-
-    public void update(Contact contact) {
-        String profileName = contact.getProfileName();
-        String username = contact.getUsername();
-        avatarText = convertNameToAvatarText(
-                TextUtils.isEmpty(profileName) ? username : profileName);
-        if (bitmaps != null) {
-            bitmaps.set(0, (Bitmap) contact.getPhoto());
-        }
-        isOnline = contact.isOnline();
-        update = true;
-    }
-
-    public void setName(String name) {
-        avatarText = convertNameToAvatarText(name);
-        update = true;
-    }
-
-    public void setPhoto(Bitmap photo) {
-        bitmaps.set(0, photo);
-        update = true;
-    }
-
-    public void setOnline(boolean online) {
-        isOnline = online;
-    }
-
-    public void setChecked(boolean checked) {
-        isChecked = checked;
-    }
-
-    private AvatarDrawable(Context context, List<Bitmap> photos, String name, String id, boolean crop, boolean group) {
-        //Log.w("AvatarDrawable", "AvatarDrawable " + (photos == null ? null : photos.size()) + " " + name);
-        cropCircle = crop;
-        isGroup = group;
-        minSize = (int) TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, SIZE_AB, context.getResources().getDisplayMetrics());
-        float borderSize = TypedValue.applyDimension(
-                TypedValue.COMPLEX_UNIT_DIP, SIZE_BORDER, context.getResources().getDisplayMetrics());
-        if (cropCircle) {
-            inSize = minSize;
-        }
-        if (photos != null && photos.size() > 0) {
-            avatarText = null;
-            bitmaps = photos;
-            if (photos.size() == 1) {
-                backgroundBounds = Collections.singletonList(new RectF());
-                inBounds = Collections.singletonList(null);
-                clipPaint = cropCircle ? Collections.singletonList(new Paint()) : null;
-                workspace = Arrays.asList((Bitmap) null);
-            } else {
-                backgroundBounds = new ArrayList<>(bitmaps.size());
-                inBounds = new ArrayList<>(bitmaps.size());
-                clipPaint = cropCircle ? new ArrayList<>(bitmaps.size()) : null;
-                workspace = cropCircle ? new ArrayList<>(bitmaps.size()) : Arrays.asList((Bitmap) null);
-                for (Bitmap ignored : bitmaps) {
-                    backgroundBounds.add(new RectF());
-                    inBounds.add(cropCircle ? null : new Rect());
-                    if (cropCircle) {
-                        Paint p = new Paint();
-                        p.setStrokeWidth(borderSize);
-                        p.setColor(Color.WHITE);
-                        p.setStyle(Paint.Style.FILL);
-                        clipPaint.add(p);
-                        workspace.add(null);
-                    }
-                }
-            }
-        } else {
-            workspace = Arrays.asList((Bitmap) null);
-            bitmaps = null;
-            backgroundBounds = null;
-            inBounds = null;
-            avatarText = convertNameToAvatarText(name);
-            color = ContextCompat.getColor(context, getAvatarColor(id));
-            clipPaint = cropCircle ? Collections.singletonList(new Paint()) : null;
-            if (avatarText == null) {
-                placeholder = (VectorDrawable) context.getDrawable(isGroup ? PLACEHOLDER_ICON_GROUP : PLACEHOLDER_ICON);
-            } else {
-                textPaint.setColor(Color.WHITE);
-                textPaint.setTypeface(Typeface.SANS_SERIF);
-            }
-        }
-        presenceFillPaint = new Paint();
-        presenceFillPaint.setColor(ContextCompat.getColor(context, PRESENCE_COLOR));
-        presenceFillPaint.setStyle(Paint.Style.FILL);
-        presenceFillPaint.setAntiAlias(true);
-
-        presenceFillPaint.setColor(ContextCompat.getColor(context, PRESENCE_COLOR));
-        presenceFillPaint.setStyle(Paint.Style.FILL);
-        presenceFillPaint.setAntiAlias(true);
-
-        presenceStrokePaint = new Paint();
-        presenceStrokePaint.setColor(ContextCompat.getColor(context, DeviceUtils.isTv(context) ? R.color.grey_900 : R.color.background));
-        presenceStrokePaint.setStyle(Paint.Style.STROKE);
-        presenceStrokePaint.setAntiAlias(true);
-
-        checkedIcon = (VectorDrawable) context.getDrawable(CHECKED_ICON);
-        checkedIcon.setTint(ContextCompat.getColor(context, R.color.colorPrimary));
-        checkedPaint = new Paint();
-        checkedPaint.setColor(ContextCompat.getColor(context, R.color.background));
-        checkedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-        checkedPaint.setAntiAlias(true);
-
-        if (clipPaint != null)
-            for (Paint p : clipPaint)
-                p.setAntiAlias(true);
-        textPaint.setAntiAlias(true);
-        textPaint.setColor(Color.WHITE);
-        textPaint.setTypeface(Typeface.SANS_SERIF);
-    }
-
-    public AvatarDrawable(AvatarDrawable other) {
-        //Log.w("AvatarDrawable", "AvatarDrawable copy");
-        cropCircle = other.cropCircle;
-        isGroup = other.isGroup;
-        minSize = other.minSize;
-        bitmaps = other.bitmaps;
-        backgroundBounds = other.backgroundBounds == null ? null : new ArrayList<>(other.backgroundBounds.size());
-        if (backgroundBounds != null) {
-            for (int i = 0, n = other.backgroundBounds.size(); i < n; i++) {
-                backgroundBounds.add(new RectF());
-            }
-        }
-
-        inBounds = other.inBounds;
-        color = other.color;
-        placeholder = other.placeholder;
-        avatarText = other.avatarText;
-        workspace = new ArrayList<>(other.workspace.size());
-        for (int i = 0, n = other.workspace.size(); i < n; i++) {
-            workspace.add(null);
-        }
-        clipPaint = other.clipPaint == null ? null : new ArrayList<>(other.clipPaint.size());
-        if (clipPaint != null) {
-            for (int i = 0, n = other.clipPaint.size(); i < n; i++) {
-                clipPaint.add(new Paint(other.clipPaint.get(i)));
-                clipPaint.get(i).setShader(null);
-            }
-        }
-
-        isOnline = other.isOnline;
-        isChecked = other.isChecked;
-        showPresence = other.showPresence;
-        presenceFillPaint = other.presenceFillPaint;
-        presenceStrokePaint = other.presenceStrokePaint;
-        checkedPaint = other.checkedPaint;
-
-        textPaint.setAntiAlias(true);
-        textPaint.setColor(Color.WHITE);
-        textPaint.setTypeface(Typeface.SANS_SERIF);
-    }
-
-    @Override
-    public void draw(@NonNull Canvas finalCanvas) {
-        if (workspace.get(0) == null)
-            return;
-        if (update) {
-            for (int i = 0, s = workspace.size(); i < s; i++)
-                drawActual(i, new Canvas(workspace.get(i)));
-            update = false;
-        }
-        if (cropCircle) {
-            finalCanvas.save();
-            finalCanvas.translate((getBounds().width() - workspace.get(0).getWidth())  / 2.f, (getBounds().height() - workspace.get(0).getHeight())  / 2.f);
-            float r = Math.min(workspace.get(0).getWidth(), workspace.get(0).getHeight()) / 2;
-            int cx =  workspace.get(0).getWidth()/2;//getBounds().centerX();
-            float cy = workspace.get(0).getHeight()/2;//getBounds().height() / 2;
-            int i = 0;
-            final float ratio = 1.333333f;
-            for (Paint paint : clipPaint) {
-                finalCanvas.drawCircle(cx, workspace.get(0).getHeight() - cy, r, paint);
-                if (i != 0) {
-                    Shader s = paint.getShader();
-                    paint.setShader(null);
-                    paint.setStyle(Paint.Style.STROKE);
-                    finalCanvas.drawCircle(cx, workspace.get(0).getHeight() - cy, r, paint);
-                    paint.setShader(s);
-                    paint.setStyle(Paint.Style.FILL);
-                }
-                i++;
-                r /= ratio;
-                cy /= ratio;
-            }
-
-            finalCanvas.restore();
-        } else {
-            finalCanvas.drawBitmap(workspace.get(0), null, getBounds(), drawPaint);
-        }
-        if (showPresence && isOnline) {
-            drawPresence(finalCanvas);
-        }
-        if (isChecked) {
-            drawChecked(finalCanvas);
-        }
-    }
-
-    private void drawActual(int i, @NonNull Canvas canvas) {
-        if (bitmaps != null) {
-            if (cropCircle) {
-                canvas.drawBitmap(bitmaps.get(i), inBounds.get(i), backgroundBounds.get(i), drawPaint);
-            } else {
-                if (backgroundBounds.size() == bitmaps.size())
-                    for (int n = 0, s = bitmaps.size(); n < s; n++) {
-                        canvas.drawBitmap(bitmaps.get(n), inBounds.get(n), backgroundBounds.get(n), drawPaint);
-                    }
-            }
-        } else {
-            canvas.drawColor(color);
-            if (avatarText != null) {
-                canvas.drawText(avatarText, textStartXPoint, textStartYPoint, textPaint);
-            } else {
-                placeholder.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN);
-                placeholder.draw(canvas);
-            }
-        }
-    }
-
-    private void setupPresenceIndicator(Rect bounds) {
-        presence.radius = (int) (0.29289321881 * (double) (bounds.width()) * 0.5);
-        presence.cx = bounds.right - presence.radius;
-        presence.cy = bounds.bottom - presence.radius;
-        int presenceStrokeWidth = presence.radius / 3;
-        presenceStrokePaint.setStrokeWidth(presenceStrokeWidth);
-        checkedPaint.setStrokeWidth(presenceStrokeWidth);
-        presence.radius -= presenceStrokeWidth * 0.5;
-
-        if (checkedIcon != null)
-            checkedIcon.setBounds(presence.cx - presence.radius, presence.cy - presence.radius, presence.cx + presence.radius, presence.cy + presence.radius);
-    }
-
-    private void drawPresence(@NonNull Canvas canvas) {
-        canvas.drawCircle(presence.cx, presence.cy, presence.radius - 1, presenceFillPaint);
-        canvas.drawCircle(presence.cx, presence.cy, presence.radius, presenceStrokePaint);
-    }
-
-    private void drawChecked(@NonNull Canvas canvas) {
-        if (checkedIcon != null) {
-            canvas.drawCircle(presence.cx, presence.cy, presence.radius, checkedPaint);
-            checkedIcon.draw(canvas);
-        }
-    }
-
-    private static Rect getSubBounds(@NonNull Rect bounds, int total, int i) {
-        if (total == 1)
-            return bounds;
-
-        if (total == 2 || (total == 3 && i == 0)) {
-            //Rect zone = getSubZone(bounds, 2, 1);
-            int w = bounds.width() / 2;
-            return (i == 0)
-                    ? new Rect(bounds.left, bounds.top, bounds.left + w, bounds.bottom)
-                    : new Rect(bounds.left + w, bounds.top, bounds.right, bounds.bottom);
-        }
-        if (total == 3 || (total == 4 && (i == 1 || i == 2))) {
-            int w = bounds.width() / 2;
-            int h = bounds.height() / 2;
-            return (i == 1)
-                    ? new Rect(bounds.left + w, bounds.top, bounds.right, bounds.top + h)
-                    : new Rect(bounds.left + w, bounds.top + h, bounds.right, bounds.bottom);
-        }
-        if (total == 4) {
-            int w = bounds.width() / 2;
-            int h = bounds.height() / 2;
-            return (i == 0)
-                    ? new Rect(bounds.left, bounds.top, bounds.left + w, bounds.top + h)
-                    : new Rect(bounds.left, bounds.top + h, bounds.left + w, bounds.bottom);
-        }
-        return null;
-    }
-
-    private static <T> void fit(int iw, int ih, int bw, int bh, boolean outfit, T ret) {
-        int a = bw * ih;
-        int b = bh * iw;
-        int w;
-        int h;
-        if (outfit == (a < b)) {
-            w = iw;
-            h = (iw * bh) / bw;
-        } else {
-            w = (ih * bw) / bh;
-            h = ih;
-        }
-        int x = (iw - w) / 2;
-        int y = (ih - h) / 2;
-        if (ret instanceof Rect)
-            ((Rect) ret).set(x, y, x + w, y + h);
-        else if (ret instanceof RectF)
-            ((RectF) ret).set(x, y, x + w, y + h);
-    }
-
-    @Override
-    protected void onBoundsChange(Rect bounds) {
-        //if (showPresence)
-        setupPresenceIndicator(bounds);
-        int d = Math.min(bounds.width(), bounds.height());
-        if (placeholder != null) {
-            int cx = (bounds.width() - d) / 2;
-            int cy = (bounds.height() - d) / 2;
-            placeholder.setBounds(cx, cy, cx + d, cy + d);
-        }
-        int iw = cropCircle ? d : bounds.width();
-        int ih = cropCircle ? d : bounds.height();
-        for (int i = 0, n = workspace.size(); i < n; i++) {
-            if (workspace.get(i) != null) {
-                workspace.get(i).recycle();
-                workspace.set(i, null);
-                clipPaint.get(i).setShader(null);
-            }
-        }
-        if (iw <= 0 || ih <= 0) {
-            return;
-        }
-        if (cropCircle) {
-            for (int i = 0, s = workspace.size(); i < s; i++) {
-                Bitmap workspacei = Bitmap.createBitmap(iw, ih, Bitmap.Config.ARGB_8888);
-                workspace.set(i, workspacei);
-                clipPaint.get(i).setShader(new BitmapShader(workspacei, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
-            }
-        } else {
-            workspace.set(0, Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888));
-        }
-
-        if (bitmaps != null) {
-            if (bitmaps.size() == 1 || cropCircle) {
-                for (int i = 0; i < bitmaps.size(); i++) {
-                    Bitmap bitmap = bitmaps.get(i);
-                    fit(iw, ih, bitmap.getWidth(), bitmap.getHeight(), true, backgroundBounds.get(i));
-                }
-            } else {
-                Rect realBounds = cropCircle ? new Rect(0, 0, iw, ih) : bounds;
-                for (int i = 0; i < bitmaps.size(); i++) {
-                    Bitmap bitmap = bitmaps.get(i);
-                    Rect subBounds = getSubBounds(realBounds, bitmaps.size(), i);
-                    if (subBounds != null) {
-                        fit(bitmap.getWidth(), bitmap.getHeight(), subBounds.width(), subBounds.height(), false, inBounds.get(i));
-                        backgroundBounds.get(i).set(subBounds);
-                    }
-                }
-            }
-        } else {
-            setAvatarTextValues(bounds);
-        }
-        update = true;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        if (placeholder != null) {
-            placeholder.setAlpha(alpha);
-        } else {
-            textPaint.setAlpha(alpha);
-        }
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter colorFilter) {
-        if (placeholder != null) {
-            placeholder.setColorFilter(colorFilter);
-        } else {
-            textPaint.setColorFilter(colorFilter);
-        }
-    }
-
-    @Override
-    public int getMinimumWidth() {
-        return minSize;
-    }
-
-    @Override
-    public int getMinimumHeight() {
-        return minSize;
-    }
-
-    public void setInSize(int s) {
-        inSize = s;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return inSize;
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return inSize;
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    private void setAvatarTextValues(Rect bounds) {
-        if (avatarText != null) {
-            textPaint.setTextSize(bounds.height() * DEFAULT_TEXT_SIZE_PERCENTAGE);
-            float stringWidth = textPaint.measureText(avatarText);
-            textStartXPoint = (bounds.width() / 2f) - (stringWidth / 2f);
-            textStartYPoint = (bounds.height() / 2f) - ((textPaint.ascent() + textPaint.descent()) / 2f);
-        }
-    }
-
-    private String convertNameToAvatarText(String name) {
-        if (TextUtils.isEmpty(name)) {
-            return null;
-        } else {
-            return new String(Character.toChars(name.codePointAt(0))).toUpperCase();
-        }
-    }
-
-    private static int getAvatarColor(String id) {
-        if (id == null) {
-            return R.color.grey_500;
-        }
-
-        String md5 = HashUtils.md5(id);
-        if (md5 == null) {
-            return R.color.grey_500;
-        }
-        int colorIndex = Integer.parseInt(md5.charAt(0) + "", 16);
-        return contactColors[colorIndex % contactColors.length];
-    }
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
new file mode 100644
index 000000000..2e143fc69
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt
@@ -0,0 +1,695 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.views
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.VectorDrawable
+import android.text.TextUtils
+import android.util.TypedValue
+import androidx.core.content.ContextCompat
+import cx.ring.R
+import cx.ring.services.VCardServiceImpl
+import cx.ring.utils.DeviceUtils.isTv
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import net.jami.model.Account
+import net.jami.model.Contact
+import net.jami.model.Conversation
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.HashUtils
+import net.jami.utils.Tuple
+import java.util.*
+
+class AvatarDrawable : Drawable {
+    private class PresenceIndicatorInfo {
+        var cx = 0
+        var cy = 0
+        var radius = 0
+    }
+
+    private val presence = PresenceIndicatorInfo()
+    private var update = true
+    private val isGroup: Boolean
+    private var inSize = -1
+    private val minSize: Int
+    private val workspace: MutableList<Bitmap?>
+    private val bitmaps: MutableList<Bitmap>?
+    private var placeholder: VectorDrawable? = null
+    private var checkedIcon: VectorDrawable? = null
+    private val backgroundBounds: List<RectF>?
+    private val inBounds: List<Rect?>?
+    private var avatarText: String? = null
+    private var textStartXPoint = 0f
+    private var textStartYPoint = 0f
+    private var color = 0
+    private val clipPaint: MutableList<Paint>?
+    private val textPaint = Paint()
+    private val presenceFillPaint: Paint
+    private val presenceStrokePaint: Paint
+    private val checkedPaint: Paint
+
+    companion object {
+        private val TAG = AvatarDrawable::class.simpleName!!
+        private const val SIZE_AB = 36
+        private const val SIZE_BORDER = 2
+        private const val DEFAULT_TEXT_SIZE_PERCENTAGE = 0.5f
+        private const val PLACEHOLDER_ICON = R.drawable.baseline_account_crop_24
+        private const val PLACEHOLDER_ICON_GROUP = R.drawable.baseline_group_24
+        private const val CHECKED_ICON = R.drawable.baseline_check_circle_24
+        private const val PRESENCE_COLOR = R.color.green_A700
+        private val contactColors = intArrayOf(
+            R.color.red_500, R.color.pink_500,
+            R.color.purple_500, R.color.deep_purple_500,
+            R.color.indigo_500, R.color.blue_500,
+            R.color.cyan_500, R.color.teal_500,
+            R.color.green_500, R.color.light_green_500,
+            R.color.grey_500, R.color.lime_500,
+            R.color.amber_500, R.color.deep_orange_500,
+            R.color.brown_500, R.color.blue_grey_500
+        )
+        private val drawPaint = Paint()
+
+        fun load(context: Context, account: Account, crop: Boolean = true): Observable<AvatarDrawable> {
+            return VCardServiceImpl.loadProfile(context, account)
+                .map { profile -> build(context, account, profile, crop) }
+        }
+        fun build(context: Context, account: Account, profile: Tuple<String?, Any?>, crop: Boolean): AvatarDrawable {
+            return Builder()
+                        .withPhoto(profile.second as Bitmap?)
+                        .withNameData(profile.first, account.registeredName)
+                        .withId(account.uri)
+                        .withCircleCrop(crop)
+                        .build(context)
+        }
+
+        private fun getSubBounds(bounds: Rect, total: Int, i: Int): Rect? {
+            if (total == 1) return bounds
+            if (total == 2 || total == 3 && i == 0) {
+                //Rect zone = getSubZone(bounds, 2, 1);
+                val w = bounds.width() / 2
+                return if (i == 0)
+                    Rect(bounds.left, bounds.top, bounds.left + w, bounds.bottom)
+                else
+                    Rect(bounds.left + w, bounds.top, bounds.right, bounds.bottom)
+            }
+            if (total == 3 || total == 4 && (i == 1 || i == 2)) {
+                val w = bounds.width() / 2
+                val h = bounds.height() / 2
+                return if (i == 1)
+                    Rect(bounds.left + w, bounds.top, bounds.right, bounds.top + h)
+                else
+                    Rect(bounds.left + w, bounds.top + h, bounds.right, bounds.bottom)
+            }
+            if (total == 4) {
+                val w = bounds.width() / 2
+                val h = bounds.height() / 2
+                return if (i == 0)
+                    Rect(bounds.left, bounds.top, bounds.left + w, bounds.top + h)
+                else
+                    Rect(bounds.left, bounds.top + h, bounds.left + w, bounds.bottom)
+            }
+            return null
+        }
+
+        private fun <T> fit(iw: Int, ih: Int, bw: Int, bh: Int, outfit: Boolean, ret: T) {
+            val a = bw * ih
+            val b = bh * iw
+            val w: Int
+            val h: Int
+            if (outfit == a < b) {
+                w = iw
+                h = iw * bh / bw
+            } else {
+                w = ih * bw / bh
+                h = ih
+            }
+            val x = (iw - w) / 2
+            val y = (ih - h) / 2
+            if (ret is Rect) ret[x, y, x + w] =
+                y + h else if (ret is RectF) ret[x.toFloat(), y.toFloat(), (x + w).toFloat()] =
+                (y + h).toFloat()
+        }
+
+        private fun getAvatarColor(id: String?): Int {
+            if (id == null) {
+                return R.color.grey_500
+            }
+            val md5 = HashUtils.md5(id) ?: return R.color.grey_500
+            val colorIndex = (md5[0].toString() + "").toInt(16)
+            return contactColors[colorIndex % contactColors.size]
+        }
+
+        init {
+            drawPaint.isAntiAlias = true
+            drawPaint.isFilterBitmap = true
+        }
+    }
+
+    private val cropCircle: Boolean
+    private var isOnline = false
+    private var isChecked = false
+    private var showPresence = false
+
+    class Builder {
+        private var photos: MutableList<Bitmap>? = null
+        private var name: String? = null
+        private var id: String? = null
+        private var circleCrop = false
+        private var isOnline = false
+        private var showPresence = true
+        private var isChecked = false
+        private var isGroup = false
+        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
+            return this
+        }
+
+        fun withPhotos(photos: MutableList<Bitmap>): Builder {
+            this.photos = if (photos.isEmpty()) null else photos
+            return this
+        }
+
+        fun withName(name: String?): Builder {
+            this.name = name
+            return this
+        }
+
+        fun withCircleCrop(crop: Boolean): Builder {
+            circleCrop = crop
+            return this
+        }
+
+        fun withOnlineState(isOnline: Boolean): Builder {
+            this.isOnline = isOnline
+            return this
+        }
+
+        fun withPresence(showPresence: Boolean): Builder {
+            this.showPresence = showPresence
+            return this
+        }
+
+        fun withCheck(checked: Boolean): Builder {
+            isChecked = checked
+            return this
+        }
+
+        fun withNameData(profileName: String?, username: String?): Builder {
+            return withName(if (TextUtils.isEmpty(profileName)) username else profileName)
+        }
+
+        fun withContact(contact: Contact?): Builder {
+            return if (contact == null) this else withPhoto(contact.photo as Bitmap?)
+                .withId(contact.primaryNumber)
+                .withOnlineState(contact.isOnline)
+                .withNameData(contact.profileName, contact.username)
+        }
+
+        fun withContacts(contacts: List<Contact>): Builder {
+            val bitmaps: MutableList<Bitmap> = ArrayList(contacts.size)
+            var notTheUser = 0
+            for (contact in contacts) {
+                if (contact.isUser) continue
+                notTheUser++
+                val bitmap = contact.photo as Bitmap?
+                if (bitmap != null) {
+                    bitmaps.add(bitmap)
+                }
+                if (bitmaps.size == 4) break
+            }
+            if (notTheUser == 1) {
+                for (contact in contacts) {
+                    if (!contact.isUser) return withContact(contact)
+                }
+            }
+            if (bitmaps.isEmpty()) {
+                // Fallback to the user avatar
+                for (contact in contacts) return withContact(contact)
+            } else {
+                return withPhotos(bitmaps)
+            }
+            return this
+        }
+
+        fun withConversation(conversation: Conversation): Builder {
+            return if (conversation.isSwarm) withContacts(conversation.contacts).setGroup()
+            else withContact(conversation.contact)
+        }
+
+        private fun setGroup(): Builder {
+            isGroup = true
+            return this
+        }
+
+        fun withViewModel(vm: SmartListViewModel): Builder {
+            val isSwarm = vm.uri.isSwarm
+            return (if (isSwarm) withContacts(vm.contacts).setGroup() else withContact(if (vm.contacts.isEmpty()) null else vm.contacts[vm.contacts.size - 1]))
+                .withPresence(vm.showPresence())
+                .withOnlineState(vm.isOnline)
+                .withCheck(vm.isChecked)
+        }
+
+        fun build(context: Context): AvatarDrawable {
+            val avatarDrawable = AvatarDrawable(
+                context, photos, name, id, circleCrop, isGroup
+            )
+            avatarDrawable.setOnline(isOnline)
+            avatarDrawable.setChecked(isChecked)
+            avatarDrawable.showPresence = showPresence
+            return avatarDrawable
+        }
+
+        fun buildAsync(context: Context): Single<AvatarDrawable> {
+            return Single.fromCallable { build(context) }
+        }
+    }
+
+    fun update(contact: Contact) {
+        val profileName = contact.profileName
+        val username = contact.username
+        avatarText = convertNameToAvatarText(
+            if (TextUtils.isEmpty(profileName)) username else profileName
+        )
+        bitmaps?.set(0, contact.photo as Bitmap)
+        isOnline = contact.isOnline
+        update = true
+    }
+
+    fun setName(name: String?) {
+        avatarText = convertNameToAvatarText(name)
+        update = true
+    }
+
+    fun setPhoto(photo: Bitmap) {
+        bitmaps!![0] = photo
+        update = true
+    }
+
+    fun setOnline(online: Boolean) {
+        isOnline = online
+    }
+
+    fun setChecked(checked: Boolean) {
+        isChecked = checked
+    }
+
+    private constructor(
+        context: Context,
+        photos: MutableList<Bitmap>?,
+        name: String?,
+        id: String?,
+        crop: Boolean,
+        group: Boolean
+    ) {
+        //Log.w("AvatarDrawable", "AvatarDrawable " + (photos == null ? null : photos.size()) + " " + name);
+        cropCircle = crop
+        isGroup = group
+        minSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SIZE_AB.toFloat(), context.resources.displayMetrics).toInt()
+        val borderSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SIZE_BORDER.toFloat(), context.resources.displayMetrics)
+        if (cropCircle) {
+            inSize = minSize
+        }
+        if (photos != null && photos.size > 0) {
+            avatarText = null
+            bitmaps = photos
+            if (photos.size == 1) {
+                backgroundBounds = listOf(RectF())
+                inBounds = listOf<Rect?>(null)
+                clipPaint = if (cropCircle) mutableListOf(Paint()) else null
+                workspace = mutableListOf(null as Bitmap?)
+            } else {
+                backgroundBounds = ArrayList(bitmaps.size)
+                inBounds = ArrayList(bitmaps.size)
+                clipPaint = if (cropCircle) ArrayList(bitmaps.size) else null
+                workspace =
+                    if (cropCircle) ArrayList(bitmaps.size) else Arrays.asList(null as Bitmap?)
+                for (ignored in bitmaps) {
+                    backgroundBounds.add(RectF())
+                    inBounds.add(if (cropCircle) null else Rect())
+                    if (cropCircle) {
+                        val p = Paint()
+                        p.strokeWidth = borderSize
+                        p.color = Color.WHITE
+                        p.style = Paint.Style.FILL
+                        clipPaint!!.add(p)
+                        workspace.add(null)
+                    }
+                }
+            }
+        } else {
+            workspace = mutableListOf(null as Bitmap?)
+            bitmaps = null
+            backgroundBounds = null
+            inBounds = null
+            avatarText = convertNameToAvatarText(name)
+            color = ContextCompat.getColor(context, getAvatarColor(id))
+            clipPaint = if (cropCircle) mutableListOf(Paint()) else null
+            if (avatarText == null) {
+                placeholder =
+                    context.getDrawable(if (isGroup) PLACEHOLDER_ICON_GROUP else PLACEHOLDER_ICON) as VectorDrawable?
+            } else {
+                textPaint.color = Color.WHITE
+                textPaint.typeface = Typeface.SANS_SERIF
+            }
+        }
+        presenceFillPaint = Paint()
+        presenceFillPaint.color = ContextCompat.getColor(context, PRESENCE_COLOR)
+        presenceFillPaint.style = Paint.Style.FILL
+        presenceFillPaint.isAntiAlias = true
+        presenceFillPaint.color = ContextCompat.getColor(context, PRESENCE_COLOR)
+        presenceFillPaint.style = Paint.Style.FILL
+        presenceFillPaint.isAntiAlias = true
+        presenceStrokePaint = Paint()
+        presenceStrokePaint.color = ContextCompat.getColor(
+            context,
+            if (isTv(context)) R.color.grey_900 else R.color.background
+        )
+        presenceStrokePaint.style = Paint.Style.STROKE
+        presenceStrokePaint.isAntiAlias = true
+        checkedIcon = context.getDrawable(CHECKED_ICON) as VectorDrawable?
+        checkedIcon!!.setTint(ContextCompat.getColor(context, R.color.colorPrimary))
+        checkedPaint = Paint()
+        checkedPaint.color = ContextCompat.getColor(context, R.color.background)
+        checkedPaint.style = Paint.Style.FILL_AND_STROKE
+        checkedPaint.isAntiAlias = true
+        if (clipPaint != null) for (p in clipPaint) p.isAntiAlias = true
+        textPaint.isAntiAlias = true
+        textPaint.color = Color.WHITE
+        textPaint.typeface = Typeface.SANS_SERIF
+    }
+
+    constructor(other: AvatarDrawable) {
+        //Log.w("AvatarDrawable", "AvatarDrawable copy");
+        cropCircle = other.cropCircle
+        isGroup = other.isGroup
+        minSize = other.minSize
+        bitmaps = other.bitmaps
+        if (other.backgroundBounds != null) {
+            backgroundBounds = ArrayList(other.backgroundBounds.size)
+            var i = 0
+            val n = other.backgroundBounds.size
+            while (i < n) {
+                backgroundBounds.add(RectF())
+                i++
+            }
+        } else {
+            backgroundBounds = null
+        }
+        inBounds = other.inBounds
+        color = other.color
+        placeholder = other.placeholder
+        avatarText = other.avatarText
+        workspace = ArrayList(other.workspace.size)
+        var i = 0
+        val n = other.workspace.size
+        while (i < n) {
+            workspace.add(null)
+            i++
+        }
+        clipPaint = if (other.clipPaint == null) null else ArrayList(other.clipPaint.size)
+        if (clipPaint != null) {
+            i = 0
+            val n = other.clipPaint!!.size
+            while (i < n) {
+                clipPaint.add(Paint(other.clipPaint[i]))
+                clipPaint[i].shader = null
+                i++
+            }
+        }
+        isOnline = other.isOnline
+        isChecked = other.isChecked
+        showPresence = other.showPresence
+        presenceFillPaint = other.presenceFillPaint
+        presenceStrokePaint = other.presenceStrokePaint
+        checkedPaint = other.checkedPaint
+        textPaint.isAntiAlias = true
+        textPaint.color = Color.WHITE
+        textPaint.typeface = Typeface.SANS_SERIF
+    }
+
+    override fun draw(finalCanvas: Canvas) {
+        if (workspace[0] == null) return
+        if (update) {
+            var i = 0
+            val s = workspace.size
+            while (i < s) {
+                drawActual(i, Canvas(workspace[i]!!))
+                i++
+            }
+            update = false
+        }
+        if (cropCircle) {
+            finalCanvas.save()
+            finalCanvas.translate(
+                (bounds.width() - workspace[0]!!
+                    .width) / 2f, (bounds.height() - workspace[0]!!.height) / 2f
+            )
+            var r = (Math.min(
+                workspace[0]!!.width, workspace[0]!!.height
+            ) / 2).toFloat()
+            val cx = workspace[0]!!.width / 2 //getBounds().centerX();
+            var cy = (workspace[0]!!.height / 2).toFloat() //getBounds().height() / 2;
+            var i = 0
+            val ratio = 1.333333f
+            for (paint in clipPaint!!) {
+                finalCanvas.drawCircle(cx.toFloat(), workspace[0]!!.height - cy, r, paint)
+                if (i != 0) {
+                    val s = paint.shader
+                    paint.shader = null
+                    paint.style = Paint.Style.STROKE
+                    finalCanvas.drawCircle(cx.toFloat(), workspace[0]!!.height - cy, r, paint)
+                    paint.shader = s
+                    paint.style = Paint.Style.FILL
+                }
+                i++
+                r /= ratio
+                cy /= ratio
+            }
+            finalCanvas.restore()
+        } else {
+            finalCanvas.drawBitmap(workspace[0]!!, null, bounds, drawPaint)
+        }
+        if (showPresence && isOnline) {
+            drawPresence(finalCanvas)
+        }
+        if (isChecked) {
+            drawChecked(finalCanvas)
+        }
+    }
+
+    private fun drawActual(i: Int, canvas: Canvas) {
+        if (bitmaps != null) {
+            if (cropCircle) {
+                canvas.drawBitmap(bitmaps[i], inBounds!![i], backgroundBounds!![i], drawPaint)
+            } else {
+                if (backgroundBounds!!.size == bitmaps.size) {
+                    var n = 0
+                    val s = bitmaps.size
+                    while (n < s) {
+                        canvas.drawBitmap(bitmaps[n], inBounds!![n], backgroundBounds[n], drawPaint)
+                        n++
+                    }
+                }
+            }
+        } else {
+            canvas.drawColor(color)
+            if (avatarText != null) {
+                canvas.drawText(avatarText!!, textStartXPoint, textStartYPoint, textPaint)
+            } else {
+                placeholder!!.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)
+                placeholder!!.draw(canvas)
+            }
+        }
+    }
+
+    private fun setupPresenceIndicator(bounds: Rect) {
+        presence.radius = (0.29289321881 * bounds.width().toDouble() * 0.5).toInt()
+        presence.cx = bounds.right - presence.radius
+        presence.cy = bounds.bottom - presence.radius
+        val presenceStrokeWidth = presence.radius / 3
+        presenceStrokePaint.strokeWidth = presenceStrokeWidth.toFloat()
+        checkedPaint.strokeWidth = presenceStrokeWidth.toFloat()
+        presence.radius -= (presenceStrokeWidth * 0.5).toInt()
+        if (checkedIcon != null) checkedIcon!!.setBounds(
+            presence.cx - presence.radius,
+            presence.cy - presence.radius,
+            presence.cx + presence.radius,
+            presence.cy + presence.radius
+        )
+    }
+
+    private fun drawPresence(canvas: Canvas) {
+        canvas.drawCircle(
+            presence.cx.toFloat(),
+            presence.cy.toFloat(),
+            (presence.radius - 1).toFloat(),
+            presenceFillPaint
+        )
+        canvas.drawCircle(
+            presence.cx.toFloat(),
+            presence.cy.toFloat(),
+            presence.radius.toFloat(),
+            presenceStrokePaint
+        )
+    }
+
+    private fun drawChecked(canvas: Canvas) {
+        if (checkedIcon != null) {
+            canvas.drawCircle(
+                presence.cx.toFloat(),
+                presence.cy.toFloat(),
+                presence.radius.toFloat(),
+                checkedPaint
+            )
+            checkedIcon!!.draw(canvas)
+        }
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        //if (showPresence)
+        setupPresenceIndicator(bounds)
+        val d = Math.min(bounds.width(), bounds.height())
+        if (placeholder != null) {
+            val cx = (bounds.width() - d) / 2
+            val cy = (bounds.height() - d) / 2
+            placeholder!!.setBounds(cx, cy, cx + d, cy + d)
+        }
+        val iw = if (cropCircle) d else bounds.width()
+        val ih = if (cropCircle) d else bounds.height()
+        var i = 0
+        val n = workspace.size
+        while (i < n) {
+            if (workspace[i] != null) {
+                workspace[i]!!.recycle()
+                workspace[i] = null
+                clipPaint!![i].shader = null
+            }
+            i++
+        }
+        if (iw <= 0 || ih <= 0) {
+            return
+        }
+        if (cropCircle) {
+            i = 0
+            val s = workspace.size
+            while (i < s) {
+                val workspacei = Bitmap.createBitmap(iw, ih, Bitmap.Config.ARGB_8888)
+                workspace[i] = workspacei
+                clipPaint!![i].shader = BitmapShader(workspacei, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+                i++
+            }
+        } else {
+            workspace[0] =
+                Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888)
+        }
+        if (bitmaps != null) {
+            if (bitmaps.size == 1 || cropCircle) {
+                for (i in bitmaps.indices) {
+                    val bitmap = bitmaps[i]
+                    fit(iw, ih, bitmap.width, bitmap.height, true, backgroundBounds!![i])
+                }
+            } else {
+                val realBounds = if (cropCircle) Rect(0, 0, iw, ih) else bounds
+                for (i in bitmaps.indices) {
+                    val bitmap = bitmaps[i]
+                    val subBounds = getSubBounds(realBounds, bitmaps.size, i)
+                    if (subBounds != null) {
+                        fit(
+                            bitmap.width,
+                            bitmap.height,
+                            subBounds.width(),
+                            subBounds.height(),
+                            false,
+                            inBounds!![i]
+                        )
+                        backgroundBounds!![i].set(subBounds)
+                    }
+                }
+            }
+        } else {
+            setAvatarTextValues(bounds)
+        }
+        update = true
+    }
+
+    override fun setAlpha(alpha: Int) {
+        if (placeholder != null) {
+            placeholder!!.alpha = alpha
+        } else {
+            textPaint.alpha = alpha
+        }
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        if (placeholder != null) {
+            placeholder!!.colorFilter = colorFilter
+        } else {
+            textPaint.colorFilter = colorFilter
+        }
+    }
+
+    override fun getMinimumWidth(): Int {
+        return minSize
+    }
+
+    override fun getMinimumHeight(): Int {
+        return minSize
+    }
+
+    fun setInSize(s: Int) {
+        inSize = s
+    }
+
+    override fun getIntrinsicWidth(): Int {
+        return inSize
+    }
+
+    override fun getIntrinsicHeight(): Int {
+        return inSize
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.TRANSLUCENT
+    }
+
+    private fun setAvatarTextValues(bounds: Rect) {
+        if (avatarText != null) {
+            textPaint.textSize = bounds.height() * DEFAULT_TEXT_SIZE_PERCENTAGE
+            val stringWidth = textPaint.measureText(avatarText)
+            textStartXPoint = bounds.width() / 2f - stringWidth / 2f
+            textStartYPoint = bounds.height() / 2f - (textPaint.ascent() + textPaint.descent()) / 2f
+        }
+    }
+
+    private fun convertNameToAvatarText(name: String?): String? {
+        return if (name == null || name.isEmpty()) {
+            null
+        } else {
+            String(Character.toChars(name.codePointAt(0))).uppercase(Locale.getDefault())
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.java b/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.java
deleted file mode 100644
index 59a0e59dc..000000000
--- a/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
- * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package cx.ring.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-
-import android.text.TextUtils;
-import android.widget.ImageView;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.RequestBuilder;
-import com.bumptech.glide.RequestManager;
-
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.smartlist.SmartListViewModel;
-import cx.ring.utils.BitmapUtils;
-import cx.ring.views.AvatarDrawable;
-import io.reactivex.rxjava3.core.Single;
-
-public class AvatarFactory {
-
-    public static final int SIZE_AB = 36;
-    public static final int SIZE_NOTIF = 48;
-    public static final int SIZE_PADDING = 8;
-
-    private AvatarFactory() {}
-
-    public static void loadGlideAvatar(ImageView view, Contact contact) {
-        getGlideAvatar(view.getContext(), contact).into(view);
-    }
-
-    public static Single<Drawable> getAvatar(Context context, Contact contact, boolean presence) {
-        return Single.fromCallable(() ->
-                new AvatarDrawable.Builder()
-                        .withContact(contact)
-                        .withCircleCrop(true)
-                        .withPresence(presence)
-                        .build(context));
-    }
-    public static Single<Drawable> getAvatar(Context context, Conversation conversation, boolean presence) {
-        return Single.fromCallable(() ->
-                new AvatarDrawable.Builder()
-                        .withConversation(conversation)
-                        .withCircleCrop(true)
-                        .withPresence(presence)
-                        .build(context));
-    }
-    public static Single<Drawable> getAvatar(Context context, SmartListViewModel vm) {
-        return Single.fromCallable(() ->
-                new AvatarDrawable.Builder()
-                        .withViewModel(vm)
-                        .withCircleCrop(true)
-                        .build(context));
-    }
-    public static Single<Drawable> getAvatar(Context context, Contact contact) {
-        return getAvatar(context, contact, true);
-    }
-    public static Single<Bitmap> getBitmapAvatar(Context context, Conversation conversation, int size, boolean presence) {
-        return getAvatar(context, conversation, presence)
-                .map(d -> BitmapUtils.drawableToBitmap(d, size));
-    }
-    public static Single<Bitmap> getBitmapAvatar(Context context, Contact contact, int size, boolean presence) {
-        return getAvatar(context, contact, presence)
-                .map(d -> BitmapUtils.drawableToBitmap(d, size));
-    }
-    public static Single<Bitmap> getBitmapAvatar(Context context, Contact contact, int size) {
-        return getBitmapAvatar(context, contact, size, true);
-    }
-
-    public static Single<Bitmap> getBitmapAvatar(Context context, Account account, int size) {
-        return AvatarDrawable.load(context, account)
-                .map(d -> BitmapUtils.drawableToBitmap(d, size));
-    }
-
-    private static Drawable getDrawable(Context context, Bitmap photo, String profileName, String username, String id) {
-        return new AvatarDrawable.Builder()
-                .withPhoto(photo)
-                .withName(TextUtils.isEmpty(profileName) ? username : profileName)
-                .withId(id)
-                .withCircleCrop(true)
-                .build(context);
-    }
-
-    public static void clearCache() {
-    }
-
-    private static <T> RequestBuilder<T> getGlideRequest(Context context, RequestBuilder<T> request, Bitmap photo, String profileName, String username, String id) {
-        return request.load(getDrawable(context, photo, profileName, username, id));
-    }
-
-    private static RequestBuilder<Drawable> getGlideAvatar(Context context, RequestManager manager, Contact contact) {
-        return getGlideRequest(context, manager.asDrawable(), (Bitmap)contact.getPhoto(), contact.getProfileName(), contact.getUsername(), contact.getPrimaryNumber());
-    }
-
-    private static RequestBuilder<Drawable> getGlideAvatar(Context context, Contact contact) {
-        return getGlideAvatar(context, Glide.with(context), contact);
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.kt b/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.kt
new file mode 100644
index 000000000..c5c2156bc
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/views/AvatarFactory.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
+ * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ * 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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package cx.ring.views
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.text.TextUtils
+import android.widget.ImageView
+import com.bumptech.glide.Glide
+import com.bumptech.glide.RequestBuilder
+import com.bumptech.glide.RequestManager
+import cx.ring.utils.BitmapUtils
+import io.reactivex.rxjava3.core.Single
+import net.jami.model.Account
+import net.jami.model.Contact
+import net.jami.model.Conversation
+import net.jami.smartlist.SmartListViewModel
+
+object AvatarFactory {
+    const val SIZE_AB = 36
+    const val SIZE_NOTIF = 48
+    const val SIZE_PADDING = 8
+    fun loadGlideAvatar(view: ImageView, contact: Contact) {
+        getGlideAvatar(view.context, contact).into(view)
+    }
+
+    fun getAvatar(context: Context, contact: Contact?, presence: Boolean): Single<Drawable> {
+        return Single.fromCallable {
+            AvatarDrawable.Builder()
+                .withContact(contact)
+                .withCircleCrop(true)
+                .withPresence(presence)
+                .build(context)
+        }
+    }
+
+    fun getAvatar(context: Context, conversation: Conversation, presence: Boolean): Single<Drawable> {
+        return Single.fromCallable {
+            AvatarDrawable.Builder()
+                .withConversation(conversation)
+                .withCircleCrop(true)
+                .withPresence(presence)
+                .build(context)
+        }
+    }
+
+    fun getAvatar(context: Context, vm: SmartListViewModel): Single<Drawable> {
+        return Single.fromCallable {
+            AvatarDrawable.Builder()
+                .withViewModel(vm)
+                .withCircleCrop(true)
+                .build(context)
+        }
+    }
+
+    fun getAvatar(context: Context, contact: Contact?): Single<Drawable> {
+        return getAvatar(context, contact, true)
+    }
+
+    fun getBitmapAvatar(context: Context, conversation: Conversation, size: Int, presence: Boolean): Single<Bitmap> {
+        return getAvatar(context, conversation, presence)
+            .map { d -> BitmapUtils.drawableToBitmap(d, size) }
+    }
+
+    fun getBitmapAvatar(context: Context, contact: Contact?, size: Int, presence: Boolean): Single<Bitmap> {
+        return getAvatar(context, contact, presence)
+            .map { d -> BitmapUtils.drawableToBitmap(d, size) }
+    }
+
+    fun getBitmapAvatar(context: Context, contact: Contact?, size: Int): Single<Bitmap> {
+        return getBitmapAvatar(context, contact, size, true)
+    }
+
+    fun getBitmapAvatar(context: Context, account: Account, size: Int): Single<Bitmap> {
+        return AvatarDrawable.load(context, account)
+            .firstOrError()
+            .map { d -> BitmapUtils.drawableToBitmap(d, size) }
+    }
+
+    private fun getDrawable(context: Context, photo: Bitmap, profileName: String?, username: String?, id: String): Drawable {
+        return AvatarDrawable.Builder()
+            .withPhoto(photo)
+            .withName(if (TextUtils.isEmpty(profileName)) username else profileName)
+            .withId(id)
+            .withCircleCrop(true)
+            .build(context)
+    }
+
+    fun clearCache() {}
+
+    private fun <T> getGlideRequest(context: Context, request: RequestBuilder<T>, photo: Bitmap, profileName: String?, username: String?, id: String): RequestBuilder<T> {
+        return request.load(getDrawable(context, photo, profileName, username, id))
+    }
+
+    private fun getGlideAvatar(context: Context, manager: RequestManager, contact: Contact): RequestBuilder<Drawable> {
+        return getGlideRequest(context, manager.asDrawable(), contact.photo as Bitmap, contact.profileName, contact.username, contact.primaryNumber)
+    }
+
+    private fun getGlideAvatar(context: Context, contact: Contact): RequestBuilder<Drawable> {
+        return getGlideAvatar(context, Glide.with(context), contact)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_account.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_account.xml
new file mode 100644
index 000000000..2b1c62186
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_account.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M22,16.7V7.3c0,0 0,0 0,0c0,-0.7 -0.5,-1.3 -1.1,-1.5c-0.2,0 -0.4,-0.1 -0.6,-0.1h-6.2v0.5c0,0.1 0,0.3 0,0.4c0,0.2 0,0.4 -0.1,0.6C14,7.6 13.9,7.8 13.7,8c-0.2,0.4 -0.6,0.6 -1,0.8c-0.6,0.2 -1.4,0.1 -1.9,-0.2c-0.2,-0.2 -0.4,-0.4 -0.5,-0.6C10.1,7.6 10,7.2 10,6.8c0,0 0,-0.1 0,-0.1c0,-0.2 0,-0.3 0,-0.5c0,-0.1 0,-0.2 0,-0.3c0,-0.1 0,-0.1 0,-0.2H5.4c-0.6,0 -1.3,0 -1.9,0C2.7,5.8 2,6.5 2,7.4c0,0.2 0,0.3 0,0.5V17c0,0.4 0,0.9 0,1.3c0,0.9 0.7,1.5 1.6,1.6c0.1,0 0.2,0 0.3,0h16.3c0.1,0 0.2,0 0.4,0c0.8,-0.1 1.4,-0.8 1.4,-1.6C22,17.8 22,17.2 22,16.7zM8.9,16.5c-1.2,0.2 -2.5,-0.3 -3.1,-1.4c-0.3,-0.5 -0.5,-1.1 -0.4,-1.7c0.1,-0.6 0.3,-1.2 0.7,-1.7c0.7,-0.9 2.1,-1.3 3.2,-0.9c1.1,0.4 1.9,1.5 1.9,2.7v0c0,0 0,0 0,0C11.2,15 10.3,16.3 8.9,16.5zM16.9,16.3h-3.7c-0.4,0 -0.7,-0.3 -0.7,-0.7c0,-0.4 0.3,-0.7 0.7,-0.7h3.7c0.4,0 0.7,0.3 0.7,0.7C17.7,16 17.3,16.3 16.9,16.3zM19.1,12.4h-5.9c-0.4,0 -0.7,-0.3 -0.7,-0.7s0.3,-0.7 0.7,-0.7h5.9c0.4,0 0.7,0.3 0.7,0.7C19.9,12.1 19.5,12.4 19.1,12.4z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M13.1,5.8c0,0.3 0,0.6 0,1c0,0 0,0.1 0,0.1c0,0 0,0.1 0,0.1c0,0 0,0 0,0s0,0.1 0,0.2c0,0 0,0.1 0,0.1v0l0,0c0,0.1 -0.1,0.1 -0.1,0.2c0,0 0,0 0,0.1c0,0 0,0 0,0c0,0 0,0 0,0c0,0 0,0 0,0c0,0 -0.1,0.1 -0.1,0.1c0,0 0,0 0,0c0,0 0,0 0,0c0,0 0,0 0,0c0,0 -0.1,0.1 -0.1,0.1c0,0 0,0 -0.1,0c0,0 0,0 0,0c-0.1,0 -0.1,0 -0.2,0c0,0 -0.1,0 -0.1,0c-0.1,0 -0.1,0 -0.2,0c0,0 -0.1,0 -0.1,0l0,0c-0.1,0 -0.1,0 -0.2,0c0,0 -0.1,0 -0.1,0h0c0,0 -0.1,0 -0.1,-0.1c0,0 -0.1,0 -0.1,-0.1c0,0 0,0 0,0l0,0c0,0 -0.1,-0.1 -0.1,-0.1c0,0 0,0 0,0c0,0 0,0 -0.1,-0.1l0,0l0,0c0,0 -0.1,-0.1 -0.1,-0.1c0,0 0,-0.1 0,-0.1c0,0 0,0 0,0c0,0 0,0 0,0c0,0 0,-0.1 0,-0.1c0,0 0,-0.1 0,-0.1c0,0 0,0 0,0c0,0 0,0 0,0c0,0 0,-0.1 0,-0.1c0,0 0,0 0,-0.1V5.4c0,-0.1 0,-0.2 0,-0.2c0,0 0,-0.1 0,-0.1c0,0 0,0 0,0.1c0,0 0,0 0,-0.1c0,-0.1 0,-0.1 0,-0.2c0,0 0,-0.1 0,-0.1c0,0 0,0 0,0c0,-0.1 0.1,-0.1 0.1,-0.2c0,0 0,0 0,0c0,0 0,0 0,0c0.1,-0.1 0.1,-0.1 0.2,-0.2c0,0 0,0 0,0c0,0 0.1,0 0.1,0c0,0 0.2,-0.1 0.2,-0.1c0,0 -0.1,0 -0.1,0c0,0 0,0 0,0c0,0 0.1,0 0.1,0c0,0 0.1,0 0.1,0c0,0 0,0 0.1,0c0,0 0,0 0,0c0,0 0,0 -0.1,0c0,0 0.2,0 0.2,0c0,0 0,0 0.1,0l0,0l0,0c0,0 0,0 0.1,0c0,0 0,0 0,0c0,0 0,0 0,0c0,0 0,0 0.1,0c0.1,0 0.2,0 0.2,0.1l0,0c0,0 0,0 0,0c0,0 0.1,0 0.1,0.1c0,0 0.1,0 0.1,0c0,0 0,0 0,0l0,0c0,0 0,0 0,0c0.1,0.1 0.1,0.1 0.2,0.2l0,0l0,0c0,0 0.1,0.1 0.1,0.1c0,0 0,0.1 0.1,0.1c0,0 0,0 0,0l0,0c0,0 0,0 0,0c0,0.1 0,0.2 0.1,0.2c0,0 0,0 0,0c0,0 0,0 0,0c0,0.1 0,0.1 0,0.2C13.1,5.4 13.1,5.6 13.1,5.8z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_add_user.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_add_user.xml
new file mode 100644
index 000000000..ca51ed764
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_add_user.xml
@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M17.1,12.1c-2.8,0 -4.9,2.2 -4.9,4.9s2.2,4.9 4.9,4.9S22,19.8 22,17S19.8,12.1 17.1,12.1zM18.3,17.6h-0.6v0.6c0,0.4 -0.3,0.6 -0.6,0.6c-0.3,0 -0.6,-0.3 -0.6,-0.6v-0.6h-0.6c-0.3,0 -0.6,-0.3 -0.6,-0.6c0,-0.3 0.3,-0.6 0.6,-0.6h0.6v-0.6c0,-0.3 0.3,-0.6 0.6,-0.6c0.3,0 0.6,0.3 0.6,0.6v0.6h0.6c0.3,0 0.6,0.3 0.6,0.6C19,17.4 18.6,17.6 18.3,17.6z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M6.6,6.2a3.6,4.2 0,1 0,7.2 0a3.6,4.2 0,1 0,-7.2 0z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M11.4,17c0,-2 1.1,-3.8 2.7,-4.8c-1.2,-0.7 -2.5,-1.1 -3.9,-1.1C5.7,11.1 2,15 2,19.7c0,2.4 3.7,2.2 8.2,2.2c1.4,0 2.8,0 4,-0.1C12.5,20.9 11.4,19.1 11.4,17z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_chat.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_chat.xml
new file mode 100644
index 000000000..9a2c3ecf8
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_chat.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M8.3,19.5c-0.2,0 -0.3,0 -0.5,-0.1C7.3,19.2 7,18.7 7,18.2v-2L6.4,16.2c-1.5,0 -2.7,-1.2 -2.7,-2.8L3.7,7.2c0,-1.5 1.2,-2.8 2.7,-2.8h11.1c1.6,0 2.8,1.2 2.8,2.8v6.1c0,1.5 -1.2,2.8 -2.8,2.8L12.4,16.1l-3.2,3C9,19.3 8.7,19.5 8.3,19.5z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_clearconversation.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_clearconversation.xml
new file mode 100644
index 000000000..72e1cc804
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_clearconversation.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M17.1,13.3c-3,0 -5.3,-2.4 -5.3,-5.3c0,-0.9 0.3,-1.8 0.7,-2.6H5.2c-1.5,0 -2.7,1.3 -2.7,2.8v6.2c0,1.6 1.2,2.8 2.7,2.8h0.6v2c0,0.5 0.3,1 0.8,1.2c0.2,0.1 0.3,0.1 0.5,0.1c0.4,0 0.7,-0.2 0.9,-0.4l3.2,-3h5.1c1.6,0 2.8,-1.3 2.8,-2.8v-1.4C18.5,13.1 17.8,13.3 17.1,13.3z"/>
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M17.1,3.5c-2.5,0 -4.4,2 -4.4,4.4s2,4.4 4.4,4.4s4.4,-1.9 4.4,-4.4S19.5,3.5 17.1,3.5zM17.5,9l-0.4,-0.4L16.7,9c-0.3,0.3 -0.6,0.2 -0.8,0c-0.2,-0.2 -0.2,-0.6 0,-0.8l0.4,-0.4L16,7.5c-0.2,-0.2 -0.2,-0.6 0,-0.8c0.2,-0.2 0.6,-0.2 0.8,0l0.4,0.4l0.4,-0.4c0.2,-0.2 0.6,-0.2 0.8,0c0.2,0.2 0.2,0.6 0,0.8l-0.4,0.4l0.4,0.4c0.2,0.2 0.2,0.6 0,0.8C18.1,9.4 17.7,9.2 17.5,9z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_deletecontact.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_deletecontact.xml
new file mode 100644
index 000000000..78a89bfaa
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_deletecontact.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <group>
+    <clip-path
+        android:pathData="M-934.6,582L985.4,582"/>
+  </group>
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M17.1,12.1c-2.8,0 -4.9,2.2 -4.9,4.9s2.2,4.9 4.9,4.9S22,19.8 22,17S19.8,12.1 17.1,12.1zM17.5,18.3l-0.4,-0.4l-0.4,0.4c-0.3,0.3 -0.6,0.2 -0.8,0c-0.2,-0.2 -0.2,-0.6 0,-0.8l0.4,-0.4l-0.4,-0.4c-0.2,-0.2 -0.2,-0.6 0,-0.8c0.2,-0.2 0.6,-0.2 0.8,0l0.4,0.4l0.4,-0.4c0.2,-0.2 0.6,-0.2 0.8,0c0.2,0.2 0.2,0.6 0,0.8L18,17l0.4,0.4c0.2,0.2 0.2,0.6 0,0.8C18.2,18.7 17.7,18.5 17.5,18.3z"/>
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M6.6,6.2a3.6,4.2 0,1 0,7.2 0a3.6,4.2 0,1 0,-7.2 0z"/>
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M11.4,17c0,-2 1.1,-3.8 2.7,-4.8c-1.2,-0.7 -2.5,-1.1 -3.9,-1.1C5.7,11.1 2,15 2,19.7c0,2.4 3.7,2.2 8.2,2.2c1.4,0 2.8,0 4,-0.1C12.5,20.9 11.4,19.1 11.4,17z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_link_device.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_link_device.xml
new file mode 100644
index 000000000..bb029b2dc
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_link_device.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M2,5.0379h13.3v4h-7c-0.2,0 -0.3,0.1 -0.3,0.3v3h-6L2,5.0379zM8.7,9.6378h10v0.7h-4.3c-0.2,0 -0.3,0.2 -0.3,0.3v5h-1.3h-4L8.8,9.6378zM14.7,11.0379h5.3v2.7h-1c0,0 -0.1,0 -0.1,0c-0.1,0 -0.2,0.2 -0.2,0.3v4.3h-4L14.7,11.0379zM2,13.0379h6v0.7h-1.6h-0.2h-4.2L2,13.0379zM6.7,14.3378h1.3v1.3h-1.3L6.7,14.3378zM19.3,14.3378h2.7v3.3h-2.7L19.3,14.3378zM7.5,16.3378h5.2h1.3v0.7h-5.7C7.9,17.0379 7.6,16.7378 7.5,16.3378zM19.3,18.3378h2.7v0.7h-2.7v-0.3c0,0 0,0 0,-0.1L19.3,18.3378z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_message_audio.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_message_audio.xml
new file mode 100644
index 000000000..6ea3dd834
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_message_audio.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M19.1,4.9C17.2,3 14.7,2 12,2S6.8,3 4.9,4.9S2,9.3 2,12s1,5.2 2.9,7.1S9.3,22 12,22h10V12C22,9.3 21,6.8 19.1,4.9zM10.5,9c0,-1 0.8,-1.8 1.8,-1.8S14.1,8 14.1,9v3.2c0,1 -0.8,1.7 -1.8,1.7s-1.8,-0.8 -1.8,-1.8V9zM15.8,12.1c0,1.8 -1.4,3.3 -3.1,3.6v0.5h0.9c0.3,0 0.5,0.2 0.5,0.5s-0.2,0.5 -0.5,0.5h-2.8c-0.3,0 -0.5,-0.2 -0.5,-0.5s0.2,-0.5 0.5,-0.5h0.9v-0.5c-1.8,-0.3 -3.1,-1.8 -3.1,-3.6v-1.4c0,-0.2 0.1,-0.3 0.2,-0.4c0.1,-0.1 0.2,-0.2 0.4,-0.2s0.3,0.1 0.4,0.2c0.1,0.1 0.2,0.3 0.2,0.4v1.4c0,1.4 1.1,2.5 2.5,2.5s2.5,-1.1 2.5,-2.5v-1.4c0,-0.2 0.1,-0.3 0.2,-0.4c0.2,-0.2 0.5,-0.1 0.6,0s0.2,0.3 0.2,0.4C15.8,10.7 15.8,12.1 15.8,12.1z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_message_video.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_message_video.xml
new file mode 100644
index 000000000..27d4753e5
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_message_video.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M19.1,4.9C17.2,3 14.7,2 12,2S6.8,3 4.9,4.9C3,6.8 2,9.3 2,12s1,5.2 2.9,7.1C6.8,21 9.3,22 12,22h10V12C22,9.3 21,6.8 19.1,4.9zM17.2,14.7c0,0.4 -0.3,0.7 -0.4,0.8c-0.3,0.1 -0.6,0.1 -0.9,-0.1l-1,-0.5v0.5c0,0.4 -0.4,0.8 -1,0.8h-6c-0.7,0 -1.1,-0.4 -1.1,-0.9V9.6c0,-0.4 0.4,-0.8 1,-0.9h6c0.6,0 1,0.4 1,0.9v0.5l1,-0.5c0.4,-0.3 0.7,-0.3 1,-0.1c0.2,0.1 0.4,0.3 0.4,0.7V14.7z"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/baseline_androidtv_settings.xml b/ring-android/app/src/main/res/drawable/baseline_androidtv_settings.xml
new file mode 100644
index 000000000..77da6220a
--- /dev/null
+++ b/ring-android/app/src/main/res/drawable/baseline_androidtv_settings.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M15.1959,12.0165c0,1.7286 -1.4464,3.1397 -3.2103,3.1397c-1.7639,-0 -3.2103,-1.4111 -3.2103,-3.1397c0,-1.7286 1.4464,-3.1397 3.2103,-3.1397C13.7848,8.8768 15.1959,10.2526 15.1959,12.0165M19.2528,9.7587l-0.4939,-1.1994c0,-0 1.1642,-2.6458 1.0583,-2.7517l-1.5522,-1.5169c-0.1058,-0.1058 -2.7517,1.0936 -2.7517,1.0936l-1.2347,-0.4939c0,-0 -1.0936,-2.6811 -1.2347,-2.6811l-2.1872,-0c-0.1411,-0 -1.1642,2.6811 -1.1642,2.6811l-1.2347,0.4939c0,-0 -2.7164,-1.1642 -2.8222,-1.0583l-1.5522,1.5169c-0.1058,0.1058 1.1289,2.7164 1.1289,2.7164l-0.4939,1.1994c0,-0 -2.7517,1.0583 -2.7517,1.1994l0,2.1519c0,0.1411 2.7517,1.1289 2.7517,1.1289l0.4939,1.1994c0,-0 -1.1642,2.6458 -1.0583,2.7517l1.5522,1.5169c0.1058,0.1058 2.7517,-1.0936 2.7517,-1.0936l1.2347,0.4939c0,-0 1.0936,2.6811 1.2347,2.6811l2.1872,-0c0.1411,-0 1.1642,-2.6811 1.1642,-2.6811l1.2347,-0.4939c0,-0 2.7164,1.1642 2.8222,1.0583l1.5522,-1.5169c0.1058,-0.1058 -1.1289,-2.7164 -1.1289,-2.7164l0.4939,-1.1994c0,-0 2.7517,-1.0583 2.7517,-1.1994l0,-2.1519C22.0045,10.7465 19.2528,9.7587 19.2528,9.7587"/>
+</vector>
diff --git a/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml b/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml
index 6c7ec0c55..0ac0b3acb 100644
--- a/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml
+++ b/ring-android/app/src/main/res/drawable/ic_tv_online_indicator.xml
@@ -1,9 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="oval">
-    <stroke android:width="1px" android:color="#fafafa"/>
     <solid android:color="#4CAF50" />
     <size
-        android:width="10dp"
-        android:height="10dp" />
+        android:width="8dp"
+        android:height="8dp" />
 </shape>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/drawable/tv_header_bg.xml b/ring-android/app/src/main/res/drawable/tv_header_bg.xml
index ed3098694..f062c80ac 100644
--- a/ring-android/app/src/main/res/drawable/tv_header_bg.xml
+++ b/ring-android/app/src/main/res/drawable/tv_header_bg.xml
@@ -5,8 +5,7 @@
             <gradient
                 android:angle="90"
                 android:startColor="#00000000"
-                android:centerColor="@color/grey_900"
-                android:endColor="@color/grey_900"
+                android:endColor="@color/tv_contact_background"
                 android:type="linear" />
         </shape>
     </item>
diff --git a/ring-android/app/src/main/res/font/mulish_regular.ttf b/ring-android/app/src/main/res/font/mulish_regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..26b7cb2d7ea5479fca50600057ac9b0f1861eb6b
GIT binary patch
literal 89244
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%0AIQQs>whHVS>uhNC!bi2GB+@@YZh;MiKB)
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
AI{*Lx

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/font/mulish_semibold.ttf b/ring-android/app/src/main/res/font/mulish_semibold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8b46d612e05f040f41862c0d82b1b02b99cda864
GIT binary patch
literal 89340
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
KQ2hl85e5Khl8Q3`

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/font/ubuntu_light.ttf b/ring-android/app/src/main/res/font/ubuntu_light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..0e9f90d7cb13b3c816150ceae52bce1c91375e11
GIT binary patch
literal 361676
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

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/font/ubuntu_medium.ttf b/ring-android/app/src/main/res/font/ubuntu_medium.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..5296045e961bc51aad3115845134299063c35581
GIT binary patch
literal 284424
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

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/font/ubuntu_regular.ttf b/ring-android/app/src/main/res/font/ubuntu_regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..dbb834a4dd5df2abb80d80ade8d2566bef49b90b
GIT binary patch
literal 298928
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
Q7F4P*fJ+r7aF2u;01&ljBme*a

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/layout-w720dp-land/tv_activity_about.xml b/ring-android/app/src/main/res/layout-w720dp-land/tv_activity_about.xml
deleted file mode 100644
index 48f8e0595..000000000
--- a/ring-android/app/src/main/res/layout-w720dp-land/tv_activity_about.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-Copyright (C) 2004-2016 Savoir-faire Linux Inc.
-
-Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
-
-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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-          android:name="cx.ring.tv.about.AboutDetailsFragment"
-          android:id="@+id/details_fragment"
-          android:layout_width="match_parent"
-          android:layout_height="match_parent"
-    />
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout-w720dp-land/tv_frag_call.xml b/ring-android/app/src/main/res/layout-w720dp-land/tv_frag_call.xml
index a4114ce97..2d45aecaa 100644
--- a/ring-android/app/src/main/res/layout-w720dp-land/tv_frag_call.xml
+++ b/ring-android/app/src/main/res/layout-w720dp-land/tv_frag_call.xml
@@ -211,6 +211,34 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             app:useCompatPadding="true"
             tools:visibility="visible" />
 
+        <LinearLayout
+            android:id="@+id/record_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_margin="16dp"
+            android:gravity="center_vertical"
+            android:layout_alignParentLeft="true"
+            android:visibility="invisible"
+            tools:visibility="visible">
+
+            <View
+                android:id="@+id/record_indicator"
+                android:layout_width="13dp"
+                android:layout_height="13dp"
+                android:backgroundTint="#BF0046"
+                android:background="@drawable/item_color_background" />
+
+            <TextView
+                android:id="@+id/record_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:textSize="12sp"
+                tools:text="Thomas"/>
+
+        </LinearLayout>
+
         <com.google.android.material.floatingactionbutton.FloatingActionButton
             android:id="@+id/call_add_btn"
             android:layout_width="wrap_content"
diff --git a/ring-android/app/src/main/res/layout/frag_conversation_tv.xml b/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
index 226588124..70647e861 100644
--- a/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
+++ b/ring-android/app/src/main/res/layout/frag_conversation_tv.xml
@@ -11,14 +11,16 @@
         android:id="@+id/title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textSize="22sp"
-        android:textStyle="bold" />
+            android:textSize="20sp"
+        android:textStyle="bold"
+        android:fontFamily="@font/ubuntu_medium"/>
 
     <TextView
         android:id="@+id/subtitle"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textSize="18sp" />
+        android:textSize="16sp"
+        android:fontFamily="@font/mulish_regular"/>
 
     <FrameLayout
         android:layout_width="match_parent"
@@ -77,12 +79,11 @@
 
                         <ImageButton
                             android:id="@+id/button_text"
-                            android:layout_width="50dp"
-                            android:layout_height="50dp"
-                            android:alpha="0.85"
+                            android:layout_width="40dp"
+                            android:layout_height="40dp"
                             android:background="@drawable/tv_button_shape"
                             android:contentDescription="@string/tv_send_text"
-                            android:src="@drawable/baseline_chat_24"
+                            android:src="@drawable/baseline_androidtv_chat"
                             android:tint="@color/white" />
 
                         <TextView
@@ -106,12 +107,11 @@
 
                         <ImageButton
                             android:id="@+id/button_audio"
-                            android:layout_width="50dp"
-                            android:layout_height="50dp"
-                            android:alpha="0.85"
+                            android:layout_width="40dp"
+                            android:layout_height="40dp"
                             android:background="@drawable/tv_button_shape"
                             android:contentDescription="@string/tv_send_audio"
-                            android:src="@drawable/baseline_mic_24"
+                            android:src="@drawable/baseline_androidtv_message_audio"
                             android:tint="@color/white" />
 
                         <TextView
@@ -136,12 +136,11 @@
 
                         <ImageButton
                             android:id="@+id/button_video"
-                            android:layout_width="50dp"
-                            android:layout_height="50dp"
-                            android:alpha="0.85"
+                            android:layout_width="40dp"
+                            android:layout_height="40dp"
                             android:background="@drawable/tv_button_shape"
                             android:contentDescription="@string/tv_send_video"
-                            android:src="@drawable/baseline_photo_camera_24"
+                            android:src="@drawable/baseline_androidtv_message_video"
                             android:tint="@color/white" />
 
                         <TextView
diff --git a/ring-android/app/src/main/res/layout/tv_about_layout.xml b/ring-android/app/src/main/res/layout/tv_about_layout.xml
new file mode 100644
index 000000000..add636cb3
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/tv_about_layout.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/title_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:textAlignment="center"/>
diff --git a/ring-android/app/src/main/res/layout/tv_activity_about.xml b/ring-android/app/src/main/res/layout/tv_activity_about.xml
deleted file mode 100644
index 48f8e0595..000000000
--- a/ring-android/app/src/main/res/layout/tv_activity_about.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-Copyright (C) 2004-2016 Savoir-faire Linux Inc.
-
-Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
-
-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, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-          android:name="cx.ring.tv.about.AboutDetailsFragment"
-          android:id="@+id/details_fragment"
-          android:layout_width="match_parent"
-          android:layout_height="match_parent"
-    />
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_activity_contact_more.xml b/ring-android/app/src/main/res/layout/tv_activity_contact_more.xml
new file mode 100644
index 000000000..a7c8a82f0
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/tv_activity_contact_more.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/generalFragment"
+    android:name="cx.ring.tv.contact.more.TVContactMoreFragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="cx.ring.tv.contact.more.TVContactMoreActivity" />
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_activity_home.xml b/ring-android/app/src/main/res/layout/tv_activity_home.xml
index 7aa205ebc..98b8881ca 100644
--- a/ring-android/app/src/main/res/layout/tv_activity_home.xml
+++ b/ring-android/app/src/main/res/layout/tv_activity_home.xml
@@ -1,10 +1,39 @@
 <?xml version="1.0" encoding="utf-8"?>
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/main_browse_fragment"
-    android:name="cx.ring.tv.main.MainFragment"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context="cx.ring.tv.main.HomeActivity"
-    tools:deviceIds="tv"
-    tools:ignore="MergeRootFrame" />
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/previewView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
+    <ImageView
+        android:id="@+id/blur"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleX="-1"/>
+
+    <View
+        android:id="@+id/fade"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/tv_transparent"/>
+
+    <fragment
+        android:id="@+id/main_browse_fragment"
+        android:name="cx.ring.tv.main.MainFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context="cx.ring.tv.main.HomeActivity"
+        tools:deviceIds="tv"
+        tools:ignore="MergeRootFrame" />
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fragment_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_activity_settings.xml b/ring-android/app/src/main/res/layout/tv_activity_settings.xml
index ccae34dd4..d42e1879f 100644
--- a/ring-android/app/src/main/res/layout/tv_activity_settings.xml
+++ b/ring-android/app/src/main/res/layout/tv_activity_settings.xml
@@ -2,7 +2,7 @@
 <fragment xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/generalFragment"
-    android:name="cx.ring.tv.account.TVSettingsFragment"
+    android:name="cx.ring.tv.settings.TVSettingsFragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context="cx.ring.tv.account.TVSettingsActivity" />
\ No newline at end of file
+    tools:context="cx.ring.tv.settings.TVSettingsActivity" />
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_frag_call.xml b/ring-android/app/src/main/res/layout/tv_frag_call.xml
index 7a206e113..a1e71e2f9 100644
--- a/ring-android/app/src/main/res/layout/tv_frag_call.xml
+++ b/ring-android/app/src/main/res/layout/tv_frag_call.xml
@@ -246,5 +246,33 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             app:useCompatPadding="true"
             tools:visibility="visible" />
 
+        <LinearLayout
+            android:id="@+id/record_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:layout_margin="16dp"
+            android:gravity="center_vertical"
+            android:layout_alignParentLeft="true"
+            android:visibility="invisible"
+            tools:visibility="visible">
+
+            <View
+                android:id="@+id/record_indicator"
+                android:layout_width="13dp"
+                android:layout_height="13dp"
+                android:backgroundTint="#BF0046"
+                android:background="@drawable/item_color_background" />
+
+            <TextView
+                android:id="@+id/record_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:textSize="12sp"
+                tools:text="Thomas"/>
+
+        </LinearLayout>
+
     </RelativeLayout>
 </layout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_frag_contact.xml b/ring-android/app/src/main/res/layout/tv_frag_contact.xml
index ba4638167..41ec18ae2 100644
--- a/ring-android/app/src/main/res/layout/tv_frag_contact.xml
+++ b/ring-android/app/src/main/res/layout/tv_frag_contact.xml
@@ -1,6 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/details_fragment"
-    android:name="cx.ring.tv.contact.TVContactFragment"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" />
\ No newline at end of file
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <FrameLayout
+        android:id="@+id/previewView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="invisible"/>
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleX="-1"/>
+
+    <fragment
+        android:id="@+id/details_fragment"
+        android:name="cx.ring.tv.contact.TVContactFragment"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/tv_frag_share.xml b/ring-android/app/src/main/res/layout/tv_frag_share.xml
index 17fb2e066..d6b9cc4b5 100644
--- a/ring-android/app/src/main/res/layout/tv_frag_share.xml
+++ b/ring-android/app/src/main/res/layout/tv_frag_share.xml
@@ -34,6 +34,7 @@
         android:text="@string/share_message"
         android:textColor="@color/text_color_primary_dark"
         android:textSize="16sp"
+        android:fontFamily="@font/ubuntu_light"
         app:layout_constraintBottom_toTopOf="@+id/qr_image"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
diff --git a/ring-android/app/src/main/res/layout/tv_titleview.xml b/ring-android/app/src/main/res/layout/tv_titleview.xml
index 3dc85a4a0..7c245313d 100644
--- a/ring-android/app/src/main/res/layout/tv_titleview.xml
+++ b/ring-android/app/src/main/res/layout/tv_titleview.xml
@@ -3,20 +3,11 @@
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <androidx.leanback.widget.SearchOrbView
-        android:id="@+id/title_orb"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical|start"
-        android:layout_marginStart="48dp"
-        android:layout_marginTop="8dp"
-        android:transitionGroup="true" />
-
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
-        android:layout_marginEnd="24dp"
+        android:layout_marginEnd="18dp"
         android:layout_marginStart="260dp"
         android:layout_toStartOf="@id/title_photo_contact"
         android:gravity="end"
@@ -28,7 +19,9 @@
             android:layout_height="wrap_content"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textAppearance="@android:style/TextAppearance.Large"
+            android:fontFamily="@font/ubuntu_medium"
+            android:textSize="16sp"
+            android:textColor="@color/white"
             android:visibility="gone"
             tools:text="account alias"
             tools:visibility="visible" />
@@ -39,7 +32,8 @@
             android:layout_height="wrap_content"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+            android:textSize="12sp"
+            android:fontFamily="@font/ubuntu_regular"
             android:visibility="gone"
             tools:text="account name"
             tools:visibility="visible" />
@@ -48,11 +42,37 @@
 
     <ImageView
         android:id="@+id/title_photo_contact"
-        android:layout_width="80dp"
-        android:layout_height="80dp"
+        android:layout_width="52dp"
+        android:layout_height="52dp"
+        android:layout_toStartOf="@+id/title_settings"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="16dp"
+        android:layout_marginTop="8dp"
+        app:srcCompat="@drawable/ic_contact_picture_fallback" />
+
+    <ImageButton
+        android:id="@+id/title_settings"
+        android:layout_width="52dp"
+        android:layout_height="52dp"
         android:layout_alignParentEnd="true"
         android:layout_gravity="center_vertical|end"
         android:layout_marginEnd="24dp"
-        android:padding="6dp"
-        app:srcCompat="@drawable/ic_contact_picture_fallback" />
+        android:layout_marginTop="18dp"
+        android:padding="26dp"
+        android:tint="@color/grey_300"
+        android:layout_centerVertical="true"
+        android:background="@drawable/tv_button_shape"
+        android:src="@drawable/baseline_androidtv_settings"
+        android:visibility="invisible"/>
+
+    <androidx.leanback.widget.SearchOrbView
+        android:id="@+id/title_orb"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical|start"
+        android:layout_marginStart="48dp"
+        android:layout_marginTop="8dp"
+        android:transitionGroup="true"
+        android:visibility="invisible"/>
+
 </merge>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/colors.xml b/ring-android/app/src/main/res/values/colors.xml
index 7feeafe29..7dbb71aca 100644
--- a/ring-android/app/src/main/res/values/colors.xml
+++ b/ring-android/app/src/main/res/values/colors.xml
@@ -50,4 +50,11 @@
     <color name="button_border">@color/color_primary_dark</color>
     <color name="chip_background">#330f426b</color>
 
+    <!--  TV  -->
+    <color name="tv_transparent">#a0000000</color>
+    <color name="tv_contact_background">#00335C</color>
+    <color name="tv_contact_row_background">#002B4E</color>
+    <color name="tv_search_background">#0095EB</color>
+
+
 </resources>
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index cb1017984..888b0faa2 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -73,7 +73,7 @@ along with this program; if not, write to the Free Software
     <string name="menu_item_account">Manage account</string>
     <string name="menu_item_settings">Settings</string>
     <string name="menu_item_plugin_list">Plugins Settings</string>
-    <string name="menu_item_share">Share my contact</string>
+    <string name="menu_item_share">Share my account</string>
     <string name="menu_item_about">About Jami</string>
 
     <!-- Dialing Fragment -->
@@ -158,7 +158,10 @@ along with this program; if not, write to the Free Software
     <string name="ab_action_speakerphone">Enable speaker</string>
     <string name="ab_action_contact_add">Add to contacts</string>
     <string name="ab_action_contact_add_question">Add to contacts?</string>
+    <string name="hist_contact_invited">Contact invited</string>
     <string name="hist_contact_added">Contact added</string>
+    <string name="hist_contact_left">Contact left</string>
+    <string name="hist_contact_banned">Contact banned</string>
     <string name="hist_invitation_received">Invitation received</string>
     <string name="ab_action_audio_call">Audio call</string>
     <string name="ab_action_video_call">Video call</string>
@@ -214,6 +217,7 @@ along with this program; if not, write to the Free Software
     <string name="clear_history_dialog_title">Clear history?</string>
     <string name="clear_history_dialog_message">This action cannot be undone.</string>
     <string name="clear_history_completed">History has been cleared.</string>
+    <string name="clear_history">Clear</string>
 
     <!-- Conversation -->
     <string name="conversation_details">Contact details</string>
@@ -370,6 +374,7 @@ along with this program; if not, write to the Free Software
     <string name="tv_send_text">Send Text</string>
     <string name="tv_send_audio">Send Audio</string>
     <string name="tv_send_video">Send Media</string>
+    <string name="tv_action_more">More</string>
     <string name="conversation_input_speech_hint">Say something…</string>
 
     <!-- Wizard -->
diff --git a/ring-android/app/src/main/res/values/strings_account.xml b/ring-android/app/src/main/res/values/strings_account.xml
index 0ffde5a80..4f968ed1e 100644
--- a/ring-android/app/src/main/res/values/strings_account.xml
+++ b/ring-android/app/src/main/res/values/strings_account.xml
@@ -213,6 +213,17 @@ along with this program; if not, write to the Free Software
     <string name="account_sip_success_message">You have successfully registered your Sip account.</string>
     <string name="account_sip_register_anyway">Register anyway</string>
 
+    <!--  TV  -->
+    <string name="account_tv_settings_header">Account settings</string>
+    <string name="account_tv_advance_settings_header">Advanced settings</string>
+    <string name="account_tv_add_contact">Add contact</string>
+    <string name="account_tv_advanced_settings">Change the general settings</string>
+    <string name="account_tv_about">About Jami</string>
+    <string name="tv_about_version">Version</string>
+    <string name="tv_about_license">License</string>
+    <string name="tv_about_author">Author rights</string>
+    <string name="tv_about_credits">Credits</string>
+
     <string name="account_link_device">Connect from another device</string>
     <string name="account_link_button">Connect from network</string>
     <string name="account_link_archive_button">Connect from backup</string>
@@ -226,7 +237,7 @@ along with this program; if not, write to the Free Software
     <string name="account_end_export_button">close</string>
     <string name="account_end_export_infos">Your PIN is:\n\n%%\n\nTo complete the process, you need to open Jami on the new device. Create a new account with \"Link this device to an account\". Your PIN is valid for 10 minutes.</string>
     <string name="account_link_export_info_light">To use this account on other devices, you must first expose it on Jami. This will generate a PIN code that you must enter on the new device to set up the account. The PIN is valid for 10 minutes.</string>
-    <string name="account_export_title">Add devices</string>
+    <string name="account_export_title">Link account to other devices</string>
     <string name="account_connect_server_button">Connect to management server</string>
     <string name="account_connect_button">Connect</string>
     <string name="prompt_server">Jami management server URL</string>
@@ -262,7 +273,7 @@ along with this program; if not, write to the Free Software
     <string name="account_sip_cannot_be_registered">Can\'t register account</string>
 
     <!-- Edit profile-->
-    <string name="account_edit_profile">Edit your profile</string>
+    <string name="account_edit_profile">Edit my account</string>
 
     <!-- Devices -->
     <string name="account_revoke_device_hint">Enter password to confirm</string>
diff --git a/ring-android/app/src/main/res/values/styles.xml b/ring-android/app/src/main/res/values/styles.xml
index afdcbc128..5fccdf6de 100644
--- a/ring-android/app/src/main/res/values/styles.xml
+++ b/ring-android/app/src/main/res/values/styles.xml
@@ -181,7 +181,7 @@
     <style name="Theme.Ring.Leanback.GuidedStep.First" />
 
     <style name="Theme.Ring.LeanbackBrowse" parent="Theme.Leanback.Browse">
-        <item name="defaultSearchColor">@color/color_primary_light</item>
+        <item name="defaultSearchColor">@color/tv_search_background</item>
         <item name="searchOrbViewStyle">@style/CustomSearchOrbView</item>
     </style>
 
@@ -195,7 +195,7 @@
     </style>
 
     <style name="CustomSearchOrbView" parent="Widget.Leanback.SearchOrbViewStyle">
-        <item name="searchOrbIcon">@drawable/baseline_person_add_24</item>
+        <item name="searchOrbIcon">@drawable/baseline_search_24</item>
     </style>
 
     <!-- A default card style. Used in cards example. -->
@@ -236,16 +236,8 @@
         <item name="android:padding">16dp</item>
     </style>
 
-    <style name="IconCardTitleStyle" parent="Widget.Leanback.ImageCardView.TitleStyle">
-        <item name="android:maxLines">2</item>
-        <item name="android:minLines">2</item>
-        <item name="android:textAlignment">center</item>
-        <item name="android:gravity">center</item>
-    </style>
-
     <style name="IconCardInfoAreaStyle" parent="Widget.Leanback.ImageCardView.InfoAreaStyle">
         <item name="android:layout_width">96dp</item>
-        <item name="android:background">@null</item>
         <item name="layout_viewType">main</item>
     </style>
 
@@ -255,13 +247,6 @@
         <item name="android:layout_height">@dimen/search_image_card_height</item>
     </style>
 
-    <style name="SearchCardStyle" parent="Widget.Leanback.ImageCardViewStyle">
-        <item name="cardBackground">@null</item>
-        <item name="android:layout_width">96dp</item>
-        <item name="android:layout_height">96dp</item>
-        <item name="lbImageCardViewType">Title</item>
-    </style>
-
     <style name="ContactCardTheme" parent="DefaultCardTheme">
         <item name="imageCardViewStyle">@style/ContactTitleViewStyle</item>
     </style>
@@ -270,14 +255,9 @@
         <item name="imageCardViewStyle">@style/ContactCompleteCardViewStyle</item>
     </style>
 
-    <style name="SearchCardTheme" parent="Theme.Leanback">
-        <item name="imageCardViewStyle">@style/SearchCardStyle</item>
-        <item name="imageCardViewImageStyle">@style/SearchCardImageStyle</item>
-    </style>
     <!-- Theme corresponding to the IconCardStyle -->
     <style name="IconCardTheme" parent="Theme.Leanback">
         <item name="imageCardViewStyle">@style/IconCardViewStyle</item>
-        <item name="imageCardViewTitleStyle">@style/IconCardTitleStyle</item>
         <item name="imageCardViewImageStyle">@style/IconCardImageStyle</item>
         <item name="imageCardViewInfoAreaStyle">@style/IconCardInfoAreaStyle</item>
     </style>
@@ -360,4 +340,59 @@
         <item name="android:textSize">20sp</item>
     </style>
 
+    <!-- Style for the title view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceTitleStyle">
+        <item name="android:layout_toEndOf">@id/guidance_icon</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignWithParentIfMissing">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:fontFamily">@font/ubuntu_light</item>
+        <item name="android:gravity">start</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:paddingBottom">4dp</item>
+        <item name="android:paddingTop">2dp</item>
+        <item name="android:textColor">#FFFFFF</item>
+        <item name="android:textSize">36sp</item>
+    </style>
+
+    <!-- Style for the description view in a GuidanceStylist's default layout. -->
+    <style name="Widget.Leanback.GuidanceDescriptionStyle">
+        <item name="android:layout_below">@id/guidance_title</item>
+        <item name="android:layout_toEndOf">@id/guidance_icon</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_alignWithParentIfMissing">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:fontFamily">@font/mulish_regular</item>
+        <item name="android:gravity">start</item>
+        <item name="android:maxLines">6</item>
+        <item name="android:textColor">#88F1F1F1</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:lineSpacingExtra">3dp</item>
+    </style>
+
+    <!-- Style for an action's title in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemTitleStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:alpha">1.00</item>
+        <item name="android:fontFamily">@font/ubuntu_medium</item>
+        <item name="android:maxLines">@integer/lb_guidedactions_item_title_min_lines</item>
+        <item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
+        <item name="android:textSize">@dimen/lb_guidedactions_item_title_font_size</item>
+    </style>
+
+    <!-- Style for an action's description in a GuidedActionsStylist's default item layout. -->
+    <style name="Widget.Leanback.GuidedActionItemDescriptionStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:alpha">0.50</item>
+        <item name="android:fontFamily">@font/mulish_regular</item>
+        <item name="android:maxLines">@integer/lb_guidedactions_item_description_min_lines</item>
+        <item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
+        <item name="android:textSize">@dimen/lb_guidedactions_item_description_font_size</item>
+        <item name="android:visibility">gone</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/xml/tv_about_pref.xml b/ring-android/app/src/main/res/xml/tv_about_pref.xml
new file mode 100644
index 000000000..f7688fbfd
--- /dev/null
+++ b/ring-android/app/src/main/res/xml/tv_about_pref.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/about">
+
+    <PreferenceCategory android:title="@string/tv_about_version">
+        <Preference
+            android:key="About.version"
+            android:selectable="true"
+            android:enabled="true"/>
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/tv_about_license">
+        <Preference
+            android:key="About.license"
+            android:selectable="true"
+            android:enabled="true"/>
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/tv_about_author">
+        <Preference
+            android:key="About.rights"
+            android:selectable="true"
+            android:enabled="true"/>
+    </PreferenceCategory>
+
+    <PreferenceCategory android:title="@string/tv_about_credits">
+        <Preference
+            android:key="About.credits"
+            android:selectable="true"
+            android:enabled="true"/>
+    </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/xml/tv_account_general_pref.xml b/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
index 4b084a2d6..62ca37cba 100644
--- a/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
+++ b/ring-android/app/src/main/res/xml/tv_account_general_pref.xml
@@ -1,5 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/action_settings">
+
+    <PreferenceCategory android:title="@string/about">
+        <Preference
+            android:key="Account.about"
+            android:title="@string/account_tv_about"
+            android:selectable="true"
+            android:fragment="cx.ring.tv.settings.TVAboutFragment"
+            android:icon="@drawable/ic_jami"/>
+    </PreferenceCategory>
+
     <PreferenceCategory android:title="@string/account_basic_category">
         <SwitchPreference
             android:defaultValue="false"
diff --git a/ring-android/app/src/main/res/xml/tv_contact_more_pref.xml b/ring-android/app/src/main/res/xml/tv_contact_more_pref.xml
new file mode 100644
index 000000000..22d9d7e77
--- /dev/null
+++ b/ring-android/app/src/main/res/xml/tv_contact_more_pref.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/tv_action_more">
+    <PreferenceCategory>
+        <Preference
+            android:key="Contact.clear"
+            android:title="@string/conversation_action_history_clear"
+            android:icon="@drawable/baseline_androidtv_clearconversation"
+            />
+        <Preference
+            android:key="Contact.delete"
+            android:title="@string/conversation_action_remove_this"
+            android:icon="@drawable/baseline_androidtv_deletecontact"
+            />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.java b/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.java
deleted file mode 100644
index 4a1493348..000000000
--- a/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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.application;
-
-import android.util.Log;
-
-import com.google.firebase.FirebaseApp;
-import com.google.firebase.messaging.FirebaseMessaging;
-import com.google.firebase.messaging.RemoteMessage;
-
-public class JamiApplicationFirebase extends JamiApplication {
-    static private final String TAG = JamiApplicationFirebase.class.getSimpleName();
-    private String pushToken = null;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        try {
-            FirebaseApp.initializeApp(this);
-            FirebaseMessaging.getInstance().getToken().addOnSuccessListener(token -> {
-                Log.w(TAG, "Found push token");
-                try {
-                    setPushToken(token);
-                } catch (Exception e) {
-                    Log.e(TAG, "Can't set push token", e);
-                }
-            });
-        } catch (Exception e) {
-            Log.e(TAG, "Can't start service", e);
-        }
-    }
-
-    @Override
-    public String getPushToken() {
-        return pushToken;
-    }
-
-    public void setPushToken(String token) {
-        // Log.d(TAG, "setPushToken: " + token);
-        pushToken = token;
-        if (mAccountService != null && mPreferencesService != null) {
-            if (mPreferencesService.getSettings().isAllowPushNotifications()) {
-                mAccountService.setPushNotificationToken(token);
-            }
-        }
-    }
-
-    public void onMessageReceived(RemoteMessage remoteMessage) {
-        // Log.d(TAG, "onMessageReceived: " + remoteMessage.getFrom());
-        if (mAccountService != null)
-            mAccountService.pushNotificationReceived(remoteMessage.getFrom(), remoteMessage.getData());
-    }
-}
diff --git a/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.kt b/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.kt
new file mode 100644
index 000000000..5fff7795f
--- /dev/null
+++ b/ring-android/app/src/withFirebase/java/cx/ring/application/JamiApplicationFirebase.kt
@@ -0,0 +1,65 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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.application
+
+import android.util.Log
+import com.google.firebase.FirebaseApp
+import com.google.firebase.messaging.FirebaseMessaging
+import com.google.firebase.messaging.RemoteMessage
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class JamiApplicationFirebase : JamiApplication() {
+
+    override var pushToken: String? = null
+        set(token) {
+            //Log.d(TAG, "setPushToken: $token");
+            field = token
+            if (mPreferencesService.settings.isAllowPushNotifications) {
+                mAccountService.setPushNotificationToken(token)
+            }
+        }
+
+    override fun onCreate() {
+        super.onCreate()
+        try {
+            Log.w(TAG, "onCreate()")
+            FirebaseApp.initializeApp(this)
+            FirebaseMessaging.getInstance().token.addOnSuccessListener { token: String? ->
+                Log.w(TAG, "Found push token")
+                try {
+                    pushToken = token
+                } catch (e: Exception) {
+                    Log.e(TAG, "Can't set push token", e)
+                }
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Can't start service", e)
+        }
+    }
+
+    fun onMessageReceived(remoteMessage: RemoteMessage) {
+        // Log.d(TAG, "onMessageReceived: " + remoteMessage.getFrom());
+        mAccountService.pushNotificationReceived(remoteMessage.from, remoteMessage.data)
+    }
+
+    companion object {
+        private val TAG = JamiApplicationFirebase::class.simpleName
+    }
+}
\ No newline at end of file
diff --git a/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.java b/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.java
deleted file mode 100644
index d56569d7e..000000000
--- a/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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.services;
-
-import android.content.Context;
-import android.os.PowerManager;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.google.firebase.messaging.FirebaseMessagingService;
-import com.google.firebase.messaging.RemoteMessage;
-
-import cx.ring.application.JamiApplication;
-import cx.ring.application.JamiApplicationFirebase;
-
-public class JamiFirebaseMessagingService extends FirebaseMessagingService {
-    @Override
-    public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
-        try {
-            // Even if wakeLock is deprecated, without this part, some devices are blocking
-            // during the call negotiation. So, re-add this code to avoid to block here.
-            PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
-            PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wake:push");
-            wl.setReferenceCounted(false);
-            wl.acquire(10 * 1000);
-        } catch (Exception e) {
-            Log.w("JamiFirebaseMessaging", "Can't acquire wake lock", e);
-        }
-
-        JamiApplicationFirebase app = (JamiApplicationFirebase)JamiApplication.getInstance();
-        if (app != null)
-            app.onMessageReceived(remoteMessage);
-    }
-
-    @Override
-    public void onNewToken(@NonNull String refreshedToken) {
-        JamiApplicationFirebase app = (JamiApplicationFirebase)JamiApplication.getInstance();
-        if (app != null)
-            app.setPushToken(refreshedToken);
-    }
-}
diff --git a/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.kt b/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.kt
new file mode 100644
index 000000000..b85f196d5
--- /dev/null
+++ b/ring-android/app/src/withFirebase/java/cx/ring/services/JamiFirebaseMessagingService.kt
@@ -0,0 +1,50 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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.services
+
+import android.os.PowerManager
+import android.util.Log
+import com.google.firebase.messaging.FirebaseMessagingService
+import com.google.firebase.messaging.RemoteMessage
+import cx.ring.application.JamiApplication
+import cx.ring.application.JamiApplicationFirebase
+
+class JamiFirebaseMessagingService : FirebaseMessagingService() {
+
+    override fun onMessageReceived(remoteMessage: RemoteMessage) {
+        try {
+            // Even if wakeLock is deprecated, without this part, some devices are blocking
+            // during the call negotiation. So, re-add this code to avoid to block here.
+            val pm = getSystemService(POWER_SERVICE) as PowerManager
+            val wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "wake:push")
+            wl.setReferenceCounted(false)
+            wl.acquire((10 * 1000).toLong())
+        } catch (e: Exception) {
+            Log.w("JamiFirebaseMessaging", "Can't acquire wake lock", e)
+        }
+        val app = JamiApplication.instance as JamiApplicationFirebase?
+        app?.onMessageReceived(remoteMessage)
+    }
+
+    override fun onNewToken(refreshedToken: String) {
+        Log.w("JamiFirebaseMessaging", "onNewToken $refreshedToken")
+        val app = JamiApplication.instance as JamiApplicationFirebase?
+        app?.pushToken = refreshedToken
+    }
+}
\ No newline at end of file
diff --git a/ring-android/build.gradle b/ring-android/build.gradle
index 11800d4fe..7c4c426f8 100644
--- a/ring-android/build.gradle
+++ b/ring-android/build.gradle
@@ -4,9 +4,13 @@ buildscript {
         maven { url "https://maven.google.com" }
         mavenCentral()
     }
+    ext.kotlin_version = '1.5.21'
+    ext.hilt_version = '2.38.1'
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.2.1'
-        classpath 'com.google.gms:google-services:4.3.8'
+        classpath 'com.android.tools.build:gradle:7.0.0'
+        classpath 'com.google.gms:google-services:4.3.10'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
     }
 }
 allprojects {
diff --git a/ring-android/gradle/wrapper/gradle-wrapper.properties b/ring-android/gradle/wrapper/gradle-wrapper.properties
index 1de822da9..d7f3c26ed 100644
--- a/ring-android/gradle/wrapper/gradle-wrapper.properties
+++ b/ring-android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
diff --git a/ring-android/libringclient/build.gradle b/ring-android/libringclient/build.gradle
index fd647e2bb..9b10c1954 100644
--- a/ring-android/libringclient/build.gradle
+++ b/ring-android/libringclient/build.gradle
@@ -1,8 +1,11 @@
+apply plugin: 'kotlin'
 apply plugin: 'java'
+apply plugin: 'kotlin-kapt'
 
 dependencies {
 
     implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
 
     // VCard parsing
     implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.2'
@@ -25,6 +28,8 @@ dependencies {
     // gson
     implementation 'com.google.code.gson:gson:2.8.7'
 
+    api "com.google.dagger:dagger:$hilt_version"
+    kapt "com.google.dagger:dagger-compiler:$hilt_version"
 }
 
 sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java
index 0a8722aad..dddac96d3 100644
--- a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java
@@ -227,7 +227,7 @@ public class AccountWizardPresenter extends RootPresenter<net.jami.account.Accou
                 .firstElement()
                 .subscribe(a -> {
                     if (!model.isLink() && a.isJami() && !StringUtils.isEmpty(model.getUsername()))
-                        mAccountService.registerName(a, model.getPassword().toString(), model.getUsername());
+                        mAccountService.registerName(a, model.getPassword(), model.getUsername());
                     mAccountService.setCurrentAccount(a);
                     if (model.isPush()) {
                         Settings settings = mPreferences.getSettings();
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.java
deleted file mode 100644
index f09359c8d..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.account;
-
-import net.jami.services.AccountService;
-
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import net.jami.mvp.AccountCreationModel;
-import net.jami.mvp.RootPresenter;
-import net.jami.utils.StringUtils;
-
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-
-public class JamiAccountCreationPresenter extends RootPresenter<net.jami.account.JamiAccountCreationView> {
-
-    public static final String TAG = JamiAccountCreationPresenter.class.getSimpleName();
-    private static final int PASSWORD_MIN_LENGTH = 6;
-    private static final long TYPING_DELAY = 350L;
-    private final PublishSubject<String> contactQuery = PublishSubject.create();
-    protected net.jami.services.AccountService mAccountService;
-    @Inject
-    @Named("UiScheduler")
-    protected Scheduler mUiScheduler;
-    private AccountCreationModel mAccountCreationModel;
-    private boolean isUsernameCorrect = false;
-    private boolean isPasswordCorrect = true;
-    private boolean isConfirmCorrect = true;
-    private boolean showLoadingAnimation = true;
-    private CharSequence mPasswordConfirm = "";
-
-    @Inject
-    public JamiAccountCreationPresenter(AccountService accountService) {
-        this.mAccountService = accountService;
-    }
-
-    @Override
-    public void bindView(net.jami.account.JamiAccountCreationView view) {
-        super.bindView(view);
-        mCompositeDisposable.add(contactQuery
-                .debounce(TYPING_DELAY, TimeUnit.MILLISECONDS)
-                .switchMapSingle(q -> mAccountService.
-                        findRegistrationByName("", "", q))
-                .observeOn(mUiScheduler)
-                .subscribe(q -> onLookupResult(q.name, q.address, q.state)));
-    }
-
-    public void init(AccountCreationModel accountCreationModel) {
-        if (accountCreationModel == null) {
-            getView().cancel();
-        }
-        mAccountCreationModel = accountCreationModel;
-    }
-
-    /**
-     * Called everytime the provided username for the new account changes
-     * Sends the new value of the username to the ContactQuery subjet and shows the loading
-     * animation if it has not been started before
-     */
-    public void userNameChanged(String userName) {
-        if (mAccountCreationModel != null)
-            mAccountCreationModel.setUsername(userName);
-        contactQuery.onNext(userName);
-        isUsernameCorrect = false;
-
-        if (showLoadingAnimation) {
-            net.jami.account.JamiAccountCreationView view = getView();
-            if (view != null)
-                view.updateUsernameAvailability(net.jami.account.JamiAccountCreationView.UsernameAvailabilityStatus.LOADING);
-            showLoadingAnimation = false;
-        }
-    }
-
-    public void registerUsernameChanged(boolean isChecked) {
-        if (mAccountCreationModel != null) {
-            if (!isChecked) {
-                mAccountCreationModel.setUsername("");
-            }
-            checkForms();
-        }
-    }
-
-    public void passwordUnset() {
-        if (mAccountCreationModel != null)
-            mAccountCreationModel.setPassword(null);
-        isPasswordCorrect = true;
-        isConfirmCorrect = true;
-        getView().showInvalidPasswordError(false);
-        getView().enableNextButton(true);
-    }
-
-    public void passwordChanged(String password, CharSequence repeat) {
-        mPasswordConfirm = repeat;
-        passwordChanged(password);
-    }
-
-    public void passwordChanged(String password) {
-        if (mAccountCreationModel != null)
-            mAccountCreationModel.setPassword(password);
-        if (!StringUtils.isEmpty(password) && password.length() < PASSWORD_MIN_LENGTH) {
-            getView().showInvalidPasswordError(true);
-            isPasswordCorrect = false;
-        } else {
-            getView().showInvalidPasswordError(false);
-            isPasswordCorrect = password.length() != 0;
-            if (!password.contentEquals(mPasswordConfirm)) {
-                if (mPasswordConfirm.length() > 0)
-                    getView().showNonMatchingPasswordError(true);
-                isConfirmCorrect = false;
-            } else {
-                getView().showNonMatchingPasswordError(false);
-                isConfirmCorrect = true;
-            }
-        }
-        getView().enableNextButton(isPasswordCorrect && isConfirmCorrect);
-    }
-
-    public void passwordConfirmChanged(String passwordConfirm) {
-        if (!passwordConfirm.equals(mAccountCreationModel.getPassword())) {
-            getView().showNonMatchingPasswordError(true);
-            isConfirmCorrect = false;
-        } else {
-            getView().showNonMatchingPasswordError(false);
-            isConfirmCorrect = true;
-        }
-        mPasswordConfirm = passwordConfirm;
-        getView().enableNextButton(isPasswordCorrect && isConfirmCorrect);
-    }
-
-    public void createAccount() {
-        if (isInputValid()) {
-            net.jami.account.JamiAccountCreationView view = getView();
-            view.goToAccountCreation(mAccountCreationModel);
-        }
-    }
-
-    private boolean isInputValid() {
-        boolean passwordOk = isPasswordCorrect && isConfirmCorrect;
-        boolean usernameOk = mAccountCreationModel != null && mAccountCreationModel.getUsername() != null || isUsernameCorrect;
-        return passwordOk && usernameOk;
-    }
-
-    private void checkForms() {
-        boolean valid = isInputValid();
-        if(valid && isUsernameCorrect)
-            getView().updateUsernameAvailability(net.jami.account.JamiAccountCreationView.
-                    UsernameAvailabilityStatus.AVAILABLE);
-    }
-
-    private void onLookupResult(String name, String address, int state) {
-        net.jami.account.JamiAccountCreationView view = getView();
-        //Once we get the result, we can show the loading animation again when the user types
-        showLoadingAnimation = true;
-        if (view == null) {
-            return;
-        }
-        if (name == null || name.isEmpty()) {
-
-            view.updateUsernameAvailability(net.jami.account.JamiAccountCreationView.
-                    UsernameAvailabilityStatus.RESET);
-            isUsernameCorrect = false;
-        } else {
-            switch (state) {
-                case 0:
-                    // on found
-                    view.updateUsernameAvailability(net.jami.account.JamiAccountCreationView.
-                            UsernameAvailabilityStatus.ERROR_USERNAME_TAKEN);
-                    isUsernameCorrect = false;
-                    break;
-                case 1:
-                    // invalid name
-                    view.updateUsernameAvailability(net.jami.account.JamiAccountCreationView.
-                            UsernameAvailabilityStatus.ERROR_USERNAME_INVALID);
-                    isUsernameCorrect = false;
-                    break;
-                case 2:
-                    // available
-                    view.updateUsernameAvailability(net.jami.account.JamiAccountCreationView.
-                            UsernameAvailabilityStatus.AVAILABLE);
-                    mAccountCreationModel.setUsername(name);
-                    isUsernameCorrect = true;
-                    break;
-                default:
-                    // on error
-                    view.updateUsernameAvailability(JamiAccountCreationView.
-                            UsernameAvailabilityStatus.ERROR);
-                    isUsernameCorrect = false;
-                    break;
-            }
-        }
-        checkForms();
-    }
-
-    public void setPush(boolean push) {
-        if (mAccountCreationModel != null) {
-            mAccountCreationModel.setPush(push);
-        }
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt
new file mode 100644
index 000000000..315cfaa90
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt
@@ -0,0 +1,200 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.account
+
+import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.subjects.PublishSubject
+import net.jami.account.JamiAccountCreationPresenter
+import net.jami.mvp.AccountCreationModel
+import net.jami.mvp.RootPresenter
+import net.jami.services.AccountService
+import net.jami.services.AccountService.RegisteredName
+import net.jami.utils.StringUtils.isEmpty
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+
+class JamiAccountCreationPresenter @Inject constructor(
+    var mAccountService: AccountService,
+    @param:Named("UiScheduler") var mUiScheduler: Scheduler
+): RootPresenter<JamiAccountCreationView>() {
+    private val contactQuery = PublishSubject.create<String>()
+
+    private var mAccountCreationModel: AccountCreationModel? = null
+    private var isUsernameCorrect = false
+    private var isPasswordCorrect = true
+    private var isConfirmCorrect = true
+    private var showLoadingAnimation = true
+    private var mPasswordConfirm: CharSequence = ""
+
+    override fun bindView(view: JamiAccountCreationView) {
+        super.bindView(view)
+        mCompositeDisposable.add(contactQuery
+            .debounce(TYPING_DELAY, TimeUnit.MILLISECONDS)
+            .switchMapSingle { q: String? -> mAccountService.findRegistrationByName("", "", q!!) }
+            .observeOn(mUiScheduler)
+            .subscribe { q: RegisteredName -> onLookupResult(q.name, q.address, q.state) })
+    }
+
+    fun init(accountCreationModel: AccountCreationModel?) {
+        if (accountCreationModel == null) {
+            view?.cancel()
+        }
+        mAccountCreationModel = accountCreationModel
+    }
+
+    /**
+     * Called everytime the provided username for the new account changes
+     * Sends the new value of the username to the ContactQuery subjet and shows the loading
+     * animation if it has not been started before
+     */
+    fun userNameChanged(userName: String) {
+        if (mAccountCreationModel != null) mAccountCreationModel!!.username = userName
+        contactQuery.onNext(userName)
+        isUsernameCorrect = false
+        if (showLoadingAnimation) {
+            val view = view
+            view?.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.LOADING)
+            showLoadingAnimation = false
+        }
+    }
+
+    fun registerUsernameChanged(isChecked: Boolean) {
+        if (mAccountCreationModel != null) {
+            if (!isChecked) {
+                mAccountCreationModel!!.username = ""
+            }
+            checkForms()
+        }
+    }
+
+    fun passwordUnset() {
+        if (mAccountCreationModel != null) mAccountCreationModel!!.password = null
+        isPasswordCorrect = true
+        isConfirmCorrect = true
+        view!!.showInvalidPasswordError(false)
+        view!!.enableNextButton(true)
+    }
+
+    fun passwordChanged(password: String, repeat: CharSequence) {
+        mPasswordConfirm = repeat
+        passwordChanged(password)
+    }
+
+    fun passwordChanged(password: String) {
+        if (mAccountCreationModel != null) mAccountCreationModel!!.password = password
+        if (!isEmpty(password) && password.length < PASSWORD_MIN_LENGTH) {
+            view!!.showInvalidPasswordError(true)
+            isPasswordCorrect = false
+        } else {
+            view!!.showInvalidPasswordError(false)
+            isPasswordCorrect = password.length != 0
+            isConfirmCorrect = if (!password.contentEquals(mPasswordConfirm)) {
+                if (mPasswordConfirm.length > 0) view!!.showNonMatchingPasswordError(true)
+                false
+            } else {
+                view!!.showNonMatchingPasswordError(false)
+                true
+            }
+        }
+        view!!.enableNextButton(isPasswordCorrect && isConfirmCorrect)
+    }
+
+    fun passwordConfirmChanged(passwordConfirm: String) {
+        isConfirmCorrect = if (passwordConfirm != mAccountCreationModel!!.password) {
+            view!!.showNonMatchingPasswordError(true)
+            false
+        } else {
+            view!!.showNonMatchingPasswordError(false)
+            true
+        }
+        mPasswordConfirm = passwordConfirm
+        view!!.enableNextButton(isPasswordCorrect && isConfirmCorrect)
+    }
+
+    fun createAccount() {
+        if (isInputValid) {
+            val view = view
+            view!!.goToAccountCreation(mAccountCreationModel)
+        }
+    }
+
+    private val isInputValid: Boolean
+        private get() {
+            val passwordOk = isPasswordCorrect && isConfirmCorrect
+            val usernameOk =
+                mAccountCreationModel != null && mAccountCreationModel!!.username != null || isUsernameCorrect
+            return passwordOk && usernameOk
+        }
+
+    private fun checkForms() {
+        val valid = isInputValid
+        if (valid && isUsernameCorrect) view!!.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.AVAILABLE)
+    }
+
+    private fun onLookupResult(name: String?, address: String?, state: Int) {
+        val view = view
+        //Once we get the result, we can show the loading animation again when the user types
+        showLoadingAnimation = true
+        if (view == null) {
+            return
+        }
+        if (name == null || name.isEmpty()) {
+            view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.RESET)
+            isUsernameCorrect = false
+        } else {
+            when (state) {
+                0 -> {
+                    // on found
+                    view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.ERROR_USERNAME_TAKEN)
+                    isUsernameCorrect = false
+                }
+                1 -> {
+                    // invalid name
+                    view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.ERROR_USERNAME_INVALID)
+                    isUsernameCorrect = false
+                }
+                2 -> {
+                    // available
+                    view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.AVAILABLE)
+                    mAccountCreationModel!!.username = name
+                    isUsernameCorrect = true
+                }
+                else -> {
+                    // on error
+                    view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.ERROR)
+                    isUsernameCorrect = false
+                }
+            }
+        }
+        checkForms()
+    }
+
+    fun setPush(push: Boolean) {
+        mAccountCreationModel!!.isPush = push
+    }
+
+    companion object {
+        val TAG = JamiAccountCreationPresenter::class.java.simpleName
+        private const val PASSWORD_MIN_LENGTH = 6
+        private const val TYPING_DELAY = 350L
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt
similarity index 58%
rename from ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.java
rename to ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt
index 1f7556cd9..1bb996865 100644
--- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.java
+++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt
@@ -17,30 +17,19 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package net.jami.account;
+package net.jami.account
 
-import net.jami.mvp.AccountCreationModel;
+import net.jami.mvp.AccountCreationModel
 
-public interface JamiAccountCreationView {
-
-    enum UsernameAvailabilityStatus {
-        ERROR_USERNAME_TAKEN,
-        ERROR_USERNAME_INVALID,
-        ERROR,
-        LOADING,
-        AVAILABLE,
-        RESET
+interface JamiAccountCreationView {
+    enum class UsernameAvailabilityStatus {
+        ERROR_USERNAME_TAKEN, ERROR_USERNAME_INVALID, ERROR, LOADING, AVAILABLE, RESET
     }
 
-    void updateUsernameAvailability(UsernameAvailabilityStatus status);
-
-    void showInvalidPasswordError(boolean display);
-
-    void showNonMatchingPasswordError(boolean display);
-
-    void enableNextButton(boolean enabled);
-
-    void goToAccountCreation(AccountCreationModel accountCreationModel);
-
-    void cancel();
-}
+    fun updateUsernameAvailability(status: UsernameAvailabilityStatus?)
+    fun showInvalidPasswordError(display: Boolean)
+    fun showNonMatchingPasswordError(display: Boolean)
+    fun enableNextButton(enabled: Boolean)
+    fun goToAccountCreation(accountCreationModel: AccountCreationModel?)
+    fun cancel()
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java
index 88dd843b0..7d76a0fde 100644
--- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java
@@ -31,6 +31,7 @@ import javax.inject.Inject;
 import javax.inject.Named;
 
 import net.jami.mvp.RootPresenter;
+import net.jami.services.VCardService;
 import net.jami.utils.Log;
 import net.jami.utils.StringUtils;
 import net.jami.utils.VCardUtils;
@@ -42,12 +43,14 @@ import io.reactivex.rxjava3.core.Scheduler;
 import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
-public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.JamiAccountSummaryView> {
+public class JamiAccountSummaryPresenter extends RootPresenter<JamiAccountSummaryView> {
 
     private static final String TAG = JamiAccountSummaryPresenter.class.getSimpleName();
 
-    private final net.jami.services.DeviceRuntimeService mDeviceRuntimeService;
-    private final net.jami.services.AccountService mAccountService;
+    private final DeviceRuntimeService mDeviceRuntimeService;
+    private final AccountService mAccountService;
+    private final VCardService mVcardService;
+
     private String mAccountID;
 
     @Inject
@@ -56,9 +59,11 @@ public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.
 
     @Inject
     public JamiAccountSummaryPresenter(AccountService accountService,
-                                       DeviceRuntimeService deviceRuntimeService) {
+                                       DeviceRuntimeService deviceRuntimeService,
+                                       VCardService vcardService) {
         mAccountService = accountService;
         mDeviceRuntimeService = deviceRuntimeService;
+        mVcardService = vcardService;
     }
 
     public void registerName(String name, String password) {
@@ -93,14 +98,14 @@ public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.
     public void setAccountId(String accountID) {
         mCompositeDisposable.clear();
         mAccountID = accountID;
-        net.jami.account.JamiAccountSummaryView v = getView();
+        JamiAccountSummaryView v = getView();
         Account account = mAccountService.getAccount(mAccountID);
         if (v != null && account != null)
             v.accountChanged(account);
         mCompositeDisposable.add(mAccountService.getObservableAccountUpdates(mAccountID)
                 .observeOn(mUiScheduler)
                 .subscribe(a -> {
-                    net.jami.account.JamiAccountSummaryView view = getView();
+                    JamiAccountSummaryView view = getView();
                     if (view != null)
                         view.accountChanged(a);
                 }));
@@ -118,7 +123,7 @@ public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.
     }
 
     public void changePassword(String oldPassword, String newPassword) {
-        net.jami.account.JamiAccountSummaryView view = getView();
+        JamiAccountSummaryView view = getView();
         if (view != null)
             view.showPasswordProgressDialog();
         mCompositeDisposable.add(mAccountService.setAccountPassword(mAccountID, oldPassword, newPassword)
@@ -158,9 +163,7 @@ public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.
                 .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, mAccountID, filesDir))
                 .subscribeOn(Schedulers.io())
                 .subscribe(vcard -> {
-                    account.resetProfile();
-                    mAccountService.refreshAccounts();
-                    getView().updateUserView(account);
+                    account.setLoadedProfile(mVcardService.loadVCardProfile(vcard).cache());
                 }, e -> Log.e(TAG, "Error saving vCard !", e)));
     }
 
@@ -183,9 +186,7 @@ public class JamiAccountSummaryPresenter extends RootPresenter<net.jami.account.
                 .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, mAccountID, filesDir))
                 .subscribeOn(Schedulers.io())
                 .subscribe(vcard -> {
-                    account.resetProfile();
-                    mAccountService.refreshAccounts();
-                    getView().updateUserView(account);
+                    account.setLoadedProfile(mVcardService.loadVCardProfile(vcard).cache());
                 }, e -> Log.e(TAG, "Error saving vCard !", e)));
     }
 
diff --git a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java
index ab2fb1e7d..ef03a12ce 100644
--- a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java
@@ -20,12 +20,13 @@
 package net.jami.account;
 
 import net.jami.mvp.AccountCreationModel;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 import net.jami.mvp.RootPresenter;
+import net.jami.services.DeviceRuntimeService;
+import net.jami.services.HardwareService;
 import net.jami.utils.Log;
 
 import io.reactivex.rxjava3.core.Scheduler;
@@ -35,14 +36,16 @@ public class ProfileCreationPresenter extends RootPresenter<net.jami.account.Pro
 
     public static final String TAG = ProfileCreationPresenter.class.getSimpleName();
 
-    private final net.jami.services.DeviceRuntimeService mDeviceRuntimeService;
-    private final net.jami.services.HardwareService mHardwareService;
+    private final DeviceRuntimeService mDeviceRuntimeService;
+    private final HardwareService mHardwareService;
     private final Scheduler mUiScheduler;
 
     private net.jami.mvp.AccountCreationModel mAccountCreationModel;
 
     @Inject
-    public ProfileCreationPresenter(DeviceRuntimeService deviceRuntimeService, HardwareService hardwareService, Scheduler uiScheduler) {
+    public ProfileCreationPresenter(DeviceRuntimeService deviceRuntimeService,
+                                    HardwareService hardwareService,
+                                    @Named("UiScheduler") Scheduler uiScheduler) {
         mDeviceRuntimeService = deviceRuntimeService;
         mHardwareService = hardwareService;
         mUiScheduler = uiScheduler;
diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java
deleted file mode 100644
index 42490c998..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.call;
-
-import net.jami.daemon.JamiService;
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Call;
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.ConversationHistory;
-import net.jami.model.Uri;
-import net.jami.mvp.RootPresenter;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.ContactService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import io.reactivex.rxjava3.annotations.NonNull;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Observer;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class CallPresenter extends RootPresenter<CallView> {
-
-    public final static String TAG = CallPresenter.class.getSimpleName();
-
-    private final AccountService mAccountService;
-    private final ContactService mContactService;
-    private final HardwareService mHardwareService;
-    private final CallService mCallService;
-    private final DeviceRuntimeService mDeviceRuntimeService;
-    private final ConversationFacade mConversationFacade;
-
-    private Conference mConference;
-    private final List<Call> mPendingCalls = new ArrayList<>();
-    private final Subject<List<Call>> mPendingSubject = BehaviorSubject.createDefault(mPendingCalls);
-
-    private boolean mOnGoingCall = false;
-    private boolean mAudioOnly = true;
-    private boolean permissionChanged = false;
-    private boolean pipIsActive = false;
-    private boolean incomingIsFullIntent = true;
-    private boolean callInitialized = false;
-
-    private int videoWidth = -1;
-    private int videoHeight = -1;
-    private int previewWidth = -1;
-    private int previewHeight = -1;
-    private String currentSurfaceId = null;
-    private String currentPluginSurfaceId = null;
-
-    private Disposable timeUpdateTask = null;
-
-    @Inject
-    @Named("UiScheduler")
-    protected Scheduler mUiScheduler;
-
-    @Inject
-    public CallPresenter(AccountService accountService,
-                         ContactService contactService,
-                         HardwareService hardwareService,
-                         CallService callService,
-                         DeviceRuntimeService deviceRuntimeService,
-                         ConversationFacade conversationFacade) {
-        mAccountService = accountService;
-        mContactService = contactService;
-        mHardwareService = hardwareService;
-        mCallService = callService;
-        mDeviceRuntimeService = deviceRuntimeService;
-        mConversationFacade = conversationFacade;
-    }
-
-    public void cameraPermissionChanged(boolean isGranted) {
-        if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo()
-                    .onErrorComplete()
-                    .blockingAwait();
-            permissionChanged = true;
-        }
-    }
-
-    public void audioPermissionChanged(boolean isGranted) {
-        if (isGranted && mHardwareService.hasMicrophone()) {
-            mCallService.restartAudioLayer();
-        }
-    }
-
-
-    @Override
-    public void unbindView() {
-        if (!mAudioOnly) {
-            mHardwareService.endCapture();
-        }
-        super.unbindView();
-    }
-
-    @Override
-    public void bindView(CallView view) {
-        super.bindView(view);
-        /*mCompositeDisposable.add(mAccountService.getRegisteredNames()
-                .observeOn(mUiScheduler)
-                .subscribe(r -> {
-                    if (mSipCall != null && mSipCall.getContact() != null) {
-                        getView().updateContactBubble(mSipCall.getContact());
-                    }
-                }));*/
-        mCompositeDisposable.add(mHardwareService.getVideoEvents()
-                .observeOn(mUiScheduler)
-                .subscribe(this::onVideoEvent));
-        mCompositeDisposable.add(mHardwareService.getAudioState()
-                .observeOn(mUiScheduler)
-                .subscribe(state -> getView().updateAudioState(state)));
-
-        /*mCompositeDisposable.add(mHardwareService
-                .getBluetoothEvents()
-                .subscribe(event -> {
-                    if (!event.connected && mSipCall == null) {
-                        hangupCall();
-                    }
-                }));*/
-    }
-
-    public void initOutGoing(String accountId, Uri conversationUri, String contactUri, boolean audioOnly) {
-        if (accountId == null || contactUri == null) {
-            Log.e(TAG, "initOutGoing: null account or contact");
-            hangupCall();
-            return;
-        }
-        if (!mHardwareService.hasCamera()) {
-            audioOnly = true;
-        }
-        //getView().blockScreenRotation();
-
-        Observable<Conference> callObservable = mCallService
-                .placeCall(accountId, conversationUri, Uri.fromString(StringUtils.toNumber(contactUri)), audioOnly)
-                //.map(mCallService::getConference)
-                .flatMapObservable(mCallService::getConfUpdates)
-                .share();
-
-        mCompositeDisposable.add(callObservable
-                .observeOn(mUiScheduler)
-                .subscribe(conference -> {
-                    contactUpdate(conference);
-                    confUpdate(conference);
-                }, e -> {
-                    hangupCall();
-                    Log.e(TAG, "Error with initOutgoing: " + e.getMessage());
-                }));
-
-        showConference(callObservable);
-    }
-
-    /**
-     * Returns to or starts an incoming call
-     *
-     * @param confId         the call id
-     * @param actionViewOnly true if only returning to call or if using full screen intent
-     */
-    public void initIncomingCall(String confId, boolean actionViewOnly) {
-        //getView().blockScreenRotation();
-
-        // if the call is incoming through a full intent, this allows the incoming call to display
-        incomingIsFullIntent = actionViewOnly;
-
-        Observable<Conference> callObservable = mCallService.getConfUpdates(confId)
-                .observeOn(mUiScheduler)
-                .share();
-
-        // Handles the case where the call has been accepted, emits a single so as to only check for permissions and start the call once
-        mCompositeDisposable.add(callObservable
-                .firstOrError()
-                .subscribe(call -> {
-                    if (!actionViewOnly) {
-                        contactUpdate(call);
-                        confUpdate(call);
-                        callInitialized = true;
-                        getView().prepareCall(true);
-                    }
-                }, e -> {
-                    hangupCall();
-                    Log.e(TAG, "Error with initIncoming, preparing call flow :" , e);
-                }));
-
-        // Handles retrieving call updates. Items emitted are only used if call is already in process or if user is returning to a call.
-        mCompositeDisposable.add(callObservable
-                .subscribe(call -> {
-                    if (callInitialized || actionViewOnly) {
-                        contactUpdate(call);
-                        confUpdate(call);
-                    }
-                }, e -> {
-                    hangupCall();
-                    Log.e(TAG, "Error with initIncoming, action view flow: ", e);
-                }));
-
-        showConference(callObservable);
-    }
-
-    private void showConference(Observable<Conference> conference) {
-        conference = conference
-                .distinctUntilChanged();
-        mCompositeDisposable.add(conference
-                .switchMap(Conference::getParticipantInfo)
-                .observeOn(mUiScheduler)
-                .subscribe(info -> getView().updateConfInfo(info),
-                        e -> Log.e(TAG, "Error with initIncoming, action view flow: ", e)));
-
-        mCompositeDisposable.add(conference
-                .switchMap(Conference::getParticipantRecording)
-                .observeOn(mUiScheduler)
-                .subscribe(contacts -> getView().updateParticipantRecording(contacts),
-                        e -> Log.e(TAG, "Error with initIncoming, action view flow: ", e)));
-    }
-
-    public void prepareOptionMenu() {
-        boolean isSpeakerOn = mHardwareService.isSpeakerPhoneOn();
-        //boolean hasContact = mSipCall != null && null != mSipCall.getContact() && mSipCall.getContact().isUnknown();
-        boolean canDial = mOnGoingCall && mConference != null;
-        // get the preferences
-        boolean displayPluginsButton = getView().displayPluginsButton();
-        boolean showPluginBtn = displayPluginsButton && mOnGoingCall && mConference != null;
-        boolean hasMultipleCamera = mHardwareService.getCameraCount() > 1 && mOnGoingCall && !mAudioOnly;
-        getView().initMenu(isSpeakerOn, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall);
-    }
-
-    public void chatClick() {
-        if (mConference == null || mConference.getParticipants().isEmpty()) {
-            return;
-        }
-        Call firstCall = mConference.getParticipants().get(0);
-        if (firstCall == null) {
-            return;
-        }
-        ConversationHistory c = firstCall.getConversation();
-        if (c instanceof Conversation) {
-            Conversation conversation = ((Conversation) c);
-            getView().goToConversation(conversation.getAccountId(), conversation.getUri());
-        } else if (firstCall.getContact() != null) {
-            getView().goToConversation(firstCall.getAccount(), firstCall.getContact().getConversationUri().blockingFirst());
-        }
-    }
-
-    public void speakerClick(boolean checked) {
-        mHardwareService.toggleSpeakerphone(checked);
-    }
-
-    public void muteMicrophoneToggled(boolean checked) {
-        mCallService.setLocalMediaMuted(mConference.getId(), CallService.MEDIA_TYPE_AUDIO, checked);
-    }
-
-
-    public boolean isMicrophoneMuted() {
-        return mCallService.isCaptureMuted();
-    }
-
-    public void switchVideoInputClick() {
-        if(mConference == null)
-            return;
-        mHardwareService.switchInput(mConference.getId(), false);
-        getView().switchCameraIcon(mHardwareService.isPreviewFromFrontCamera());
-    }
-
-    public void configurationChanged(int rotation) {
-        mHardwareService.setDeviceOrientation(rotation);
-    }
-
-    public void dialpadClick() {
-        getView().displayDialPadKeyboard();
-    }
-
-    public void acceptCall() {
-        if (mConference == null) {
-            return;
-        }
-        mCallService.accept(mConference.getId());
-    }
-
-    public void hangupCall() {
-        if (mConference != null) {
-            if (mConference.isConference())
-                mCallService.hangUpConference(mConference.getId());
-            else
-                mCallService.hangUp(mConference.getId());
-        }
-        for (Call call : mPendingCalls) {
-            mCallService.hangUp(call.getDaemonIdString());
-        }
-        finish();
-    }
-
-    public void refuseCall() {
-        final Conference call = mConference;
-        if (call != null) {
-            mCallService.refuse(call.getId());
-        }
-        finish();
-    }
-
-    public void videoSurfaceCreated(Object holder) {
-        if (mConference == null) {
-            return;
-        }
-        String newId = mConference.getId();
-        if (!newId.equals(currentSurfaceId)) {
-            mHardwareService.removeVideoSurface(currentSurfaceId);
-            currentSurfaceId = newId;
-        }
-        mHardwareService.addVideoSurface(mConference.getId(), holder);
-        getView().displayContactBubble(false);
-    }
-
-    public void videoSurfaceUpdateId(String newId) {
-        if (!Objects.equals(newId, currentSurfaceId)) {
-            mHardwareService.updateVideoSurfaceId(currentSurfaceId, newId);
-            currentSurfaceId = newId;
-        }
-    }
-
-    public void pluginSurfaceCreated(Object holder) {
-        if (mConference == null) {
-            return;
-        }
-        String newId = mConference.getPluginId();
-        if (!newId.equals(currentPluginSurfaceId)) {
-            mHardwareService.removeVideoSurface(currentPluginSurfaceId);
-            currentPluginSurfaceId = newId;
-        }
-        mHardwareService.addVideoSurface(mConference.getPluginId(), holder);
-        getView().displayContactBubble(false);
-    }
-
-    public void pluginSurfaceUpdateId(String newId) {
-        if (!Objects.equals(newId, currentPluginSurfaceId)) {
-            mHardwareService.updateVideoSurfaceId(currentPluginSurfaceId, newId);
-            currentPluginSurfaceId = newId;
-        }
-    }
-
-    public void previewVideoSurfaceCreated(Object holder) {
-        mHardwareService.addPreviewVideoSurface(holder, mConference);
-        //mHardwareService.startCapture(null);
-    }
-
-    public void videoSurfaceDestroyed() {
-        if (currentSurfaceId != null) {
-            mHardwareService.removeVideoSurface(currentSurfaceId);
-            currentSurfaceId = null;
-        }
-    }
-    public void pluginSurfaceDestroyed() {
-        if (currentPluginSurfaceId != null) {
-            mHardwareService.removeVideoSurface(currentPluginSurfaceId);
-            currentPluginSurfaceId = null;
-        }
-    }
-    public void previewVideoSurfaceDestroyed() {
-        mHardwareService.removePreviewVideoSurface();
-        mHardwareService.endCapture();
-    }
-
-    public void displayChanged() {
-        mHardwareService.switchInput(mConference.getId(), false);
-    }
-
-    public void layoutChanged() {
-        //getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
-    }
-
-
-    public void uiVisibilityChanged(boolean displayed) {
-        Log.w(TAG, "uiVisibilityChanged " + mOnGoingCall + " "  + displayed);
-        CallView view = getView();
-        if (view != null)
-            view.displayHangupButton(mOnGoingCall && displayed);
-    }
-
-    private void finish() {
-        if (timeUpdateTask != null && !timeUpdateTask.isDisposed()) {
-            timeUpdateTask.dispose();
-            timeUpdateTask = null;
-        }
-        mConference = null;
-        CallView view = getView();
-        if (view != null)
-            view.finish();
-    }
-
-    private Disposable contactDisposable = null;
-
-    private void contactUpdate(final Conference conference) {
-        if (mConference != conference) {
-            mConference = conference;
-            if (contactDisposable != null && !contactDisposable.isDisposed()) {
-                contactDisposable.dispose();
-            }
-            if (conference.getParticipants().isEmpty())
-                return;
-
-            // Updates of participant (and  pending participant) list
-            Observable<List<Call>> callsObservable = mPendingSubject
-                    .map(pendingList -> {
-                        Log.w(TAG, "mPendingSubject onNext " + pendingList.size() + " " + conference.getParticipants().size());
-                        if (pendingList.isEmpty())
-                            return conference.getParticipants();
-                        List<Call> newList = new ArrayList<>(conference.getParticipants().size() + pendingList.size());
-                        newList.addAll(conference.getParticipants());
-                        newList.addAll(pendingList);
-                        return newList;
-                    });
-
-            // Updates of individual contacts
-            Observable<List<Observable<Call>>> contactsObservable = callsObservable
-                    .flatMapSingle(calls -> Observable
-                            .fromIterable(calls)
-                            .map(call -> mContactService.observeContact(call.getAccount(), call.getContact(), false)
-                                    .map(contact -> call))
-                            .toList(calls.size()));
-
-            // Combined updates of contacts as participant list updates
-            Observable<List<Call>> contactUpdates = contactsObservable
-                    .switchMap(list -> Observable
-                            .combineLatest(list, objects -> {
-                                Log.w(TAG, "flatMapObservable " + objects.length);
-                                ArrayList<Call> calls = new ArrayList<>(objects.length);
-                                for (Object call : objects)
-                                    calls.add((Call)call);
-                                return (List<Call>)calls;
-                            }))
-                    .filter(list -> !list.isEmpty());
-
-            contactDisposable = contactUpdates
-                    .observeOn(mUiScheduler)
-                    .subscribe(cs -> getView().updateContactBubble(cs), e -> Log.e(TAG, "Error updating contact data", e));
-            mCompositeDisposable.add(contactDisposable);
-        }
-        mPendingSubject.onNext(mPendingCalls);
-    }
-
-    private void confUpdate(Conference call) {
-        Log.w(TAG, "confUpdate " + call.getId() + " " + call.getState());
-
-        Call.CallStatus status = call.getState();
-        if (status == Call.CallStatus.HOLD) {
-            if (call.isSimpleCall())
-                mCallService.unhold(call.getId());
-            else
-                JamiService.addMainParticipant(call.getConfId());
-        }
-        mAudioOnly = !call.hasVideo();
-        CallView view = getView();
-        if (view == null)
-            return;
-        view.updateMenu();
-        if (call.isOnGoing()) {
-            mOnGoingCall = true;
-            view.initNormalStateDisplay(mAudioOnly, isMicrophoneMuted());
-            view.updateMenu();
-            if (!mAudioOnly) {
-                mHardwareService.setPreviewSettings();
-                mHardwareService.updatePreviewVideoSurface(call);
-                videoSurfaceUpdateId(call.getId());
-                pluginSurfaceUpdateId(call.getPluginId());
-                view.displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission());
-                if (permissionChanged) {
-                    mHardwareService.switchInput(mConference.getId(), permissionChanged);
-                    permissionChanged = false;
-                }
-            }
-            if (timeUpdateTask != null)
-                timeUpdateTask.dispose();
-            timeUpdateTask = mUiScheduler.schedulePeriodicallyDirect(this::updateTime, 0, 1, TimeUnit.SECONDS);
-        } else if (call.isRinging()) {
-            Call scall = call.getCall();
-
-            view.handleCallWakelock(mAudioOnly);
-            if (scall.isIncoming()) {
-                if (mAccountService.getAccount(scall.getAccount()).isAutoanswerEnabled()) {
-                    mCallService.accept(scall.getDaemonIdString());
-                    // only display the incoming call screen if the notification is a full screen intent
-                } else if (incomingIsFullIntent) {
-                    view.initIncomingCallDisplay();
-                }
-            } else {
-                mOnGoingCall = false;
-                view.updateCallStatus(scall.getCallStatus());
-                view.initOutGoingCallDisplay();
-            }
-        } else {
-            finish();
-        }
-    }
-
-    public void maximizeParticipant(Conference.ParticipantInfo info) {
-        Contact contact = info == null ? null : info.contact;
-        if (mConference.getMaximizedParticipant() == contact)
-            info = null;
-        mConference.setMaximizedParticipant(contact);
-        if (info != null) {
-            mCallService.setConfMaximizedParticipant(mConference.getConfId(), info.contact.getUri());
-        } else {
-            mCallService.setConfGridLayout(mConference.getConfId());
-        }
-    }
-
-    private void updateTime() {
-        CallView view = getView();
-        if (view != null && mConference != null) {
-            if (mConference.isOnGoing()) {
-                long start = mConference.getTimestampStart();
-                if (start != Long.MAX_VALUE) {
-                    view.updateTime((System.currentTimeMillis() - start) / 1000);
-                } else {
-                    view.updateTime(-1);
-                }
-            }
-        }
-    }
-
-    private void onVideoEvent(HardwareService.VideoEvent event) {
-        Log.d(TAG, "VIDEO_EVENT: " + event.start + " " + event.callId + " " + event.w + "x" + event.h);
-
-        if (event.start) {
-            getView().displayVideoSurface(true, !isPipMode() && mDeviceRuntimeService.hasVideoPermission());
-        } else if (mConference != null && mConference.getId().equals(event.callId)) {
-            getView().displayVideoSurface(event.started, event.started && !isPipMode() && mDeviceRuntimeService.hasVideoPermission());
-            if (event.started) {
-                videoWidth = event.w;
-                videoHeight = event.h;
-                getView().resetVideoSize(videoWidth, videoHeight);
-            }
-        } else if (event.callId == null) {
-            if (event.started) {
-                previewWidth = event.w;
-                previewHeight = event.h;
-                getView().resetPreviewVideoSize(previewWidth, previewHeight, event.rot);
-            }
-        }
-        if (mConference != null && mConference.getPluginId().equals(event.callId)) {
-            if (event.started) {
-                previewWidth = event.w;
-                previewHeight = event.h;
-                getView().resetPluginPreviewVideoSize(previewWidth, previewHeight, event.rot);
-            }
-        }
-        /*if (event.started || event.start) {
-            getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
-        }*/
-    }
-
-    public void positiveButtonClicked() {
-        if (mConference.isRinging() && mConference.isIncoming()) {
-            acceptCall();
-        } else {
-            hangupCall();
-        }
-    }
-
-    public void negativeButtonClicked() {
-        if (mConference.isRinging() && mConference.isIncoming()) {
-            refuseCall();
-        } else {
-            hangupCall();
-        }
-    }
-
-    public void toggleButtonClicked() {
-        if (mConference != null && !(mConference.isRinging() && mConference.isIncoming())) {
-            hangupCall();
-        }
-    }
-
-    public boolean isAudioOnly() {
-        return mAudioOnly;
-    }
-
-    public void requestPipMode() {
-        if (mConference != null && mConference.isOnGoing() && mConference.hasVideo()) {
-            getView().enterPipMode(mConference.getId());
-        }
-    }
-
-    public boolean isPipMode() {
-        return pipIsActive;
-    }
-
-    public void pipModeChanged(boolean pip) {
-        pipIsActive = pip;
-        if (pip) {
-            getView().displayHangupButton(false);
-            getView().displayPreviewSurface(false);
-            getView().displayVideoSurface(true, false);
-        } else {
-            getView().displayPreviewSurface(true);
-            getView().displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission());
-        }
-    }
-
-    public void toggleCallMediaHandler(String id, boolean toggle)
-    {
-        if (mConference != null && mConference.isOnGoing() && mConference.hasVideo()) {
-            getView().toggleCallMediaHandler(id, mConference.getId(), toggle);
-        }
-    }
-
-    public boolean isSpeakerphoneOn() {
-        return mHardwareService.isSpeakerPhoneOn();
-    }
-
-    public void sendDtmf(CharSequence s) {
-        mCallService.playDtmf(s.toString());
-    }
-
-    public void addConferenceParticipant(String accountId, Uri contactUri) {
-        mCompositeDisposable.add(mConversationFacade.startConversation(accountId, contactUri)
-                .map(Conversation::getCurrentCalls)
-                .subscribe(confs -> {
-                    if (confs.isEmpty()) {
-                        final Observer<Call> pendingObserver = new Observer<Call>() {
-                            private Call call = null;
-
-                            @Override
-                            public void onSubscribe(@NonNull Disposable d) {}
-
-                            @Override
-                            public void onNext(@NonNull Call sipCall) {
-                                if (call == null) {
-                                    call = sipCall;
-                                    mPendingCalls.add(sipCall);
-                                }
-                                mPendingSubject.onNext(mPendingCalls);
-                            }
-
-                            @Override
-                            public void onError(Throwable e) {}
-
-                            @Override
-                            public void onComplete() {
-                                if (call != null) {
-                                    mPendingCalls.remove(call);
-                                    mPendingSubject.onNext(mPendingCalls);
-                                    call = null;
-                                }
-                            }
-                        };
-
-                        // Place new call, join to conference when answered
-                        Maybe<Call> newCall = mCallService.placeCallObservable(accountId, null, contactUri, mAudioOnly)
-                                .doOnEach(pendingObserver)
-                                .filter(Call::isOnGoing)
-                                .firstElement()
-                                .delay(1, TimeUnit.SECONDS)
-                                .doOnEvent((v, e) -> pendingObserver.onComplete());
-                        mCompositeDisposable.add(newCall.subscribe(call ->  {
-                            String id = mConference.getId();
-                            if (mConference.isConference()) {
-                                mCallService.addParticipant(call.getDaemonIdString(), id);
-                            } else {
-                                mCallService.joinParticipant(id, call.getDaemonIdString()).subscribe();
-                            }
-                        }));
-                    } else {
-                        // Selected contact already in call or conference, join it to current conference
-                        Conference selectedConf = confs.get(0);
-                        if (selectedConf != mConference) {
-                            if (mConference.isConference()) {
-                                if (selectedConf.isConference())
-                                    mCallService.joinConference(mConference.getId(), selectedConf.getId());
-                                else
-                                    mCallService.addParticipant(selectedConf.getId(), mConference.getId());
-                            } else {
-                                if (selectedConf.isConference())
-                                    mCallService.addParticipant(mConference.getId(), selectedConf.getId());
-                                else
-                                    mCallService.joinParticipant(mConference.getId(), selectedConf.getId()).subscribe();
-                            }
-                        }
-                    }
-                }));
-    }
-
-    public void startAddParticipant() {
-        getView().startAddParticipant(mConference.getId());
-    }
-
-    public void hangupParticipant(Conference.ParticipantInfo info) {
-        if (info.call != null)
-            mCallService.hangUp(info.call.getDaemonIdString());
-        else
-            mCallService.hangupParticipant(mConference.getId(), info.contact.getPrimaryNumber());
-    }
-
-    public void muteParticipant(Conference.ParticipantInfo info, boolean mute) {
-        mCallService.muteParticipant(mConference.getId(), info.contact.getPrimaryNumber(), mute);
-    }
-
-    public void openParticipantContact(Conference.ParticipantInfo info) {
-        Call call = info.call == null ? mConference.getFirstCall() : info.call;
-        getView().goToContact(call.getAccount(), info.contact);
-    }
-
-    public void stopCapture() {
-        mHardwareService.stopCapture();
-    }
-
-    public boolean startScreenShare(Object mediaProjection) {
-        return mHardwareService.startScreenShare(mediaProjection);
-    }
-
-    public void stopScreenShare() {
-        mHardwareService.stopScreenShare();
-    }
-
-    public boolean isMaximized(Conference.ParticipantInfo info) {
-        return mConference.getMaximizedParticipant() == info.contact;
-    }
-
-    public void startPlugin(String mediaHandlerId) {
-        mHardwareService.startMediaHandler(mediaHandlerId);
-        if(mConference == null)
-            return;
-        mHardwareService.switchInput(mConference.getId(), mHardwareService.isPreviewFromFrontCamera());
-    }
-
-    public void stopPlugin() {
-        mHardwareService.stopMediaHandler();
-        if(mConference == null)
-            return;
-        mHardwareService.switchInput(mConference.getId(), mHardwareService.isPreviewFromFrontCamera());
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt
new file mode 100644
index 000000000..da0acdd51
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt
@@ -0,0 +1,698 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.call
+
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Observer
+import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.daemon.JamiService
+import net.jami.services.ConversationFacade
+import net.jami.model.*
+import net.jami.model.Call.CallStatus
+import net.jami.model.Conference.ParticipantInfo
+import net.jami.model.Uri.Companion.fromString
+import net.jami.mvp.RootPresenter
+import net.jami.services.*
+import net.jami.services.HardwareService.AudioState
+import net.jami.services.HardwareService.VideoEvent
+import net.jami.utils.Log
+import net.jami.utils.StringUtils.toNumber
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+
+class CallPresenter @Inject constructor(
+    private val mAccountService: AccountService,
+    private val mContactService: ContactService,
+    private val mHardwareService: HardwareService,
+    private val mCallService: CallService,
+    private val mDeviceRuntimeService: DeviceRuntimeService,
+    private val mConversationFacade: ConversationFacade
+) : RootPresenter<CallView>() {
+    private var mConference: Conference? = null
+    private val mPendingCalls: MutableList<Call> = ArrayList()
+    private val mPendingSubject: Subject<List<Call>> = BehaviorSubject.createDefault(mPendingCalls)
+    private var mOnGoingCall = false
+    var isAudioOnly = true
+        private set
+    private var permissionChanged = false
+    var isPipMode = false
+        private set
+    private var incomingIsFullIntent = true
+    private var callInitialized = false
+    private var videoWidth = -1
+    private var videoHeight = -1
+    private var previewWidth = -1
+    private var previewHeight = -1
+    private var currentSurfaceId: String? = null
+    private var currentPluginSurfaceId: String? = null
+    private var timeUpdateTask: Disposable? = null
+
+    @Inject
+    @Named("UiScheduler")
+    lateinit var mUiScheduler: Scheduler
+
+    fun cameraPermissionChanged(isGranted: Boolean) {
+        if (isGranted && mHardwareService.isVideoAvailable) {
+            mHardwareService.initVideo()
+                .onErrorComplete()
+                .blockingAwait()
+            permissionChanged = true
+        }
+    }
+
+    fun audioPermissionChanged(isGranted: Boolean) {
+        if (isGranted && mHardwareService.hasMicrophone()) {
+            mCallService.restartAudioLayer()
+        }
+    }
+
+    override fun unbindView() {
+        if (!isAudioOnly) {
+            mHardwareService.endCapture()
+        }
+        super.unbindView()
+    }
+
+    override fun bindView(view: CallView) {
+        super.bindView(view)
+        /*mCompositeDisposable.add(mAccountService.getRegisteredNames()
+                .observeOn(mUiScheduler)
+                .subscribe(r -> {
+                    if (mSipCall != null && mSipCall.getContact() != null) {
+                        getView().updateContactBubble(mSipCall.getContact());
+                    }
+                }));*/mCompositeDisposable.add(mHardwareService.getVideoEvents()
+            .observeOn(mUiScheduler)
+            .subscribe { event: VideoEvent -> onVideoEvent(event) })
+        mCompositeDisposable.add(mHardwareService.audioState
+            .observeOn(mUiScheduler)
+            .subscribe { state: AudioState? -> getView()!!.updateAudioState(state) })
+
+        /*mCompositeDisposable.add(mHardwareService
+                .getBluetoothEvents()
+                .subscribe(event -> {
+                    if (!event.connected && mSipCall == null) {
+                        hangupCall();
+                    }
+                }));*/
+    }
+
+    fun initOutGoing(accountId: String?, conversationUri: Uri?, contactUri: String?, audioOnly: Boolean) {
+        Log.e(TAG, "initOutGoing")
+        var audioOnly = audioOnly
+        if (accountId == null || contactUri == null) {
+            Log.e(TAG, "initOutGoing: null account or contact")
+            hangupCall()
+            return
+        }
+        if (!mHardwareService.hasCamera()) {
+            audioOnly = true
+        }
+        //getView().blockScreenRotation();
+        val callObservable = mCallService
+            .placeCall(accountId, conversationUri, fromString(toNumber(contactUri)!!), audioOnly)
+            //.map(mCallService::getConference)
+            .flatMapObservable { call: Call -> mCallService.getConfUpdates(call) }
+            .share()
+        mCompositeDisposable.add(callObservable
+            .observeOn(mUiScheduler)
+            .subscribe({ conference: Conference ->
+                contactUpdate(conference)
+                confUpdate(conference)
+            }) { e: Throwable ->
+                hangupCall()
+                Log.e(TAG, "Error with initOutgoing: " + e.message)
+            })
+        showConference(callObservable)
+    }
+
+    /**
+     * Returns to or starts an incoming call
+     *
+     * @param confId         the call id
+     * @param actionViewOnly true if only returning to call or if using full screen intent
+     */
+    fun initIncomingCall(confId: String, actionViewOnly: Boolean) {
+        //getView().blockScreenRotation();
+
+        // if the call is incoming through a full intent, this allows the incoming call to display
+        incomingIsFullIntent = actionViewOnly
+        val callObservable = mCallService.getConfUpdates(confId)
+            .observeOn(mUiScheduler)
+            .share()
+
+        // Handles the case where the call has been accepted, emits a single so as to only check for permissions and start the call once
+        mCompositeDisposable.add(callObservable
+            .firstOrError()
+            .subscribe({ call: Conference ->
+                if (!actionViewOnly) {
+                    contactUpdate(call)
+                    confUpdate(call)
+                    callInitialized = true
+                    view!!.prepareCall(true)
+                }
+            }) { e: Throwable? ->
+                hangupCall()
+                Log.e(TAG, "Error with initIncoming, preparing call flow :", e)
+            })
+
+        // Handles retrieving call updates. Items emitted are only used if call is already in process or if user is returning to a call.
+        mCompositeDisposable.add(callObservable
+            .subscribe({ call: Conference ->
+                if (callInitialized || actionViewOnly) {
+                    contactUpdate(call)
+                    confUpdate(call)
+                }
+            }) { e: Throwable? ->
+                hangupCall()
+                Log.e(TAG, "Error with initIncoming, action view flow: ", e)
+            })
+        showConference(callObservable)
+    }
+
+    private fun showConference(conference: Observable<Conference>) {
+        var conference = conference
+        conference = conference
+            .distinctUntilChanged()
+        mCompositeDisposable.add(conference
+            .switchMap { obj: Conference -> obj.participantInfo }
+            .observeOn(mUiScheduler)
+            .subscribe(
+                { info: List<ParticipantInfo>? -> view!!.updateConfInfo(info) }
+            ) { e: Throwable? -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) })
+        mCompositeDisposable.add(conference
+            .switchMap { obj: Conference -> obj.participantRecording }
+            .observeOn(mUiScheduler)
+            .subscribe(
+                { contacts: Set<Contact>? -> view!!.updateParticipantRecording(contacts) }
+            ) { e: Throwable? -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) })
+    }
+
+    fun prepareOptionMenu() {
+        val isSpeakerOn: Boolean = mHardwareService.isSpeakerphoneOn
+        //boolean hasContact = mSipCall != null && null != mSipCall.getContact() && mSipCall.getContact().isUnknown();
+        val canDial = mOnGoingCall && mConference != null
+        // get the preferences
+        val displayPluginsButton = view!!.displayPluginsButton()
+        val showPluginBtn = displayPluginsButton && mOnGoingCall && mConference != null
+        val hasMultipleCamera = mHardwareService.cameraCount > 1 && mOnGoingCall && !isAudioOnly
+        view!!.initMenu(isSpeakerOn, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall)
+    }
+
+    fun chatClick() {
+        if (mConference == null || mConference!!.participants.isEmpty()) {
+            return
+        }
+        val firstCall = mConference!!.participants[0] ?: return
+        val c = firstCall.conversation
+        if (c is Conversation) {
+            val conversation = c
+            view!!.goToConversation(conversation.accountId, conversation.uri)
+        } else if (firstCall.contact != null) {
+            view!!.goToConversation(firstCall.account, firstCall.contact!!.conversationUri.blockingFirst())
+        }
+    }
+
+    val isSpeakerphoneOn: Boolean
+        get() = mHardwareService.isSpeakerphoneOn
+
+    fun speakerClick(checked: Boolean) {
+        mHardwareService.toggleSpeakerphone(checked)
+    }
+
+    fun muteMicrophoneToggled(checked: Boolean) {
+        mCallService.setLocalMediaMuted(mConference!!.id, CallService.MEDIA_TYPE_AUDIO, checked)
+    }
+
+    val isMicrophoneMuted: Boolean
+        get() = mCallService.isCaptureMuted
+
+    fun switchVideoInputClick() {
+        if (mConference == null) return
+        mHardwareService.switchInput(mConference!!.id, false)
+        view!!.switchCameraIcon(mHardwareService.isPreviewFromFrontCamera)
+    }
+
+    fun configurationChanged(rotation: Int) {
+        mHardwareService.setDeviceOrientation(rotation)
+    }
+
+    fun dialpadClick() {
+        view?.displayDialPadKeyboard()
+    }
+
+    fun acceptCall() {
+        mConference?.let { mCallService.accept(it.id) }
+    }
+
+    fun hangupCall() {
+        mConference?.let { conference ->
+            if (conference.isConference)
+                mCallService.hangUpConference(conference.id)
+            else
+                mCallService.hangUp(conference.id)
+        }
+        for (call in mPendingCalls) {
+            mCallService.hangUp(call.daemonIdString!!)
+        }
+        finish()
+    }
+
+    fun refuseCall() {
+        mConference?.let { mCallService.refuse(it.id) }
+        finish()
+    }
+
+    fun videoSurfaceCreated(holder: Any?) {
+        if (mConference == null) {
+            return
+        }
+        val newId = mConference!!.id
+        if (newId != currentSurfaceId) {
+            mHardwareService.removeVideoSurface(currentSurfaceId)
+            currentSurfaceId = newId
+        }
+        mHardwareService.addVideoSurface(mConference!!.id, holder)
+        view!!.displayContactBubble(false)
+    }
+
+    fun videoSurfaceUpdateId(newId: String?) {
+        if (newId != currentSurfaceId) {
+            mHardwareService.updateVideoSurfaceId(currentSurfaceId, newId)
+            currentSurfaceId = newId
+        }
+    }
+
+    fun pluginSurfaceCreated(holder: Any?) {
+        if (mConference == null) {
+            return
+        }
+        val newId = mConference!!.pluginId
+        if (newId != currentPluginSurfaceId) {
+            mHardwareService.removeVideoSurface(currentPluginSurfaceId)
+            currentPluginSurfaceId = newId
+        }
+        mHardwareService.addVideoSurface(mConference!!.pluginId, holder)
+        view!!.displayContactBubble(false)
+    }
+
+    fun pluginSurfaceUpdateId(newId: String?) {
+        if (newId != currentPluginSurfaceId) {
+            mHardwareService.updateVideoSurfaceId(currentPluginSurfaceId, newId)
+            currentPluginSurfaceId = newId
+        }
+    }
+
+    fun previewVideoSurfaceCreated(holder: Any?) {
+        mHardwareService.addPreviewVideoSurface(holder, mConference)
+        //mHardwareService.startCapture(null);
+    }
+
+    fun videoSurfaceDestroyed() {
+        if (currentSurfaceId != null) {
+            mHardwareService.removeVideoSurface(currentSurfaceId)
+            currentSurfaceId = null
+        }
+    }
+
+    fun pluginSurfaceDestroyed() {
+        if (currentPluginSurfaceId != null) {
+            mHardwareService.removeVideoSurface(currentPluginSurfaceId)
+            currentPluginSurfaceId = null
+        }
+    }
+
+    fun previewVideoSurfaceDestroyed() {
+        mHardwareService.removePreviewVideoSurface()
+        mHardwareService.endCapture()
+    }
+
+    fun displayChanged() {
+        mHardwareService.switchInput(mConference!!.id, false)
+    }
+
+    fun layoutChanged() {
+        //getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
+    }
+
+    fun uiVisibilityChanged(displayed: Boolean) {
+        Log.w(TAG, "uiVisibilityChanged $mOnGoingCall $displayed")
+        val view = view
+        view?.displayHangupButton(mOnGoingCall && displayed)
+    }
+
+    private fun finish() {
+        if (timeUpdateTask != null && !timeUpdateTask!!.isDisposed) {
+            timeUpdateTask!!.dispose()
+            timeUpdateTask = null
+        }
+        mConference = null
+        val view = view
+        view?.finish()
+    }
+
+    private var contactDisposable: Disposable? = null
+    private fun contactUpdate(conference: Conference) {
+        if (mConference !== conference) {
+            mConference = conference
+            if (contactDisposable != null && !contactDisposable!!.isDisposed) {
+                contactDisposable!!.dispose()
+            }
+            if (conference.participants.isEmpty()) return
+
+            // Updates of participant (and  pending participant) list
+            val callsObservable = mPendingSubject
+                .map<List<Call>> { pendingList: List<Call> ->
+                    Log.w(TAG, "mPendingSubject onNext " + pendingList.size + " " + conference.participants.size)
+                    if (pendingList.isEmpty()) return@map conference.participants
+                    val newList: MutableList<Call> = ArrayList(conference.participants.size + pendingList.size)
+                    newList.addAll(conference.participants)
+                    newList.addAll(pendingList)
+                    newList
+                }
+
+            // Updates of individual contacts
+            val contactsObservable = callsObservable
+                .flatMapSingle { calls: List<Call> ->
+                    Observable.fromIterable(calls)
+                        .map { call: Call -> mContactService.observeContact(call.account!!, call.contact!!, false)
+                                .map { call } }
+                        .toList(calls.size)
+                }
+
+            // Combined updates of contacts as participant list updates
+            val contactUpdates = contactsObservable
+                .switchMap { list: List<Observable<Call>>? ->
+                    Observable
+                        .combineLatest(list) { objects: Array<Any> ->
+                            Log.w(TAG, "flatMapObservable " + objects.size)
+                            val calls = ArrayList<Call>(objects.size)
+                            for (call in objects) calls.add(call as Call)
+                            calls
+                        }
+                }
+                .filter { list: List<Call> -> !list.isEmpty() }
+            contactDisposable = contactUpdates
+                .observeOn(mUiScheduler)
+                .subscribe({ cs: List<Call>? -> view!!.updateContactBubble(cs) }) { e: Throwable? ->
+                    Log.e(
+                        TAG,
+                        "Error updating contact data",
+                        e
+                    )
+                }
+            mCompositeDisposable.add(contactDisposable)
+        }
+        mPendingSubject.onNext(mPendingCalls)
+    }
+
+    private fun confUpdate(call: Conference) {
+        Log.w(TAG, "confUpdate " + call.id + " " + call.state)
+        val status = call.state
+        if (status === CallStatus.HOLD) {
+            if (call.isSimpleCall) mCallService.unhold(call.id) else JamiService.addMainParticipant(call.id)
+        }
+        isAudioOnly = !call.hasVideo()
+        val view = view ?: return
+        view.updateMenu()
+        if (call.isOnGoing) {
+            mOnGoingCall = true
+            view.initNormalStateDisplay(isAudioOnly, isMicrophoneMuted)
+            view.updateMenu()
+            if (!isAudioOnly) {
+                mHardwareService.setPreviewSettings()
+                mHardwareService.updatePreviewVideoSurface(call)
+                videoSurfaceUpdateId(call.id)
+                pluginSurfaceUpdateId(call.pluginId)
+                view.displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission())
+                if (permissionChanged) {
+                    mHardwareService.switchInput(mConference!!.id, permissionChanged)
+                    permissionChanged = false
+                }
+            }
+            if (timeUpdateTask != null) timeUpdateTask!!.dispose()
+            timeUpdateTask = mUiScheduler.schedulePeriodicallyDirect({ updateTime() }, 0, 1, TimeUnit.SECONDS)
+        } else if (call.isRinging) {
+            val scall = call.call!!
+            view.handleCallWakelock(isAudioOnly)
+            if (scall.isIncoming) {
+                if (mAccountService.getAccount(scall.account!!)!!.isAutoanswerEnabled) {
+                    mCallService.accept(scall.daemonIdString!!)
+                    // only display the incoming call screen if the notification is a full screen intent
+                } else if (incomingIsFullIntent) {
+                    view.initIncomingCallDisplay()
+                }
+            } else {
+                mOnGoingCall = false
+                view.updateCallStatus(scall.callStatus)
+                view.initOutGoingCallDisplay()
+            }
+        } else {
+            finish()
+        }
+    }
+
+    fun maximizeParticipant(info: ParticipantInfo?) {
+        var info = info
+        val contact = info?.contact
+        if (mConference!!.maximizedParticipant == contact) info = null
+        mConference!!.maximizedParticipant = contact
+        if (info != null) {
+            mCallService.setConfMaximizedParticipant(mConference!!.id, info.contact.uri)
+        } else {
+            mCallService.setConfGridLayout(mConference!!.id)
+        }
+    }
+
+    private fun updateTime() {
+        val view = view
+        if (view != null && mConference != null) {
+            if (mConference!!.isOnGoing) {
+                val start = mConference!!.timestampStart
+                if (start != Long.MAX_VALUE) {
+                    view.updateTime((System.currentTimeMillis() - start) / 1000)
+                } else {
+                    view.updateTime(-1)
+                }
+            }
+        }
+    }
+
+    private fun onVideoEvent(event: VideoEvent) {
+        Log.d(TAG, "VIDEO_EVENT: " + event.start + " " + event.callId + " " + event.w + "x" + event.h)
+        if (event.start) {
+            view!!.displayVideoSurface(true, !isPipMode && mDeviceRuntimeService.hasVideoPermission())
+        } else if (mConference != null && mConference!!.id == event.callId) {
+            view!!.displayVideoSurface(
+                event.started,
+                event.started && !isPipMode && mDeviceRuntimeService.hasVideoPermission()
+            )
+            if (event.started) {
+                videoWidth = event.w
+                videoHeight = event.h
+                view!!.resetVideoSize(videoWidth, videoHeight)
+            }
+        } else if (event.callId == null) {
+            if (event.started) {
+                previewWidth = event.w
+                previewHeight = event.h
+                view!!.resetPreviewVideoSize(previewWidth, previewHeight, event.rot)
+            }
+        }
+        if (mConference != null && mConference!!.pluginId == event.callId) {
+            if (event.started) {
+                previewWidth = event.w
+                previewHeight = event.h
+                view!!.resetPluginPreviewVideoSize(previewWidth, previewHeight, event.rot)
+            }
+        }
+        /*if (event.started || event.start) {
+            getView().resetVideoSize(videoWidth, videoHeight, previewWidth, previewHeight);
+        }*/
+    }
+
+    fun positiveButtonClicked() {
+        if (mConference!!.isRinging && mConference!!.isIncoming) {
+            acceptCall()
+        } else {
+            hangupCall()
+        }
+    }
+
+    fun negativeButtonClicked() {
+        if (mConference!!.isRinging && mConference!!.isIncoming) {
+            refuseCall()
+        } else {
+            hangupCall()
+        }
+    }
+
+    fun toggleButtonClicked() {
+        if (mConference != null && !(mConference!!.isRinging && mConference!!.isIncoming)) {
+            hangupCall()
+        }
+    }
+
+    fun requestPipMode() {
+        if (mConference != null && mConference!!.isOnGoing && mConference!!.hasVideo()) {
+            view!!.enterPipMode(mConference!!.id)
+        }
+    }
+
+    fun pipModeChanged(pip: Boolean) {
+        isPipMode = pip
+        if (pip) {
+            view!!.displayHangupButton(false)
+            view!!.displayPreviewSurface(false)
+            view!!.displayVideoSurface(true, false)
+        } else {
+            view!!.displayPreviewSurface(true)
+            view!!.displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission())
+        }
+    }
+
+    fun toggleCallMediaHandler(id: String?, toggle: Boolean) {
+        if (mConference != null && mConference!!.isOnGoing && mConference!!.hasVideo()) {
+            view!!.toggleCallMediaHandler(id, mConference!!.id, toggle)
+        }
+    }
+
+    fun sendDtmf(s: CharSequence) {
+        mCallService.playDtmf(s.toString())
+    }
+
+    fun addConferenceParticipant(accountId: String, uri: Uri) {
+        mCompositeDisposable.add(mConversationFacade.startConversation(accountId, uri)
+            .subscribe { conversation: Conversation ->
+                val confs: List<Conference> = conversation.currentCalls
+                if (confs.isEmpty()) {
+                    val pendingObserver: Observer<Call> = object : Observer<Call> {
+                        private var call: Call? = null
+                        override fun onSubscribe(d: Disposable) {}
+                        override fun onNext(sipCall: Call) {
+                            if (call == null) {
+                                call = sipCall
+                                mPendingCalls.add(sipCall)
+                            }
+                            mPendingSubject.onNext(mPendingCalls)
+                        }
+
+                        override fun onError(e: Throwable) {}
+                        override fun onComplete() {
+                            if (call != null) {
+                                mPendingCalls.remove(call)
+                                mPendingSubject.onNext(mPendingCalls)
+                                call = null
+                            }
+                        }
+                    }
+                    val contactUri = if (uri.isSwarm) conversation.contact!!.uri else uri
+
+                    // Place new call, join to conference when answered
+                    val newCall = mCallService.placeCallObservable(accountId, null, contactUri, isAudioOnly)
+                        .doOnEach(pendingObserver)
+                        .filter(Call::isOnGoing)
+                        .firstElement()
+                        .delay(1, TimeUnit.SECONDS)
+                        .doOnEvent { v: Call, e: Throwable -> pendingObserver.onComplete() }
+                    mCompositeDisposable.add(newCall.subscribe { call: Call ->
+                        val id = mConference!!.id
+                        if (mConference!!.isConference) {
+                            mCallService.addParticipant(call.daemonIdString!!, id)
+                        } else {
+                            mCallService.joinParticipant(id, call.daemonIdString!!).subscribe()
+                        }
+                    })
+                } else {
+                    // Selected contact already in call or conference, join it to current conference
+                    val selectedConf = confs[0]
+                    if (selectedConf !== mConference) {
+                        if (mConference!!.isConference) {
+                            if (selectedConf.isConference)
+                                mCallService.joinConference(mConference!!.id, selectedConf.id)
+                            else
+                                mCallService.addParticipant(selectedConf.id, mConference!!.id)
+                        } else {
+                            if (selectedConf.isConference)
+                                mCallService.addParticipant(mConference!!.id, selectedConf.id)
+                            else
+                                mCallService.joinParticipant(mConference!!.id, selectedConf.id).subscribe()
+                        }
+                    }
+                }
+            })
+    }
+
+    fun startAddParticipant() {
+        view!!.startAddParticipant(mConference!!.id)
+    }
+
+    fun hangupParticipant(info: ParticipantInfo) {
+        if (info.call != null)
+            mCallService.hangUp(info.call.daemonIdString!!)
+        else
+            mCallService.hangupParticipant(mConference!!.id, info.contact.primaryNumber)
+    }
+
+    fun muteParticipant(info: ParticipantInfo, mute: Boolean) {
+        mCallService.muteParticipant(mConference!!.id, info.contact.primaryNumber, mute)
+    }
+
+    fun openParticipantContact(info: ParticipantInfo) {
+        val call = info.call ?: mConference!!.firstCall!!
+        view!!.goToContact(call.account, info.contact)
+    }
+
+    fun stopCapture() {
+        mHardwareService.stopCapture()
+    }
+
+    fun startScreenShare(mediaProjection: Any?): Boolean {
+        return mHardwareService.startScreenShare(mediaProjection)
+    }
+
+    fun stopScreenShare() {
+        mHardwareService.stopScreenShare()
+    }
+
+    fun isMaximized(info: ParticipantInfo): Boolean {
+        return mConference?.maximizedParticipant == info.contact
+    }
+
+    fun startPlugin(mediaHandlerId: String?) {
+        mHardwareService.startMediaHandler(mediaHandlerId)
+        mConference?.let { conference -> mHardwareService.switchInput(conference.id, mHardwareService.isPreviewFromFrontCamera) }
+    }
+
+    fun stopPlugin() {
+        mHardwareService.stopMediaHandler()
+        mConference?.let { conference -> mHardwareService.switchInput(conference.id, mHardwareService.isPreviewFromFrontCamera) }
+    }
+
+    companion object {
+        val TAG = CallPresenter::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.java b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.java
index 03ff087a2..1a526d962 100644
--- a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.java
@@ -25,7 +25,7 @@ import java.util.List;
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import net.jami.facades.ConversationFacade;
+import net.jami.services.ConversationFacade;
 import net.jami.model.Account;
 import net.jami.model.Uri;
 import net.jami.mvp.RootPresenter;
@@ -70,9 +70,9 @@ public class ContactRequestsPresenter extends RootPresenter<net.jami.contactrequ
     }
 
     @Override
-    public void unbindView() {
+    public void onDestroy() {
         mAccount.onComplete();
-        super.unbindView();
+        super.onDestroy();
     }
 
     public void updateAccount(String accountId) {
diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java
deleted file mode 100644
index 5463e3ca5..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.conversation;
-
-import net.jami.daemon.Blob;
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Account;
-import net.jami.model.Call;
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.DataTransfer;
-import net.jami.model.Error;
-import net.jami.model.Interaction;
-import net.jami.model.TrustRequest;
-import net.jami.model.Uri;
-import net.jami.mvp.RootPresenter;
-import net.jami.services.AccountService;
-import net.jami.services.ContactService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.PreferencesService;
-import net.jami.services.VCardService;
-import net.jami.utils.FileUtils;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-import net.jami.utils.VCardUtils;
-
-import java.io.File;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class ConversationPresenter extends RootPresenter<ConversationView> {
-
-    private static final String TAG = ConversationPresenter.class.getSimpleName();
-    private final ContactService mContactService;
-    private final AccountService mAccountService;
-    private final HardwareService mHardwareService;
-    private final ConversationFacade mConversationFacade;
-    private final VCardService mVCardService;
-    private final DeviceRuntimeService mDeviceRuntimeService;
-    private final PreferencesService mPreferencesService;
-
-    private Conversation mConversation;
-    private Uri mConversationUri;
-
-    private CompositeDisposable mConversationDisposable;
-    private final CompositeDisposable mVisibilityDisposable = new CompositeDisposable();
-
-    @Inject
-    @Named("UiScheduler")
-    protected Scheduler mUiScheduler;
-
-    private final Subject<Conversation> mConversationSubject = BehaviorSubject.create();
-
-    @Inject
-    public ConversationPresenter(ContactService contactService,
-                                 AccountService accountService,
-                                 HardwareService hardwareService,
-                                 ConversationFacade conversationFacade,
-                                 VCardService vCardService,
-                                 DeviceRuntimeService deviceRuntimeService,
-                                 PreferencesService preferencesService) {
-        mContactService = contactService;
-        mAccountService = accountService;
-        mHardwareService = hardwareService;
-        mConversationFacade = conversationFacade;
-        mVCardService = vCardService;
-        mDeviceRuntimeService = deviceRuntimeService;
-        mPreferencesService = preferencesService;
-        mCompositeDisposable.add(mVisibilityDisposable);
-    }
-
-    public void init(Uri conversationUri, String accountId) {
-        Log.w(TAG, "init " + conversationUri + " " + accountId);
-        if (conversationUri.equals(mConversationUri))
-            return;
-        mConversationUri = conversationUri;
-        mCompositeDisposable.add(mConversationFacade.getAccountSubject(accountId)
-                .observeOn(mUiScheduler)
-                .flatMap(account -> mConversationFacade.loadConversationHistory(account, conversationUri)
-                            .map(c -> {
-                                setConversation(account, c);
-                                return c;
-                            }))
-                .subscribe(c -> {}, e -> {
-                    Log.e(TAG, "Error loading conversation", e);
-                    getView().goToHome();
-                }));
-        getView().setReadIndicatorStatus(showReadIndicator());
-    }
-
-    private void setConversation(Account account, final Conversation conversation) {
-        Log.w(TAG, "setConversation " + conversation.getAggregateHistory().size());
-        if (mConversation == conversation)
-            return;
-        //if (mConversation != null)
-        //    mConversation.setVisible(false);
-        mConversation = conversation;
-        mConversationSubject.onNext(conversation);
-        ConversationView view = getView();
-        if (view != null)
-            initView(account, conversation, view);
-    }
-
-    public void pause() {
-        mVisibilityDisposable.clear();
-        if (mConversation != null) {
-            mConversation.setVisible(false);
-        }
-    }
-
-    public void resume(boolean isBubble) {
-        Log.w(TAG, "resume " + mConversationUri);
-        mVisibilityDisposable.clear();
-        mVisibilityDisposable.add(mConversationSubject
-                .subscribe(conversation -> {
-                    conversation.setVisible(true);
-                    updateOngoingCallView(conversation);
-                    mConversationFacade.readMessages(mAccountService.getAccount(conversation.getAccountId()), conversation, !isBubble);
-                }, e -> Log.e(TAG, "Error loading conversation", e)));
-    }
-
-    private void initContact(final Account account, final Conversation conversation, final ConversationView view) {
-        if (account.isJami()) {
-            Log.w(TAG, "initContact " + conversation.getUri());
-            if (conversation.isSwarm() || account.isContact(conversation)) {
-                view.switchToConversationView();
-            } else {
-                Uri uri = conversation.getUri();
-                TrustRequest req = account.getRequest(uri);
-                if (req == null) {
-                    view.switchToUnknownView(uri.getRawUriString());
-                } else {
-                    view.switchToIncomingTrustRequestView(req.getDisplayname());
-                }
-            }
-        } else {
-            view.switchToConversationView();
-        }
-        view.displayContact(conversation);
-    }
-
-    private void initView(Account account, final Conversation c, final ConversationView view) {
-        Log.w(TAG, "initView " + c.getUri());
-        if (mConversationDisposable == null) {
-            mConversationDisposable = new CompositeDisposable();
-            mCompositeDisposable.add(mConversationDisposable);
-        }
-        mConversationDisposable.clear();
-        view.hideNumberSpinner();
-
-        if (account.isJami()) {
-            mConversationDisposable.add(c.getContact()
-                    .getConversationUri()
-                    .observeOn(mUiScheduler)
-                    .subscribe(uri -> init(uri, account.getAccountID())));
-        }
-
-        mConversationDisposable.add(Observable.combineLatest(
-                mHardwareService.getConnectivityState(),
-                mAccountService.getObservableAccount(account),
-                (isConnected, a) -> isConnected || a.isRegistered())
-                .observeOn(mUiScheduler)
-                .subscribe(isOk -> {
-                    ConversationView v = getView();
-                    if (v != null) {
-                        if (!isOk)
-                            v.displayNetworkErrorPanel();
-                        else if(!account.isEnabled()) {
-                            v.displayAccountOfflineErrorPanel();
-                        }
-                        else {
-                            v.hideErrorPanel();
-                        }
-                    }
-                }));
-
-        mConversationDisposable.add(c.getSortedHistory()
-                .observeOn(mUiScheduler)
-                .subscribe(view::refreshView, e -> Log.e(TAG, "Can't update element", e)));
-        mConversationDisposable.add(c.getCleared()
-                .observeOn(mUiScheduler)
-                .subscribe(view::refreshView, e -> Log.e(TAG, "Can't update elements", e)));
-
-        mConversationDisposable.add(c.getContactUpdates()
-                .switchMap(contacts -> Observable.merge(mContactService.observeLoadedContact(c.getAccountId(), contacts, true)))
-                .observeOn(mUiScheduler)
-                .subscribe(contact -> {
-                    ConversationView v = getView();
-                    if (v != null)
-                        v.updateContact(contact);
-                }));
-
-        mConversationDisposable.add(mContactService.getLoadedContact(c.getAccountId(), c.getContacts(), true)
-                .observeOn(mUiScheduler)
-                .subscribe(contact -> initContact(account, c, view), e -> Log.e(TAG, "Can't get contact", e)));
-
-        mConversationDisposable.add(c.getUpdatedElements()
-                .observeOn(mUiScheduler)
-                .subscribe(elementTuple -> {
-                    switch(elementTuple.second) {
-                        case ADD:
-                            view.addElement(elementTuple.first);
-                            break;
-                        case UPDATE:
-                            view.updateElement(elementTuple.first);
-                            break;
-                        case REMOVE:
-                            view.removeElement(elementTuple.first);
-                            break;
-                    }
-                }, e -> Log.e(TAG, "Can't update element", e)));
-
-        if (showTypingIndicator()) {
-            mConversationDisposable.add(c.getComposingStatus()
-                    .observeOn(mUiScheduler)
-                    .subscribe(view::setComposingStatus));
-        }
-        mConversationDisposable.add(c.getLastDisplayed()
-                .observeOn(mUiScheduler)
-                .subscribe(view::setLastDisplayed));
-        mConversationDisposable.add(c.getCalls()
-                .observeOn(mUiScheduler)
-                .subscribe(calls -> updateOngoingCallView(mConversation), e -> Log.e(TAG, "Can't update call view", e)));
-        mConversationDisposable.add(c.getColor()
-                .observeOn(mUiScheduler)
-                .subscribe(view::setConversationColor, e -> Log.e(TAG, "Can't update conversation color", e)));
-        mConversationDisposable.add(c.getSymbol()
-                .observeOn(mUiScheduler)
-                .subscribe(view::setConversationSymbol, e -> Log.e(TAG, "Can't update conversation color", e)));
-
-        Log.e(TAG, "getLocationUpdates subscribe");
-        mConversationDisposable.add(account
-                .getLocationUpdates(c.getUri())
-                .observeOn(mUiScheduler)
-                .subscribe(u -> {
-                    Log.e(TAG, "getLocationUpdates: update");
-                    getView().showMap(c.getAccountId(), c.getUri().getUri(), false);
-                }));
-    }
-
-    public void loadMore() {
-        mConversationDisposable.add(mAccountService.loadMore(mConversation)
-                .subscribe(c -> {}, e-> {}));
-    }
-
-    public void openContact() {
-        if (mConversation != null)
-            getView().goToContactActivity(mConversation.getAccountId(), mConversation.getUri());
-    }
-
-    public void sendTextMessage(String message) {
-        if (StringUtils.isEmpty(message) || mConversation == null) {
-            return;
-        }
-        Conference conference = mConversation.getCurrentCall();
-        if (mConversation.isSwarm() || conference == null || !conference.isOnGoing()) {
-            mConversationFacade.sendTextMessage(mConversation, mConversationUri, message).subscribe();
-        } else {
-            mConversationFacade.sendTextMessage(mConversation, conference, message);
-        }
-    }
-
-    public void selectFile() {
-        getView().openFilePicker();
-    }
-
-    public void sendFile(File file) {
-        if (mConversation ==  null)
-            return;
-        mConversationFacade.sendFile(mConversation, mConversationUri, file).subscribe();
-    }
-
-    /**
-     * Gets the absolute path of the file dataTransfer and sends both the DataTransfer and the
-     * found path to the ConversationView in order to start saving the file
-     *
-     * @param interaction an interaction representing a datat transfer
-     */
-    public void saveFile(Interaction interaction) {
-        DataTransfer transfer = (DataTransfer) interaction;
-        String fileAbsolutePath = getDeviceRuntimeService().
-                getConversationPath(transfer)
-                .getAbsolutePath();
-        getView().startSaveFile(transfer, fileAbsolutePath);
-    }
-
-    public void shareFile(Interaction interaction) {
-        DataTransfer file = (DataTransfer) interaction;
-        File path = getDeviceRuntimeService().getConversationPath(file);
-        getView().shareFile(path, file.getDisplayName());
-    }
-
-    public void openFile(Interaction interaction) {
-        DataTransfer file = (DataTransfer) interaction;
-        File path = getDeviceRuntimeService().getConversationPath(file);
-        getView().openFile(path, file.getDisplayName());
-    }
-
-    public void acceptFile(DataTransfer transfer) {
-        getView().acceptFile(mConversation.getAccountId(), mConversationUri, transfer);
-    }
-
-    public void refuseFile(DataTransfer transfer) {
-        getView().refuseFile(mConversation.getAccountId(), mConversationUri, transfer);
-    }
-
-    public void deleteConversationItem(Interaction element) {
-        mConversationFacade.deleteConversationItem(mConversation, element);
-    }
-
-    public void cancelMessage(Interaction message) {
-        mConversationFacade.cancelMessage(message);
-    }
-
-    private void sendTrustRequest() {
-        Contact contact = mConversation.getContact();
-        if (contact != null) {
-            contact.setStatus(Contact.Status.REQUEST_SENT);
-        }
-        mVCardService.loadSmallVCardWithDefault(mConversation.getAccountId(), VCardService.MAX_SIZE_REQUEST)
-                .subscribeOn(Schedulers.computation())
-                .subscribe(vCard -> mAccountService.sendTrustRequest(mConversation, contact.getUri(), Blob.fromString(VCardUtils.vcardToString(vCard))),
-                        e -> mAccountService.sendTrustRequest(mConversation, contact.getUri(), null));
-    }
-
-    public void clickOnGoingPane() {
-        Conference conf = mConversation == null ? null : mConversation.getCurrentCall();
-        if (conf != null) {
-            getView().goToCallActivity(conf.getId());
-        } else {
-            getView().displayOnGoingCallPane(false);
-        }
-    }
-
-    public void goToCall(boolean audioOnly) {
-        if (audioOnly && !mHardwareService.hasMicrophone()) {
-            getView().displayErrorToast(Error.NO_MICROPHONE);
-            return;
-        }
-
-        mCompositeDisposable.add(mConversationSubject
-                .firstElement()
-                .subscribe(conversation -> {
-                    ConversationView view = getView();
-                    if (view != null) {
-                        Conference conf = mConversation.getCurrentCall();
-                        if (conf != null
-                                && !conf.getParticipants().isEmpty()
-                                && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.INACTIVE
-                                && conf.getParticipants().get(0).getCallStatus() != Call.CallStatus.FAILURE) {
-                            view.goToCallActivity(conf.getId());
-                        } else {
-                            view.goToCallActivityWithResult(mConversation.getAccountId(), mConversation.getUri(), mConversation.getContact().getUri(), audioOnly);
-                        }
-                    }
-                }));
-    }
-
-    private void updateOngoingCallView(Conversation conversation) {
-        Conference conf = conversation == null ? null : conversation.getCurrentCall();
-        if (conf != null && (conf.getState() == Call.CallStatus.CURRENT || conf.getState() == Call.CallStatus.HOLD || conf.getState() == Call.CallStatus.RINGING)) {
-            getView().displayOnGoingCallPane(true);
-        } else {
-            getView().displayOnGoingCallPane(false);
-        }
-    }
-
-    public void onBlockIncomingContactRequest() {
-        mConversationFacade.discardRequest(mConversation.getAccountId(), mConversationUri);
-        mAccountService.removeContact(mConversation.getAccountId(), mConversationUri.getHost(), true);
-
-        getView().goToHome();
-    }
-
-    public void onRefuseIncomingContactRequest() {
-        mConversationFacade.discardRequest(mConversation.getAccountId(), mConversationUri);
-        getView().goToHome();
-    }
-
-    public void onAcceptIncomingContactRequest() {
-        mConversationFacade.acceptRequest(mConversation.getAccountId(), mConversationUri);
-        getView().switchToConversationView();
-    }
-
-    public void onAddContact() {
-        sendTrustRequest();
-        getView().switchToConversationView();
-    }
-
-    public DeviceRuntimeService getDeviceRuntimeService() {
-        return mDeviceRuntimeService;
-    }
-
-    public void noSpaceLeft() {
-        Log.e(TAG, "configureForFileInfoTextMessage: no space left on device");
-        getView().displayErrorToast(Error.NO_SPACE_LEFT);
-    }
-
-    public void setConversationColor(int color) {
-        mCompositeDisposable.add(mConversationSubject
-                .firstElement()
-                .subscribe(conversation -> conversation.setColor(color)));
-    }
-    public void setConversationSymbol(CharSequence symbol) {
-        mCompositeDisposable.add(mConversationSubject
-                .firstElement()
-                .subscribe(conversation -> conversation.setSymbol(symbol)));
-    }
-
-    public void cameraPermissionChanged(boolean isGranted) {
-        if (isGranted && mHardwareService.isVideoAvailable()) {
-            mHardwareService.initVideo()
-                    .onErrorComplete()
-                    .subscribe();
-        }
-    }
-
-    public void shareLocation() {
-        getView().startShareLocation(mConversation.getAccountId(), mConversationUri.getUri());
-    }
-
-    public void showPluginListHandlers() {
-        getView().showPluginListHandlers(mConversation.getAccountId(), mConversationUri.getUri());
-    }
-
-    public Tuple<String, String> getPath() {
-        return new Tuple<>(mConversation.getAccountId(), mConversationUri.getUri());
-    }
-
-    public void onComposingChanged(boolean hasMessage) {
-        if (mConversation == null || !showTypingIndicator()) {
-            return;
-        }
-        mConversationFacade.setIsComposing(mConversation.getAccountId(), mConversationUri, hasMessage);
-    }
-
-    public boolean showTypingIndicator() {
-        return mPreferencesService.getSettings().isAllowTypingIndicator();
-    }
-
-    private boolean showReadIndicator() {
-        return mPreferencesService.getSettings().isAllowReadIndicator();
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt
new file mode 100644
index 000000000..42e68480d
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt
@@ -0,0 +1,434 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.conversation
+
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.daemon.Blob
+import net.jami.services.ConversationFacade
+import net.jami.model.*
+import net.jami.model.Account.ComposingStatus
+import net.jami.model.Conversation.ElementStatus
+import net.jami.mvp.RootPresenter
+import net.jami.services.*
+import net.jami.utils.Log
+import net.jami.utils.StringUtils.isEmpty
+import net.jami.utils.Tuple
+import net.jami.utils.VCardUtils.vcardToString
+import java.io.File
+import javax.inject.Inject
+import javax.inject.Named
+
+class ConversationPresenter @Inject constructor(
+    private val mContactService: ContactService,
+    private val mAccountService: AccountService,
+    private val mHardwareService: HardwareService,
+    private val mConversationFacade: ConversationFacade,
+    private val mVCardService: VCardService,
+    val deviceRuntimeService: DeviceRuntimeService,
+    private val mPreferencesService: PreferencesService,
+    @Named("UiScheduler") private var mUiScheduler: Scheduler
+) : RootPresenter<ConversationView>() {
+    private var mConversation: Conversation? = null
+    private var mConversationUri: Uri? = null
+    private var mConversationDisposable: CompositeDisposable? = null
+    private val mVisibilityDisposable = CompositeDisposable()
+    private val mConversationSubject: Subject<Conversation> = BehaviorSubject.create()
+
+    fun init(conversationUri: Uri, accountId: String) {
+        Log.w(TAG, "init $conversationUri $accountId")
+        if (conversationUri == mConversationUri) return
+        mConversationUri = conversationUri
+        mCompositeDisposable.add(mConversationFacade.getAccountSubject(accountId)
+            .flatMap { a: Account ->
+                mConversationFacade.loadConversationHistory(a, conversationUri)
+                    .observeOn(mUiScheduler)
+                    .doOnSuccess { c: Conversation -> setConversation(a, c) }
+            }
+            .observeOn(mUiScheduler)
+            .subscribe({}) { e: Throwable ->
+                Log.e(TAG, "Error loading conversation", e)
+                view?.goToHome()
+            })
+        view?.setReadIndicatorStatus(showReadIndicator())
+    }
+
+    override fun unbindView() {
+        super.unbindView()
+        mConversation = null
+        mConversationUri = null
+        mConversationDisposable?.let { conversationDisposable ->
+            conversationDisposable.dispose()
+            mConversationDisposable = null
+        }
+    }
+
+    private fun setConversation(account: Account, conversation: Conversation) {
+        Log.w(TAG, "setConversation " + conversation.aggregateHistory.size)
+        if (mConversation == conversation) return
+        mConversation = conversation
+        mConversationSubject.onNext(conversation)
+        val view = view
+        view?.let { initView(account, conversation, it) }
+    }
+
+    fun pause() {
+        mVisibilityDisposable.clear()
+        mConversation?.isVisible = false
+    }
+
+    fun resume(isBubble: Boolean) {
+        Log.w(TAG, "resume $mConversationUri")
+        mVisibilityDisposable.clear()
+        mVisibilityDisposable.add(mConversationSubject
+            .subscribe({ conversation: Conversation ->
+                conversation.isVisible = true
+                updateOngoingCallView(conversation)
+                mAccountService.getAccount(conversation.accountId)?.let { account ->
+                    mConversationFacade.readMessages(account, conversation, !isBubble)}
+            }) { e -> Log.e(TAG, "Error loading conversation", e) })
+    }
+
+    private fun initContact(account: Account, conversation: Conversation, mode: Conversation.Mode, view: ConversationView) {
+        if (account.isJami) {
+            Log.w(TAG, "initContact " + conversation.uri)
+            if (mode === Conversation.Mode.Syncing) {
+                view.switchToSyncingView()
+            } else if (conversation.isSwarm || account.isContact(conversation)) {
+                //if (conversation.isEnded())
+                //    conversation.s
+                view.switchToConversationView()
+            } else {
+                val uri = conversation.uri
+                val req = account.getRequest(uri)
+                if (req == null) {
+                    view.switchToUnknownView(uri.rawUriString)
+                } else {
+                    view.switchToIncomingTrustRequestView(req.displayname)
+                }
+            }
+        } else {
+            view.switchToConversationView()
+        }
+        view.displayContact(conversation)
+    }
+
+    private fun initView(account: Account, c: Conversation, view: ConversationView) {
+        Log.w(TAG, "initView " + c.uri + " " + c.mode)
+        val disposable = mConversationDisposable?.apply { clear() } ?: CompositeDisposable().apply {
+            mConversationDisposable = this
+            mCompositeDisposable.add(this)
+        }
+
+        view.hideNumberSpinner()
+        disposable.add(c.mode
+            .switchMapSingle { mode: Conversation.Mode ->
+                mContactService.getLoadedContact(c.accountId, c.contacts, true)
+                    .observeOn(mUiScheduler)
+                    .doOnSuccess { initContact(account, c, mode, view) }
+            }
+            .subscribe())
+        disposable.add(c.mode
+            .switchMap { mode: Conversation.Mode -> if (mode === Conversation.Mode.Legacy || mode === Conversation.Mode.OneToOne) c.contact!!.conversationUri else Observable.empty() }
+            .observeOn(mUiScheduler)
+            .subscribe { uri: Uri -> init(uri, account.accountID) })
+        disposable.add(
+            Observable.combineLatest(
+                mHardwareService.connectivityState,
+                mAccountService.getObservableAccount(account),
+                { isConnected: Boolean, a: Account -> isConnected || a.isRegistered })
+                .observeOn(mUiScheduler)
+                .subscribe { isOk: Boolean ->
+                    val v = getView()
+                    if (v != null) {
+                        if (!isOk) v.displayNetworkErrorPanel() else if (!account.isEnabled) {
+                            v.displayAccountOfflineErrorPanel()
+                        } else {
+                            v.hideErrorPanel()
+                        }
+                    }
+                })
+        disposable.add(c.sortedHistory
+            .observeOn(mUiScheduler)
+            .subscribe({ conversation: List<Interaction> -> view.refreshView(conversation) }) { e: Throwable ->
+                Log.e(TAG, "Can't update element", e)
+            })
+        disposable.add(c.cleared
+            .observeOn(mUiScheduler)
+            .subscribe({ conversation: List<Interaction> -> view.refreshView(conversation) }) { e: Throwable ->
+                Log.e(TAG, "Can't update elements", e)
+            })
+        disposable.add(c.contactUpdates
+            .switchMap { contacts: List<Contact> ->
+                Observable.merge(mContactService.observeLoadedContact(c.accountId, contacts, true))
+            }
+            .observeOn(mUiScheduler)
+            .subscribe { contact: Contact -> getView()?.updateContact(contact) })
+        disposable.add(c.updatedElements
+            .observeOn(mUiScheduler)
+            .subscribe({ elementTuple ->
+                when (elementTuple.second) {
+                    ElementStatus.ADD -> view.addElement(elementTuple.first)
+                    ElementStatus.UPDATE -> view.updateElement(elementTuple.first)
+                    ElementStatus.REMOVE -> view.removeElement(elementTuple.first)
+                }
+            }, { e: Throwable -> Log.e(TAG, "Can't update element", e) })
+        )
+        if (showTypingIndicator()) {
+            disposable.add(c.composingStatus
+                .observeOn(mUiScheduler)
+                .subscribe { composingStatus: ComposingStatus -> view.setComposingStatus(composingStatus) })
+        }
+        disposable.add(c.getLastDisplayed()
+            .observeOn(mUiScheduler)
+            .subscribe { interaction: Interaction -> view.setLastDisplayed(interaction) })
+        disposable.add(c.calls
+            .observeOn(mUiScheduler)
+            .subscribe({ updateOngoingCallView(c) }) { e: Throwable ->
+                Log.e(TAG, "Can't update call view", e)
+            })
+        disposable.add(c.getColor()
+            .observeOn(mUiScheduler)
+            .subscribe({ integer: Int -> view.setConversationColor(integer) }) { e: Throwable ->
+                Log.e(TAG, "Can't update conversation color", e)
+            })
+        disposable.add(c.getSymbol()
+            .observeOn(mUiScheduler)
+            .subscribe({ symbol: CharSequence -> view.setConversationSymbol(symbol) }) { e: Throwable ->
+                Log.e(TAG, "Can't update conversation color", e)
+            })
+        disposable.add(account
+            .getLocationUpdates(c.uri)
+            .observeOn(mUiScheduler)
+            .subscribe {
+                Log.e(TAG, "getLocationUpdates: update")
+                getView()?.showMap(c.accountId, c.uri.uri, false)
+            }
+        )
+    }
+
+    fun loadMore() {
+        mConversationDisposable?.add(mAccountService.loadMore(mConversation!!).subscribe({}) {})
+    }
+
+    fun openContact() {
+        mConversation?.let { conversation -> view?.goToContactActivity(conversation.accountId, conversation.uri) }
+    }
+
+    fun sendTextMessage(message: String?) {
+        val conversation = mConversation
+        if (isEmpty(message) || conversation == null) {
+            return
+        }
+        val conference = conversation.currentCall
+        if (conversation.isSwarm || conference == null || !conference.isOnGoing) {
+            mConversationFacade.sendTextMessage(conversation, conversation.uri, message).subscribe()
+        } else {
+            mConversationFacade.sendTextMessage(conversation, conference, message)
+        }
+    }
+
+    fun selectFile() {
+        view!!.openFilePicker()
+    }
+
+    fun sendFile(file: File?) {
+        mConversation?.let { conversation ->
+            mConversationFacade.sendFile(conversation, conversation.uri, file).subscribe() }
+    }
+
+    /**
+     * Gets the absolute path of the file dataTransfer and sends both the DataTransfer and the
+     * found path to the ConversationView in order to start saving the file
+     *
+     * @param interaction an interaction representing a datat transfer
+     */
+    fun saveFile(interaction: Interaction) {
+        val transfer = interaction as DataTransfer
+        val fileAbsolutePath = deviceRuntimeService.getConversationPath(transfer).absolutePath
+        view?.startSaveFile(transfer, fileAbsolutePath)
+    }
+
+    fun shareFile(interaction: Interaction) {
+        val file = interaction as DataTransfer
+        val path = deviceRuntimeService.getConversationPath(file)
+        view?.shareFile(path, file.displayName)
+    }
+
+    fun openFile(interaction: Interaction) {
+        val file = interaction as DataTransfer
+        val path = deviceRuntimeService.getConversationPath(file)
+        view?.openFile(path, file.displayName)
+    }
+
+    fun acceptFile(transfer: DataTransfer) {
+        view?.acceptFile(mConversation!!.accountId, mConversationUri!!, transfer)
+    }
+
+    fun refuseFile(transfer: DataTransfer) {
+        view!!.refuseFile(mConversation!!.accountId, mConversationUri!!, transfer)
+    }
+
+    fun deleteConversationItem(element: Interaction) {
+        mConversationFacade.deleteConversationItem(mConversation, element)
+    }
+
+    fun cancelMessage(message: Interaction) {
+        mConversationFacade.cancelMessage(message)
+    }
+
+    private fun sendTrustRequest() {
+        val conversation = mConversation ?: return
+        val contact = conversation.contact ?: return
+        contact.status = Contact.Status.REQUEST_SENT
+        mVCardService.loadSmallVCardWithDefault(conversation.accountId, VCardService.MAX_SIZE_REQUEST)
+            .subscribeOn(Schedulers.computation())
+            .subscribe({ vCard -> mAccountService.sendTrustRequest(conversation, contact.uri, Blob.fromString(vcardToString(vCard)))})
+            { mAccountService.sendTrustRequest(conversation, contact.uri, null) }
+    }
+
+    fun clickOnGoingPane() {
+        val conf = mConversation?.currentCall
+        if (conf != null) {
+            view?.goToCallActivity(conf.id)
+        } else {
+            view?.displayOnGoingCallPane(false)
+        }
+    }
+
+    fun goToCall(audioOnly: Boolean) {
+        if (audioOnly && !mHardwareService.hasMicrophone()) {
+            view!!.displayErrorToast(Error.NO_MICROPHONE)
+            return
+        }
+        mCompositeDisposable.add(mConversationSubject
+            .firstElement()
+            .subscribe { conversation: Conversation ->
+                val view = view
+                if (view != null) {
+                    val conf = conversation.currentCall
+                    if (conf != null && conf.participants.isNotEmpty()
+                        && conf.participants[0].callStatus !== Call.CallStatus.INACTIVE
+                        && conf.participants[0].callStatus !== Call.CallStatus.FAILURE) {
+                        view.goToCallActivity(conf.id)
+                    } else {
+                        view.goToCallActivityWithResult(conversation.accountId, conversation.uri, conversation.contact!!.uri, audioOnly)
+                    }
+                }
+            })
+    }
+
+    private fun updateOngoingCallView(conversation: Conversation?) {
+        val conf = conversation?.currentCall
+        view?.displayOnGoingCallPane(conf != null && (conf.state === Call.CallStatus.CURRENT || conf.state === Call.CallStatus.HOLD || conf.state === Call.CallStatus.RINGING))
+    }
+
+    fun onBlockIncomingContactRequest() {
+        mConversation?.let { conversation ->
+            mConversationFacade.discardRequest(conversation.accountId, conversation.uri)
+            mAccountService.removeContact(conversation.accountId, conversation.uri.host, true)
+        }
+        view?.goToHome()
+    }
+
+    fun onRefuseIncomingContactRequest() {
+        mConversation?.let { conversation ->
+            mConversationFacade.discardRequest(conversation.accountId, conversation.uri)
+        }
+        view?.goToHome()
+    }
+
+    fun onAcceptIncomingContactRequest() {
+        mConversation?.let { conversation ->
+            mConversationFacade.acceptRequest(conversation.accountId, conversation.uri)
+        }
+        view?.switchToConversationView()
+    }
+
+    fun onAddContact() {
+        sendTrustRequest()
+        view?.switchToConversationView()
+    }
+
+    fun noSpaceLeft() {
+        Log.e(TAG, "configureForFileInfoTextMessage: no space left on device")
+        view?.displayErrorToast(Error.NO_SPACE_LEFT)
+    }
+
+    fun setConversationColor(color: Int) {
+        mCompositeDisposable.add(mConversationSubject
+            .firstElement()
+            .subscribe { conversation: Conversation -> conversation.setColor(color) })
+    }
+
+    fun setConversationSymbol(symbol: CharSequence) {
+        mCompositeDisposable.add(mConversationSubject.firstElement()
+            .subscribe { conversation -> conversation.setSymbol(symbol) })
+    }
+
+    fun cameraPermissionChanged(isGranted: Boolean) {
+        if (isGranted && mHardwareService.isVideoAvailable) {
+            mHardwareService.initVideo()
+                .onErrorComplete()
+                .subscribe()
+        }
+    }
+
+    fun shareLocation() {
+        view?.startShareLocation(mConversation!!.accountId, mConversationUri!!.uri)
+    }
+
+    fun showPluginListHandlers() {
+        view?.showPluginListHandlers(mConversation!!.accountId, mConversationUri!!.uri)
+    }
+
+    val path: Tuple<String, String>
+        get() = Tuple(mConversation!!.accountId, mConversationUri!!.uri)
+
+    fun onComposingChanged(hasMessage: Boolean) {
+        if (showTypingIndicator()) {
+            mConversation?.let { conversation ->
+                mConversationFacade.setIsComposing(conversation.accountId, conversation.uri, hasMessage)
+            }
+        }
+    }
+
+    private fun showTypingIndicator(): Boolean {
+        return mPreferencesService.settings.isAllowTypingIndicator
+    }
+
+    private fun showReadIndicator(): Boolean {
+        return mPreferencesService.settings.isAllowReadIndicator
+    }
+
+    companion object {
+        private val TAG = ConversationPresenter::class.simpleName!!
+    }
+
+    init {
+        mCompositeDisposable.add(mVisibilityDisposable)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java
deleted file mode 100644
index 9194f0f8a..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.conversation;
-
-import java.io.File;
-import java.util.List;
-
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.Error;
-import net.jami.model.Interaction;
-import net.jami.model.DataTransfer;
-import net.jami.model.Uri;
-import net.jami.mvp.BaseView;
-
-public interface ConversationView extends BaseView {
-
-    void refreshView(List<Interaction> conversation);
-
-    void scrollToEnd();
-
-    void updateContact(Contact contact);
-
-    void displayContact(Conversation conversation);
-
-    void displayOnGoingCallPane(boolean display);
-
-    void displayNumberSpinner(Conversation conversation, Uri number);
-
-    void displayErrorToast(Error error);
-
-    void hideNumberSpinner();
-
-    void clearMsgEdit();
-
-    void goToHome();
-
-    void goToAddContact(Contact contact);
-
-    void goToCallActivity(String conferenceId);
-
-    void goToCallActivityWithResult(String accountId, Uri conversationUri, Uri contactUri, boolean audioOnly);
-
-    void goToContactActivity(String accountId, Uri uri);
-
-    void switchToUnknownView(String name);
-
-    void switchToIncomingTrustRequestView(String message);
-
-    void switchToConversationView();
-
-    void askWriteExternalStoragePermission();
-
-    void openFilePicker();
-
-    void acceptFile(String accountId, Uri conversationUri, DataTransfer transfer);
-    void refuseFile(String accountId, Uri conversationUri, DataTransfer transfer);
-    void shareFile(File path, String displayName);
-    void openFile(File path, String displayName);
-
-    void addElement(Interaction e);
-    void updateElement(Interaction e);
-    void removeElement(Interaction e);
-    void setComposingStatus(Account.ComposingStatus composingStatus);
-    void setLastDisplayed(Interaction interaction);
-
-    void setConversationColor(int integer);
-    void setConversationSymbol(CharSequence symbol);
-
-    void startSaveFile(DataTransfer currentFile, String fileAbsolutePath);
-
-    void startShareLocation(String accountId, String contactId);
-
-    void showMap(String accountId, String contactId, boolean open);
-    void hideMap();
-
-    void showPluginListHandlers(String accountId, String peerId);
-
-    void hideErrorPanel();
-
-    void displayNetworkErrorPanel();
-
-    void displayAccountOfflineErrorPanel();
-
-    void setReadIndicatorStatus(boolean show);
-
-    void updateLastRead(String last);
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt
new file mode 100644
index 000000000..d69902d1e
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt
@@ -0,0 +1,70 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.conversation
+
+import net.jami.model.*
+import net.jami.model.Account.ComposingStatus
+import net.jami.mvp.BaseView
+import java.io.File
+
+interface ConversationView : BaseView {
+    fun refreshView(conversation: List<Interaction>)
+    fun scrollToEnd()
+    fun updateContact(contact: Contact)
+    fun displayContact(conversation: Conversation)
+    fun displayOnGoingCallPane(display: Boolean)
+    fun displayNumberSpinner(conversation: Conversation, number: Uri)
+    override fun displayErrorToast(error: Error)
+    fun hideNumberSpinner()
+    fun clearMsgEdit()
+    fun goToHome()
+    fun goToAddContact(contact: Contact)
+    fun goToCallActivity(conferenceId: String)
+    fun goToCallActivityWithResult(accountId: String, conversationUri: Uri, contactUri: Uri, audioOnly: Boolean)
+    fun goToContactActivity(accountId: String, uri: Uri)
+    fun switchToUnknownView(name: String)
+    fun switchToIncomingTrustRequestView(message: String)
+    fun switchToConversationView()
+    fun switchToSyncingView()
+    fun switchToEndedView()
+    fun askWriteExternalStoragePermission()
+    fun openFilePicker()
+    fun acceptFile(accountId: String, conversationUri: Uri, transfer: DataTransfer)
+    fun refuseFile(accountId: String, conversationUri: Uri, transfer: DataTransfer)
+    fun shareFile(path: File, displayName: String)
+    fun openFile(path: File, displayName: String)
+    fun addElement(e: Interaction)
+    fun updateElement(e: Interaction)
+    fun removeElement(e: Interaction)
+    fun setComposingStatus(composingStatus: ComposingStatus)
+    fun setLastDisplayed(interaction: Interaction)
+    fun setConversationColor(integer: Int)
+    fun setConversationSymbol(symbol: CharSequence)
+    fun startSaveFile(currentFile: DataTransfer, fileAbsolutePath: String)
+    fun startShareLocation(accountId: String, contactId: String)
+    fun showMap(accountId: String, contactId: String, open: Boolean)
+    fun hideMap()
+    fun showPluginListHandlers(accountId: String, peerId: String)
+    fun hideErrorPanel()
+    fun displayNetworkErrorPanel()
+    fun displayAccountOfflineErrorPanel()
+    fun setReadIndicatorStatus(show: Boolean)
+    fun updateLastRead(last: String)
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java
deleted file mode 100644
index 168d9e1a9..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.facades;
-
-import net.jami.model.Account;
-import net.jami.model.Call;
-import net.jami.model.Conference;
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.DataTransfer;
-import net.jami.model.Interaction;
-import net.jami.model.TextMessage;
-import net.jami.model.Uri;
-import net.jami.services.AccountService;
-import net.jami.services.CallService;
-import net.jami.services.ContactService;
-import net.jami.services.DeviceRuntimeService;
-import net.jami.services.HardwareService;
-import net.jami.services.HistoryService;
-import net.jami.services.NotificationService;
-import net.jami.services.PreferencesService;
-import net.jami.smartlist.SmartListViewModel;
-import net.jami.utils.FileUtils;
-import net.jami.utils.Log;
-import net.jami.utils.Tuple;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.NavigableMap;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class ConversationFacade {
-
-    private final static String TAG = ConversationFacade.class.getSimpleName();
-
-    private final AccountService mAccountService;
-    private final HistoryService mHistoryService;
-    private final CallService mCallService;
-    private final ContactService mContactService;
-    private final NotificationService mNotificationService;
-    private final CompositeDisposable mDisposableBag = new CompositeDisposable();
-    private final Observable<Account> currentAccountSubject;
-    private final Subject<Conversation> conversationSubject = PublishSubject.create();
-    @Inject
-    HardwareService mHardwareService;
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-    @Inject
-    PreferencesService mPreferencesService;
-
-    public ConversationFacade(HistoryService historyService,
-                              CallService callService,
-                              AccountService accountService,
-                              ContactService contactService,
-                              NotificationService notificationService) {
-        mHistoryService = historyService;
-        mCallService = callService;
-        mAccountService = accountService;
-        mContactService = contactService;
-        mNotificationService = notificationService;
-
-        currentAccountSubject = mAccountService
-                .getCurrentAccountSubject()
-                .switchMapSingle(this::loadSmartlist);
-
-        mDisposableBag.add(mCallService.getCallsUpdates()
-                .subscribe(this::onCallStateChange));
-
-        /*mDisposableBag.add(mCallService.getConnectionUpdates()
-                    .subscribe(mNotificationService::onConnectionUpdate));*/
-
-        mDisposableBag.add(mCallService.getConfsUpdates()
-                .observeOn(Schedulers.io())
-                .subscribe(this::onConfStateChange));
-
-        mDisposableBag.add(currentAccountSubject
-                .switchMap(a -> a.getPendingSubject()
-                        .doOnNext(p -> mNotificationService.showIncomingTrustRequestNotification(a)))
-                .subscribe());
-
-        mDisposableBag.add(mAccountService.getIncomingRequests()
-                .concatMapSingle(r -> getAccountSubject(r.getAccountId()))
-                .subscribe(mNotificationService::showIncomingTrustRequestNotification,
-                        e -> Log.e(TAG, "Error showing contact request")));
-
-        mDisposableBag.add(mAccountService
-                .getIncomingMessages()
-                .concatMapSingle(msg -> getAccountSubject(msg.getAccount())
-                        .map(a -> {
-                            a.addTextMessage(msg);
-                            return msg;
-                        }))
-                .subscribe(this::parseNewMessage,
-                        e -> Log.e(TAG, "Error adding text message", e)));
-        mDisposableBag.add(mAccountService
-                .getIncomingSwarmMessages()
-                .subscribe(this::parseNewMessage,
-                        e -> Log.e(TAG, "Error adding text message", e)));
-
-        mDisposableBag.add(mAccountService.getLocationUpdates()
-                .concatMapSingle(location -> getAccountSubject(location.getAccount())
-                        .map(a -> {
-                            long expiration = a.onLocationUpdate(location);
-                            mDisposableBag.add(Completable.timer(expiration, TimeUnit.MILLISECONDS)
-                                    .subscribe(a::maintainLocation));
-                            return location;
-                        }))
-                .subscribe());
-
-        mDisposableBag.add(mAccountService.getObservableAccountList()
-                .switchMap(accounts -> {
-                    List<Observable<Tuple<Account, Account.ContactLocationEntry>>> r = new ArrayList<>(accounts.size());
-                    for (Account a : accounts)
-                        r.add(a.getLocationUpdates().map(s -> new Tuple<>(a, s)));
-                    return Observable.merge(r);
-                })
-                .distinctUntilChanged()
-                .subscribe(t -> {
-                    Log.e(TAG, "Location reception started for " + t.second.contact);
-                    mNotificationService.showLocationNotification(t.first, t.second.contact);
-                    mDisposableBag.add(t.second.location.doOnComplete(() ->
-                            mNotificationService.cancelLocationNotification(t.first, t.second.contact)).subscribe());
-                }));
-
-        mDisposableBag.add(mAccountService
-                .getMessageStateChanges()
-                .concatMapSingle(e -> getAccountSubject(e.getAccount())
-                        .map(a -> e.getConversation() == null ? a.getSwarm(e.getConversationId()) : a.getByUri(e.getConversation().getParticipant()))
-                        .doOnSuccess(conversation -> conversation.updateInteraction(e)))
-                .subscribe(c -> {
-                }, e -> Log.e(TAG, "Error updating text message", e)));
-
-        mDisposableBag.add(mAccountService
-                .getDataTransfers()
-                .subscribe(this::handleDataTransferEvent,
-                        e -> Log.e(TAG, "Error adding data transfer", e)));
-    }
-
-    public Observable<Conversation> getUpdatedConversation() {
-        return conversationSubject;
-    }
-
-    public Single<Conversation> startConversation(String accountId, final Uri contactId) {
-        return getAccountSubject(accountId)
-                .map(account -> account.getByUri(contactId));
-    }
-
-    public Observable<Account> getCurrentAccountSubject() {
-        return currentAccountSubject;
-    }
-
-    public Single<Account> getAccountSubject(String accountId) {
-        return mAccountService
-                .getAccountSingle(accountId)
-                .flatMap(this::loadSmartlist);
-    }
-
-    public Observable<List<Conversation>> getConversationsSubject() {
-        return currentAccountSubject
-                .switchMap(Account::getConversationsSubject);
-    }
-
-    public String readMessages(String accountId, Uri contact) {
-        Account account = mAccountService.getAccount(accountId);
-        return account != null ?
-                readMessages(account, account.getByUri(contact), true) : null;
-    }
-
-    public String readMessages(Account account, Conversation conversation, boolean cancelNotification) {
-        if (conversation != null) {
-            String lastMessage = readMessages(conversation);
-            if (lastMessage != null) {
-                account.refreshed(conversation);
-                if (mPreferencesService.getSettings().isAllowReadIndicator()) {
-                    mAccountService.setMessageDisplayed(account.getAccountID(), conversation.getUri(), lastMessage);
-                }
-                if (cancelNotification) {
-                    mNotificationService.cancelTextNotification(account.getAccountID(), conversation.getUri());
-                }
-            }
-            return lastMessage;
-        }
-        return null;
-    }
-
-    private String readMessages(Conversation conversation) {
-        String lastRead = null;
-        if (conversation.isSwarm()) {
-            lastRead = conversation.readMessages();
-            if (lastRead != null)
-                mHistoryService.setMessageRead(conversation.getAccountId(), conversation.getUri(), lastRead);
-        } else {
-            NavigableMap<Long, Interaction> messages = conversation.getRawHistory();
-            for (Interaction e : messages.descendingMap().values()) {
-                if (!(e.getType().equals(Interaction.InteractionType.TEXT)))
-                    continue;
-                if (e.isRead()) {
-                    break;
-                }
-                e.read();
-                Long did = e.getDaemonId();
-                if (lastRead == null && did != null && did != 0L)
-                    lastRead = Long.toString(did, 16);
-                mHistoryService.updateInteraction(e, conversation.getAccountId()).subscribe();
-            }
-        }
-        return lastRead;
-    }
-
-    public Completable sendTextMessage(Conversation c, Uri to, String txt) {
-        if (c.isSwarm()) {
-            mAccountService.sendConversationMessage(c.getAccountId(), c.getUri(), txt);
-            return Completable.complete();
-        }
-        return mCallService.sendAccountTextMessage(c.getAccountId(), to.getRawUriString(), txt)
-                .map(id -> {
-                    TextMessage message = new TextMessage(null, c.getAccountId(), Long.toHexString(id), c, txt);
-                    if (c.isVisible())
-                        message.read();
-                    mHistoryService.insertInteraction(c.getAccountId(), c, message).subscribe();
-                    c.addTextMessage(message);
-                    mAccountService.getAccount(c.getAccountId()).conversationUpdated(c);
-                    return message;
-                }).ignoreElement();
-    }
-
-    public void sendTextMessage(Conversation c, Conference conf, String txt) {
-        mCallService.sendTextMessage(conf.getId(), txt);
-        TextMessage message = new TextMessage(null, c.getAccountId(), conf.getId(), c, txt);
-        message.read();
-        mHistoryService.insertInteraction(c.getAccountId(), c, message).subscribe();
-        c.addTextMessage(message);
-    }
-
-    public void setIsComposing(String accountId, Uri conversationUri, boolean isComposing) {
-        mCallService.setIsComposing(accountId, conversationUri.getUri(), isComposing);
-    }
-
-    public Completable sendFile(Conversation conversation, Uri to, File file) {
-        if (file == null || !file.exists() || !file.canRead()) {
-            Log.w(TAG, "sendFile: file not found or not readable: " + file);
-            return null;
-        }
-
-        if (conversation.isSwarm()) {
-            File destPath = mDeviceRuntimeService.getNewConversationPath(conversation.getAccountId(), conversation.getUri().getRawRingId(), file.getName());
-            FileUtils.moveFile(file, destPath);
-            mAccountService.sendFile(conversation, destPath);
-            return Completable.complete();
-        }
-
-        return Single.fromCallable(() -> {
-            DataTransfer transfer = new DataTransfer(conversation, to.getRawRingId(), conversation.getAccountId(), file.getName(), true, file.length(), 0, null);
-            mHistoryService.insertInteraction(conversation.getAccountId(), conversation, transfer).blockingAwait();
-
-            transfer.destination = mDeviceRuntimeService.getConversationDir(conversation.getUri().getRawRingId());
-            return transfer;
-        })
-                .flatMap(t -> mAccountService.sendFile(file, t))
-                .flatMapCompletable(transfer -> Completable.fromAction(() -> {
-                    File destination = new File(transfer.destination, transfer.getStoragePath());
-                    if (!mDeviceRuntimeService.hardLinkOrCopy(file, destination)) {
-                        Log.e(TAG, "sendFile: can't move file to " + destination);
-                    }
-                }))
-                .subscribeOn(Schedulers.io());
-    }
-
-    public void deleteConversationItem(Conversation conversation, Interaction element) {
-        if (element.getType() == Interaction.InteractionType.DATA_TRANSFER) {
-            DataTransfer transfer = (DataTransfer) element;
-            if (transfer.getStatus() == Interaction.InteractionStatus.TRANSFER_ONGOING) {
-                mAccountService.cancelDataTransfer(conversation.getAccountId(), conversation.getUri().getRawRingId(), transfer.getMessageId(), transfer.getFileId());
-            } else {
-                File file = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), transfer.getStoragePath());
-                mDisposableBag.add(Completable.mergeArrayDelayError(
-                        mHistoryService.deleteInteraction(element.getId(), element.getAccount()),
-                        Completable.fromAction(file::delete)
-                                .subscribeOn(Schedulers.io()))
-                        .subscribe(() -> conversation.removeInteraction(transfer),
-                                e -> Log.e(TAG, "Can't delete file transfer", e)));
-            }
-        } else {
-            // handling is the same for calls and texts
-            mDisposableBag.add(mHistoryService.deleteInteraction(element.getId(), element.getAccount()).subscribeOn(Schedulers.io())
-                    .subscribe(() -> conversation.removeInteraction(element),
-                            e -> Log.e(TAG, "Can't delete message", e)));
-        }
-    }
-
-    public void cancelMessage(Interaction message) {
-        mDisposableBag.add(Completable.mergeArrayDelayError(
-                mCallService.cancelMessage(message.getAccount(), message.getId()).subscribeOn(Schedulers.io()))
-                .andThen(startConversation(message.getAccount(), Uri.fromString(message.getConversation().getParticipant())))
-                .subscribe(c -> c.removeInteraction(message),
-                        e -> Log.e(TAG, "Can't cancel message sending", e)));
-    }
-
-    /**
-     * Loads the smartlist from cache or database
-     *
-     * @param account the user account
-     * @return an account single
-     */
-    private Single<Account> loadSmartlist(final Account account) {
-        synchronized (account) {
-            if (account.historyLoader == null) {
-                Log.d(TAG, "loadSmartlist(): start loading");
-                account.historyLoader = getSmartlist(account);
-            }
-            return account.historyLoader;
-        }
-    }
-
-    /**
-     * Loads history for a specific conversation from cache or database
-     *
-     * @param account         the user account
-     * @param conversationUri the conversation
-     * @return a conversation single
-     */
-    public Single<Conversation> loadConversationHistory(final Account account, final Uri conversationUri) {
-        Conversation conversation = account.getByUri(conversationUri);
-        if (conversation == null)
-            return Single.error(new RuntimeException("Can't get conversation"));
-        synchronized (conversation) {
-            if (!conversation.isSwarm() && conversation.getId() == null) {
-                return Single.just(conversation);
-            }
-            Single<Conversation> ret = conversation.getLoaded();
-            if (ret == null) {
-                ret = conversation.isSwarm() ? mAccountService.loadMore(conversation) : getConversationHistory(conversation);
-                conversation.setLoaded(ret);
-            }
-            return ret;
-        }
-    }
-
-    private Observable<SmartListViewModel> observeConversation(Account account, Conversation conversation, boolean hasPresence) {
-        return Observable.merge(account.getConversationSubject()
-                        .filter(c -> c == conversation)
-                        .startWithItem(conversation),
-                mContactService
-                        .observeContact(conversation.getAccountId(), conversation.getContacts(), hasPresence))
-                .map(e -> new SmartListViewModel(conversation, hasPresence));
-        /*return account.getConversationSubject()
-                .filter(c -> c == conversation)
-                .startWith(conversation)
-                .switchMap(c -> mContactService
-                        .observeContact(c.getAccountId(), c.getContacts(), hasPresence)
-                        .map(contact -> new SmartListViewModel(c, hasPresence)));*/
-    }
-
-    public Observable<List<Observable<SmartListViewModel>>> getSmartList(Observable<Account> currentAccount, boolean hasPresence) {
-        return currentAccount.switchMap(account -> account.getConversationsSubject()
-                .switchMapSingle(conversations -> Observable.fromIterable(conversations)
-                        .map(conversation -> observeConversation(account, conversation, hasPresence))
-                        .toList()));
-    }
-
-    public Observable<List<SmartListViewModel>> getContactList(Observable<Account> currentAccount) {
-        return currentAccount.switchMap(account -> account.getConversationsSubject()
-                .switchMapSingle(conversations -> Observable.fromIterable(conversations)
-                        .filter(conversation -> !conversation.isSwarm())
-                        .map(conversation -> new SmartListViewModel(conversation, false))
-                        .toList()));
-    }
-
-    public Observable<List<Observable<SmartListViewModel>>> getPendingList(Observable<Account> currentAccount) {
-        return currentAccount.switchMap(account -> account.getPendingSubject()
-                .switchMapSingle(conversations -> Observable.fromIterable(conversations)
-                        .map(conversation -> observeConversation(account, conversation, false))
-                        .toList()));
-    }
-
-    public Observable<List<Observable<SmartListViewModel>>> getSmartList(boolean hasPresence) {
-        return getSmartList(mAccountService.getCurrentAccountSubject(), hasPresence);
-    }
-
-    public Observable<List<Observable<SmartListViewModel>>> getPendingList() {
-        return getPendingList(mAccountService.getCurrentAccountSubject());
-    }
-
-    public Observable<List<SmartListViewModel>> getContactList() {
-        return getContactList(mAccountService.getCurrentAccountSubject());
-    }
-
-    private Single<List<Observable<SmartListViewModel>>> getSearchResults(Account account, String query) {
-        Uri uri = Uri.fromString(query);
-        if (account.isSip()) {
-            Contact contact = account.getContactFromCache(uri);
-            return mContactService.loadContactData(contact, account.getAccountID())
-                    .andThen(Single.just(Collections.singletonList(Observable.just(new SmartListViewModel(account.getAccountID(), contact, contact.getPrimaryNumber(), null)))));
-        } else if (uri.isHexId()) {
-            return mContactService.getLoadedContact(account.getAccountID(), account.getContactFromCache(uri))
-                    .map(contact -> Collections.singletonList(Observable.just(new SmartListViewModel(account.getAccountID(), contact, contact.getPrimaryNumber(), null))));
-        } else if (account.canSearch() && !query.contains("@")) {
-            return mAccountService.searchUser(account.getAccountID(), query)
-                    .map(AccountService.UserSearchResult::getResultsViewModels);
-        } else {
-            return mAccountService.findRegistrationByName(account.getAccountID(), "", query)
-                    .map(result -> result.state == 0 ? Collections.singletonList(observeConversation(account, account.getByUri(result.address), false)) : Collections.emptyList());
-        }
-    }
-
-    private Observable<List<Observable<SmartListViewModel>>> getSearchResults(Account account, Observable<String> query) {
-        return query.switchMapSingle(q -> q.isEmpty()
-                ? SmartListViewModel.EMPTY_LIST
-                : getSearchResults(account, q))
-                .distinctUntilChanged();
-    }
-
-    public Observable<List<Observable<SmartListViewModel>>> getFullList(Observable<Account> currentAccount, Observable<String> query, boolean hasPresence) {
-        return currentAccount.switchMap(account -> Observable.combineLatest(
-                account.getConversationsSubject(),
-                getSearchResults(account, query),
-                query,
-                (conversations, searchResults, q) -> {
-                    List<Observable<SmartListViewModel>> newList = new ArrayList<>(conversations.size() + searchResults.size() + 2);
-                    if (!searchResults.isEmpty()) {
-                        newList.add(SmartListViewModel.TITLE_PUBLIC_DIR);
-                        newList.addAll(searchResults);
-                    }
-                    if (!conversations.isEmpty()) {
-                        if (q.isEmpty()) {
-                            for (Conversation conversation : conversations)
-                                newList.add(observeConversation(account, conversation, hasPresence));
-                        } else {
-                            String lq = q.toLowerCase();
-                            newList.add(SmartListViewModel.TITLE_CONVERSATIONS);
-                            int nRes = 0;
-                            for (Conversation conversation : conversations) {
-                                if (conversation.matches(lq)) {
-                                    newList.add(observeConversation(account, conversation, hasPresence));
-                                    nRes++;
-                                }
-                            }
-                            if (nRes == 0)
-                                newList.remove(newList.size() - 1);
-                        }
-                    }
-                    return newList;
-                }));
-    }
-
-    /**
-     * Loads the smartlist from the database and updates the view
-     *
-     * @param account the user account
-     */
-    private Single<Account> getSmartlist(final Account account) {
-        List<Completable> actions = new ArrayList<>(account.getConversations().size() + 1);
-        for (Conversation c : account.getConversations())  {
-            if (c.isSwarm())
-                actions.add(c.getLastElementLoaded());
-        }
-        actions.add(mHistoryService.getSmartlist(account.getAccountID())
-                .flatMapCompletable(conversationHistoryList -> Completable.fromAction(() -> {
-                    List<Conversation> conversations = new ArrayList<>();
-                    for (Interaction e : conversationHistoryList) {
-                        Conversation conversation = account.getByUri(e.getConversation().getParticipant());
-                        if (conversation == null)
-                            continue;
-                        conversation.setId(e.getConversation().getId());
-                        conversation.addElement(e);
-                        conversations.add(conversation);
-                    }
-                    account.setHistoryLoaded(conversations);
-                })));
-        return Completable.merge(actions)
-                .andThen(Single.just(account))
-                .cache();
-    }
-
-    /**
-     * Loads a conversation's history from the database
-     *
-     * @param conversation a conversation object with a valid conversation ID
-     * @return a conversation single
-     */
-    private Single<Conversation> getConversationHistory(final Conversation conversation) {
-        Log.d(TAG, "getConversationHistory() " + conversation.getAccountId() + " " + conversation.getUri());
-
-        return mHistoryService.getConversationHistory(conversation.getAccountId(), conversation.getId())
-                .map(loadedConversation -> {
-                    /*if (loadedConversation.isEmpty())
-                        return conversation;*/
-                    conversation.clearHistory(true);
-                    conversation.setHistory(loadedConversation);
-                    return conversation;
-                })
-                .cache();
-    }
-
-    public Completable clearHistory(final String accountId, final Uri contact) {
-        return mHistoryService
-                .clearHistory(contact.getUri(), accountId, false)
-                .doOnSubscribe(s -> {
-                    Account account = mAccountService.getAccount(accountId);
-                    if (account != null) {
-                        account.clearHistory(contact, false);
-                    }
-                });
-    }
-
-    public Completable clearAllHistory() {
-        List<Account> accounts = mAccountService.getAccounts();
-        return mHistoryService
-                .clearHistory(accounts)
-                .doOnSubscribe(s -> {
-                    for (Account account : accounts) {
-                        if (account != null) {
-                            account.clearAllHistory();
-                        }
-                    }
-                });
-    }
-
-    public void updateTextNotifications(String accountId, List<Conversation> conversations) {
-        Log.d(TAG, "updateTextNotifications() " + accountId + " " + conversations.size());
-
-        for (Conversation conversation : conversations) {
-            mNotificationService.showTextNotification(accountId, conversation);
-        }
-    }
-
-    private void parseNewMessage(final TextMessage txt) {
-        if (txt.isRead()) {
-            if (txt.getMessageId() == null) {
-                mHistoryService.updateInteraction(txt, txt.getAccount()).subscribe();
-            }
-            if (mPreferencesService.getSettings().isAllowReadIndicator()) {
-                if (txt.getMessageId() != null) {
-                    mAccountService.setMessageDisplayed(txt.getAccount(), new Uri(Uri.SWARM_SCHEME, txt.getConversationId()), txt.getMessageId());
-                } else {
-                    mAccountService.setMessageDisplayed(txt.getAccount(), new Uri(Uri.JAMI_URI_SCHEME, txt.getAuthor()), txt.getDaemonIdString());
-                }
-            }
-        }
-        getAccountSubject(txt.getAccount())
-                .flatMapObservable(Account::getConversationsSubject)
-                .firstOrError()
-                .subscribeOn(Schedulers.io())
-                .subscribe(c -> updateTextNotifications(txt.getAccount(), c), e -> Log.e(TAG, e.getMessage()));
-    }
-
-    public void acceptRequest(String accountId, Uri contactUri) {
-        if (accountId == null || contactUri == null)
-            return;
-        mPreferencesService.removeRequestPreferences(accountId, contactUri.getRawRingId());
-        mAccountService.acceptTrustRequest(accountId, contactUri);
-    }
-
-    public void discardRequest(String accountId, Uri contact) {
-        mHistoryService.clearHistory(contact.getUri(), accountId, true).subscribe();
-        mPreferencesService.removeRequestPreferences(accountId, contact.getRawRingId());
-        mAccountService.discardTrustRequest(accountId, contact);
-    }
-
-    private void handleDataTransferEvent(DataTransfer transfer) {
-        Conversation conversation = mAccountService.getAccount(transfer.getAccount()).onDataTransferEvent(transfer);
-        Interaction.InteractionStatus status = transfer.getStatus();
-        if ((status == Interaction.InteractionStatus.TRANSFER_CREATED || status == Interaction.InteractionStatus.FILE_AVAILABLE) && !transfer.isOutgoing()) {
-            if (transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.getAccount()))) {
-                mAccountService.acceptFileTransfer(conversation, transfer.getFileId(), transfer);
-                return;
-            }
-        }
-        mNotificationService.handleDataTransferNotification(transfer, conversation, conversation.isVisible());
-    }
-
-    private void onConfStateChange(Conference conference) {
-        Log.d(TAG, "onConfStateChange Thread id: " + Thread.currentThread().getId());
-    }
-
-    private void onCallStateChange(Call call) {
-        Log.d(TAG, "onCallStateChange Thread id: " + Thread.currentThread().getId());
-        Call.CallStatus newState = call.getCallStatus();
-        boolean incomingCall = newState == Call.CallStatus.RINGING && call.isIncoming();
-        mHardwareService.updateAudioState(newState, incomingCall, !call.isAudioOnly());
-
-        Account account = mAccountService.getAccount(call.getAccount());
-        if (account == null)
-            return;
-        Contact contact = call.getContact();
-        String conversationId = call.getConversationId();
-        Log.w(TAG, "CallStateChange " + call.getDaemonIdString() + " conversationId:" + conversationId);
-
-        Conversation conversation = conversationId == null
-                ? (contact == null ? null : account.getByUri(contact.getUri()))
-                : account.getSwarm(conversationId);
-        Conference conference = null;
-        if (conversation != null) {
-            conference = conversation.getConference(call.getDaemonIdString());
-            if (conference == null) {
-                if (newState == Call.CallStatus.OVER)
-                    return;
-                conference = new Conference(call);
-                conversation.addConference(conference);
-                account.updated(conversation);
-            }
-        }
-
-        Log.w(TAG, "CALL_STATE_CHANGED : updating call state to " + newState);
-        if ((call.isRinging() || newState == Call.CallStatus.CURRENT) && call.getTimestamp() == 0) {
-            call.setTimestamp(System.currentTimeMillis());
-        }
-
-        if (incomingCall) {
-            mNotificationService.handleCallNotification(conference, false);
-            mHardwareService.setPreviewSettings();
-        } else if ((newState == Call.CallStatus.CURRENT && call.isIncoming())
-                || newState == Call.CallStatus.RINGING && !call.isIncoming()) {
-            mNotificationService.handleCallNotification(conference, false);
-            mAccountService.sendProfile(call.getDaemonIdString(), call.getAccount());
-        } else if (newState == Call.CallStatus.HUNGUP
-                || newState == Call.CallStatus.BUSY
-                || newState == Call.CallStatus.FAILURE
-                || newState == Call.CallStatus.OVER) {
-            mNotificationService.handleCallNotification(conference, true);
-            mHardwareService.closeAudioState();
-            long now = System.currentTimeMillis();
-            if (call.getTimestamp() == 0) {
-                call.setTimestamp(now);
-            }
-            if (newState == Call.CallStatus.HUNGUP || call.getTimestampEnd() == 0) {
-                call.setTimestampEnd(now);
-            }
-            if (conference != null && conference.removeParticipant(call) && !conversation.isSwarm()) {
-                Log.w(TAG, "Adding call history for conversation " + conversation.getUri());
-                mHistoryService.insertInteraction(account.getAccountID(), conversation, call).subscribe();
-                conversation.addCall(call);
-                if (call.isIncoming() && call.isMissed()) {
-                    mNotificationService.showMissedCallNotification(call);
-                }
-                account.updated(conversation);
-            }
-            mCallService.removeCallForId(call.getDaemonIdString());
-            if (conversation != null && conference.getParticipants().isEmpty()) {
-                conversation.removeConference(conference);
-            }
-        }
-    }
-
-    public Single<Call> placeCall(String accountId, Uri contactUri, boolean video) {
-        //String rawId = contactUri.getRawRingId();
-        return getAccountSubject(accountId).flatMap(account -> {
-            //CallContact contact = account.getContact(rawId);
-            //if (contact == null)
-            //    mAccountService.addContact(accountId, rawId);
-            return mCallService.placeCall(accountId, null, contactUri, video);
-        });
-    }
-
-    public void cancelFileTransfer(String accountId, Uri conversationId, String messageId, String fileId) {
-        mAccountService.cancelDataTransfer(accountId, conversationId.isSwarm() ? conversationId.getRawRingId() : "", messageId, fileId);
-        mNotificationService.removeTransferNotification(accountId, conversationId, fileId);
-        DataTransfer transfer = mAccountService.getAccount(accountId).getDataTransfer(fileId);
-        if (transfer != null)
-            deleteConversationItem((Conversation) transfer.getConversation(), transfer);
-    }
-
-    public Completable removeConversation(String accountId, Uri conversationUri) {
-        if (conversationUri.isSwarm()) {
-            // For a one to one conversation, contact is strongly related, so remove the contact.
-            // This will remove related conversations
-            Account account = mAccountService.getAccount(accountId);
-            Conversation conversation = account.getSwarm(conversationUri.getRawRingId());
-            if (conversation != null && conversation.getMode().blockingFirst() == Conversation.Mode.OneToOne) {
-                Contact contact = conversation.getContact();
-                mAccountService.removeContact(accountId, contact.getUri().getRawRingId(), false);
-                return Completable.complete();
-            } else {
-                return mAccountService.removeConversation(accountId, conversationUri);
-            }
-        } else {
-            return mHistoryService
-                    .clearHistory(conversationUri.getUri(), accountId, true)
-                    .doOnSubscribe(s -> {
-                        Account account = mAccountService.getAccount(accountId);
-                        account.clearHistory(conversationUri, true);
-                        mAccountService.removeContact(accountId, conversationUri.getRawRingId(), false);
-                    });
-        }
-    }
-
-    public void banConversation(String accountId, Uri conversationUri) {
-        if (conversationUri.isSwarm()) {
-            startConversation(accountId, conversationUri)
-                    .subscribe(conversation -> {
-                        try {
-                            Contact contact = conversation.getContact();
-                            mAccountService.removeContact(accountId, contact.getUri().getRawUriString(), true);
-                        } catch (Exception e) {
-                            mAccountService.removeConversation(accountId, conversationUri);
-                        }
-                    });
-            //return mAccountService.removeConversation(accountId, conversationUri);
-        } else {
-            mAccountService.removeContact(accountId, conversationUri.getRawUriString(), true);
-        }
-    }
-
-
-    public Single<Conversation> createConversation(String accountId, Collection<Contact> currentSelection) {
-        List<String> contactIds = new ArrayList<>(currentSelection.size());
-        for (Contact contact : currentSelection)
-            contactIds.add(contact.getPrimaryNumber());
-        return mAccountService.startConversation(accountId, contactIds);
-    }
-}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Account.java b/ring-android/libringclient/src/main/java/net/jami/model/Account.java
deleted file mode 100644
index c41dfaf8a..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Account.java
+++ /dev/null
@@ -1,1124 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
- *
- *  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 net.jami.model;
-
-import net.jami.services.AccountService;
-import net.jami.smartlist.SmartListViewModel;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class Account {
-    private static final String TAG = Account.class.getSimpleName();
-
-    private static final String CONTACT_ADDED = "added";
-    private static final String CONTACT_CONFIRMED = "confirmed";
-    private static final String CONTACT_BANNED = "banned";
-    private static final String CONTACT_ID = "id";
-    private static final int LOCATION_SHARING_EXPIRATION_MS = 1000 * 60 * 2;
-
-    private final String accountID;
-
-    private AccountConfig mVolatileDetails;
-    private AccountConfig mDetails;
-    private String mUsername;
-
-    private final ArrayList<AccountCredentials> credentialsDetails = new ArrayList<>();
-    private Map<String, String> devices = new HashMap<>();
-    private final Map<String, Contact> mContacts = new HashMap<>();
-    private final Map<String, TrustRequest> mRequests = new HashMap<>();
-    private final Map<String, Contact> mContactCache = new HashMap<>();
-    private final Map<String, Conversation> swarmConversations = new HashMap<>();
-    private final HashMap<String, DataTransfer> mDataTransfers = new HashMap<>();
-
-    private final Map<String, Conversation> conversations = new HashMap<>();
-    private final Map<String, Conversation> pending = new HashMap<>();
-    private final Map<String, Conversation> cache = new HashMap<>();
-
-    private final List<Conversation> sortedConversations = new ArrayList<>();
-    private final List<Conversation> sortedPending = new ArrayList<>();
-
-    public boolean registeringUsername = false;
-    private boolean conversationsChanged = true;
-    private boolean pendingsChanged = true;
-    private boolean historyLoaded = false;
-
-    private final Subject<Conversation> conversationSubject = PublishSubject.create();
-    private final Subject<List<Conversation>> conversationsSubject = BehaviorSubject.create();
-    private final Subject<List<Conversation>> pendingSubject = BehaviorSubject.create();
-    private final Subject<Integer> unreadConversationsSubject = BehaviorSubject.create();
-    private final Subject<Integer> unreadPendingSubject = BehaviorSubject.create();
-    private final Observable<Integer> unreadConversationsCount = unreadConversationsSubject.distinctUntilChanged();
-    private final Observable<Integer> unreadPendingCount = unreadPendingSubject.distinctUntilChanged();
-
-    private final BehaviorSubject<Collection<Contact>> contactListSubject = BehaviorSubject.create();
-
-    private final Map<Contact, Observable<ContactLocation>> contactLocations = new HashMap<>();
-    private final Subject<Map<Contact, Observable<ContactLocation>>> mLocationSubject = BehaviorSubject.createDefault(contactLocations);
-    private final Subject<ContactLocationEntry> mLocationStartedSubject = PublishSubject.create();
-
-    public Single<Account> historyLoader;
-    private Single<Tuple<String, Object>> mLoadedProfile = null;
-
-    public Account(String bAccountID) {
-        accountID = bAccountID;
-        mDetails = new AccountConfig();
-        mVolatileDetails = new AccountConfig();
-    }
-
-    public Account(String bAccountID, final Map<String, String> details,
-                   final List<Map<String, String>> credentials,
-                   final Map<String, String> volDetails) {
-        accountID = bAccountID;
-        setDetails(details);
-        mVolatileDetails = new AccountConfig(volDetails);
-        setCredentials(credentials);
-    }
-
-    public void cleanup() {
-        conversationSubject.onComplete();
-        conversationsSubject.onComplete();
-        pendingSubject.onComplete();
-        contactListSubject.onComplete();
-        //trustRequestsSubject.onComplete();
-    }
-
-    public boolean canSearch() {
-        return !StringUtils.isEmpty(getDetail(ConfigKey.MANAGER_URI));
-    }
-
-    public boolean isContact(Conversation conversation) {
-        Contact contact = conversation.getContact();
-        return contact != null && getContact(contact.getUri().getRawRingId()) != null;
-    }
-
-    public void conversationStarted(Conversation conversation) {
-        Log.w(TAG, "conversationStarted " + conversation.getAccountId() + " " + conversation.getUri() + " " + conversation.isSwarm() + " " + conversation.getContacts().size());
-        synchronized (conversations) {
-            if (conversation.isSwarm() && conversation.getMode() == Conversation.Mode.OneToOne) {
-                Contact contact = conversation.getContact();
-                String key = contact.getUri().getUri();
-                Conversation removed = cache.remove(key);
-                conversations.remove(key);
-                //Conversation contactConversation = getByUri(contact.getPrimaryUri());
-                //Log.w(TAG, "conversationStarted " + conversation.getAccountId() + " contact " + key + " " + removed);
-                /*if (contactConversation != null) {
-                    conversations.remove(contactConversation.getUri().getUri());
-                }*/
-                contact.setConversationUri(conversation.getUri());
-            }
-            conversations.put(conversation.getUri().getUri(), conversation);
-            conversationChanged();
-        }
-    }
-    public Conversation getSwarm(String conversationId) {
-        synchronized (conversations) {
-            return swarmConversations.get(conversationId);
-        }
-    }
-
-    public Conversation newSwarm(String conversationId, Conversation.Mode mode) {
-        synchronized (conversations) {
-            Conversation c = swarmConversations.get(conversationId);
-            if (c == null) {
-                c = new Conversation(accountID, new Uri(Uri.SWARM_SCHEME, conversationId), mode);
-                swarmConversations.put(conversationId, c);
-            }
-            return c;
-        }
-    }
-
-    public void removeSwarm(String conversationId) {
-        Log.w(TAG, "removeSwarm " + conversationId);
-        synchronized (conversations) {
-            Conversation conversation = swarmConversations.remove(conversationId);
-            if (conversation != null) {
-                Conversation c = conversations.remove(conversation.getUri().getUri());
-                try {
-                    Contact contact = c.getContact();
-                    Log.w(TAG, "removeSwarm: adding back contact conversation " + contact + " " + contact.getConversationUri().blockingFirst() + " " + c.getUri());
-                    if (contact.getConversationUri().blockingFirst().equals(c.getUri()))  {
-                        contact.setConversationUri(contact.getUri());
-                        contactAdded(contact);
-                    }
-                } catch (Exception ignored) {}
-                conversationChanged();
-            }
-        }
-    }
-
-    public static class ContactLocation {
-        public double latitude;
-        public double longitude;
-        public long timestamp;
-        public Date receivedDate;
-    }
-    public static class ContactLocationEntry {
-        public Contact contact;
-        public Observable<ContactLocation> location;
-    }
-    public enum ComposingStatus {
-        Idle,
-        Active;
-
-        public static ComposingStatus fromInt(int status) {
-            return status == 1 ? Active : Idle;
-        }
-    }
-
-    public Observable<List<Conversation>> getConversationsSubject() {
-        return conversationsSubject;
-    }
-
-    public Observable<List<SmartListViewModel>> getConversationsViewModels(boolean withPresence) {
-        return conversationsSubject
-                .map(conversations -> {
-                    ArrayList<SmartListViewModel> viewModel = new ArrayList<>(conversations.size());
-                    for (Conversation c : conversations)
-                        viewModel.add(new SmartListViewModel(c, withPresence));
-                    return viewModel;
-                });
-    }
-
-    public Observable<Conversation> getConversationSubject() {
-        return conversationSubject;
-    }
-
-    public Observable<List<Conversation>> getPendingSubject() {
-        return pendingSubject;
-    }
-
-    public Collection<Conversation> getConversations() {
-        return conversations.values();
-    }
-
-    public Collection<Conversation> getPending() {
-        return pending.values();
-    }
-
-    public Observable<Integer> getUnreadConversations() {
-        return unreadConversationsCount;
-    }
-
-    public Observable<Integer> getUnreadPending() {
-        return unreadPendingCount;
-    }
-
-    private void pendingRefreshed() {
-        if (historyLoaded) {
-            pendingSubject.onNext(getSortedPending());
-            updateUnreadPending();
-        }
-    }
-
-    private void pendingChanged() {
-        pendingsChanged = true;
-        pendingRefreshed();
-    }
-
-    private void pendingUpdated(Conversation conversation) {
-        if (!historyLoaded)
-            return;
-        if (pendingsChanged) {
-            getSortedPending();
-        } else {
-            if (conversation != null)
-                conversation.sortHistory();
-            Collections.sort(sortedPending, (a, b) -> Interaction.compare(b.getLastEvent(), a.getLastEvent()));
-        }
-        pendingSubject.onNext(getSortedPending());
-    }
-
-    private void conversationRefreshed(Conversation conversation) {
-        if (historyLoaded) {
-            conversationSubject.onNext(conversation);
-            updateUnreadConversations();
-        }
-    }
-
-    public void conversationChanged() {
-        synchronized (conversations) {
-            conversationsChanged = true;
-            if (historyLoaded) {
-                conversationsSubject.onNext(new ArrayList<>(getSortedConversations()));
-            }
-            updateUnreadConversations();
-        }
-    }
-
-    public void conversationUpdated(Conversation conversation) {
-        synchronized (conversations) {
-            if (!historyLoaded)
-                return;
-            if (conversationsChanged) {
-                getSortedConversations();
-            } else {
-                if (conversation != null)
-                    conversation.sortHistory();
-                Collections.sort(sortedConversations, (a, b) -> Interaction.compare(b.getLastEvent(), a.getLastEvent()));
-            }
-            conversationsSubject.onNext(new ArrayList<>(sortedConversations));
-            updateUnreadConversations();
-        }
-    }
-
-    private void updateUnreadConversations() {
-        int unread = 0;
-        for (Conversation model : sortedConversations) {
-            Interaction last = model.getLastEvent();
-            if (last != null && !last.isRead())
-                unread++;
-        }
-        // Log.w(TAG, "updateUnreadConversations " + unread);
-        unreadConversationsSubject.onNext(unread);
-    }
-
-    private void updateUnreadPending() {
-        unreadPendingSubject.onNext(sortedPending.size());
-    }
-
-    /**
-     * Clears a conversation
-     *
-     * @param contact the contact
-     * @param delete  true if you want to remove the conversation
-     */
-    public void clearHistory(Uri contact, boolean delete) {
-        Conversation conversation = getByUri(contact);
-        // if it is a sip account, we do not add a contact event
-        conversation.clearHistory(delete || isSip());
-        conversationChanged();
-    }
-
-    public void clearAllHistory() {
-        for (Conversation conversation : getConversations()) {
-            // if it is a sip account, we do not add a contact event
-            conversation.clearHistory(isSip());
-        }
-        for (Conversation conversation : pending.values()) {
-            conversation.clearHistory(true);
-        }
-        conversationChanged();
-        pendingChanged();
-    }
-
-    public void updated(Conversation conversation) {
-        String key = conversation.getUri().getUri();
-        synchronized (conversations) {
-            if (conversation == conversations.get(key)) {
-                conversationUpdated(conversation);
-                return;
-            }
-        }
-        synchronized (pending) {
-            if (conversation == pending.get(key)) {
-                pendingUpdated(conversation);
-                return;
-            }
-        }
-        if (conversation == cache.get(key)) {
-            if (isJami() && !conversation.isSwarm() && conversation.getContacts().size() == 1 && !conversation.getContact().getConversationUri().blockingFirst().equals(conversation.getUri()))  {
-                return;
-            }
-            if (mContacts.containsKey(key) || !isJami()) {
-                Log.w(TAG, "updated " + conversation.getAccountId() + " contact " + key);
-                conversations.put(key, conversation);
-                conversationChanged();
-            } else {
-                pending.put(key, conversation);
-                pendingChanged();
-            }
-        }
-    }
-
-    public void refreshed(Conversation conversation) {
-        synchronized (conversations) {
-            if (conversations.containsValue(conversation)) {
-                conversationRefreshed(conversation);
-                return;
-            }
-        }
-        synchronized (pending) {
-            if (pending.containsValue(conversation))
-                pendingRefreshed();
-        }
-    }
-
-    public void addTextMessage(TextMessage txt) {
-        Conversation conversation = null;
-        String daemonId = txt.getDaemonIdString();
-        if (daemonId != null && !StringUtils.isEmpty(daemonId)) {
-            conversation = getConversationByCallId(daemonId);
-        }
-        if (conversation == null) {
-            conversation = getByKey(txt.getConversation().getParticipant());
-            txt.setContact(conversation.getContact());
-        }
-        conversation.addTextMessage(txt);
-        updated(conversation);
-    }
-
-    public Conversation onDataTransferEvent(DataTransfer transfer) {
-        Log.d(TAG, "Account onDataTransferEvent " + transfer.getMessageId());
-        Conversation conversation = (Conversation) transfer.getConversation();
-        Interaction.InteractionStatus transferEventCode = transfer.getStatus();
-        if (transferEventCode == Interaction.InteractionStatus.TRANSFER_CREATED) {
-            conversation.addFileTransfer(transfer);
-        } else {
-            conversation.updateFileTransfer(transfer, transferEventCode);
-        }
-        updated(conversation);
-        return conversation;
-    }
-
-    public Observable<Collection<Contact>> getBannedContactsUpdates() {
-        return contactListSubject.concatMapSingle(list -> Observable.fromIterable(list).filter(Contact::isBanned).toList());
-    }
-
-    public Contact getContactFromCache(String key) {
-        if (StringUtils.isEmpty(key))
-            return null;
-        synchronized (mContactCache) {
-            Contact contact = mContactCache.get(key);
-            if (contact == null) {
-                if (isSip())
-                    contact = Contact.buildSIP(Uri.fromString(key));
-                else
-                    contact = Contact.build(key, isMe(key));
-                mContactCache.put(key, contact);
-            }
-            return contact;
-        }
-    }
-
-    boolean isMe(String uri) {
-        //Log.w(TAG, "isMe " + uri + " " + getUsername());
-        return getUsername().equals(uri);
-    }
-
-    public Contact getContactFromCache(Uri uri) {
-        return getContactFromCache(uri.getUri());
-    }
-
-    public void dispose() {
-        contactListSubject.onComplete();
-        //trustRequestsSubject.onComplete();
-    }
-
-    public Map<String, String> getDevices() {
-        return devices;
-    }
-
-    public void setCredentials(List<Map<String, String>> credentials) {
-        credentialsDetails.clear();
-        if (credentials != null) {
-            credentialsDetails.ensureCapacity(credentials.size());
-            for (int i = 0; i < credentials.size(); ++i) {
-                credentialsDetails.add(new AccountCredentials(credentials.get(i)));
-            }
-        }
-    }
-
-    public void setDetails(Map<String, String> details) {
-        mDetails = new AccountConfig(details);
-        mUsername = mDetails.get(ConfigKey.ACCOUNT_USERNAME);
-    }
-
-    public void setDetail(ConfigKey key, String val) {
-        mDetails.put(key, val);
-    }
-
-    public void setDetail(ConfigKey key, boolean val) {
-        mDetails.put(key, val);
-    }
-
-    public AccountConfig getConfig() {
-        return mDetails;
-    }
-
-    public void setDevices(Map<String, String> devs) {
-        devices = devs;
-    }
-
-    public String getAccountID() {
-        return accountID;
-    }
-
-    public String getUsername() {
-        return mUsername;
-    }
-
-    public String getDisplayname() {
-        return mDetails.get(ConfigKey.ACCOUNT_DISPLAYNAME);
-    }
-
-    public String getDisplayUsername() {
-        if (isJami()) {
-            String registeredName = getRegisteredName();
-            if (registeredName != null && !registeredName.isEmpty()) {
-                return registeredName;
-            }
-        }
-        return getUsername();
-    }
-
-    public String getHost() {
-        return mDetails.get(ConfigKey.ACCOUNT_HOSTNAME);
-    }
-
-    public void setHost(String host) {
-        mDetails.put(ConfigKey.ACCOUNT_HOSTNAME, host);
-    }
-
-    public String getProxy() {
-        return mDetails.get(ConfigKey.ACCOUNT_ROUTESET);
-    }
-
-    public void setProxy(String proxy) {
-        mDetails.put(ConfigKey.ACCOUNT_ROUTESET, proxy);
-    }
-
-    public boolean isDhtProxyEnabled() {
-        return mDetails.getBool(ConfigKey.PROXY_ENABLED);
-    }
-
-    public void setDhtProxyEnabled(boolean active) {
-        mDetails.put(ConfigKey.PROXY_ENABLED, active ? "true" : "false");
-    }
-
-    public String getRegistrationState() {
-        return mVolatileDetails.get(ConfigKey.ACCOUNT_REGISTRATION_STATUS);
-    }
-
-    public void setRegistrationState(String registeredState, int code) {
-        mVolatileDetails.put(ConfigKey.ACCOUNT_REGISTRATION_STATUS, registeredState);
-        mVolatileDetails.put(ConfigKey.ACCOUNT_REGISTRATION_STATE_CODE, Integer.toString(code));
-    }
-
-    public void setVolatileDetails(Map<String, String> volatileDetails) {
-        mVolatileDetails = new AccountConfig(volatileDetails);
-    }
-
-    public String getRegisteredName() {
-        return mVolatileDetails.get(ConfigKey.ACCOUNT_REGISTERED_NAME);
-    }
-
-    public String getAlias() {
-        return mDetails.get(ConfigKey.ACCOUNT_ALIAS);
-    }
-
-    public Boolean isSip() {
-        return mDetails.get(ConfigKey.ACCOUNT_TYPE).equals(AccountConfig.ACCOUNT_TYPE_SIP);
-    }
-
-    public Boolean isJami() {
-        return mDetails.get(ConfigKey.ACCOUNT_TYPE).equals(AccountConfig.ACCOUNT_TYPE_RING);
-    }
-
-    public void setAlias(String alias) {
-        mDetails.put(ConfigKey.ACCOUNT_ALIAS, alias);
-    }
-
-    private String getDetail(ConfigKey key) {
-        return mDetails.get(key);
-    }
-
-    public boolean getDetailBoolean(ConfigKey key) {
-        return mDetails.getBool(key);
-    }
-
-    public boolean isEnabled() {
-        return mDetails.getBool(ConfigKey.ACCOUNT_ENABLE);
-    }
-
-    public boolean isActive() {
-        return mVolatileDetails.getBool(ConfigKey.ACCOUNT_ACTIVE);
-    }
-
-    public void setEnabled(boolean isChecked) {
-        mDetails.put(ConfigKey.ACCOUNT_ENABLE, isChecked);
-    }
-
-    public boolean hasPassword() {
-        return mDetails.getBool(ConfigKey.ARCHIVE_HAS_PASSWORD);
-    }
-
-    public boolean hasManager() {
-        return !mDetails.get(ConfigKey.MANAGER_URI).isEmpty();
-    }
-
-    public HashMap<String, String> getDetails() {
-        return mDetails.getAll();
-    }
-
-    public boolean isTrying() {
-        return getRegistrationState().contentEquals(AccountConfig.STATE_TRYING);
-    }
-
-    public boolean isRegistered() {
-        return (getRegistrationState().contentEquals(AccountConfig.STATE_READY) || getRegistrationState().contentEquals(AccountConfig.STATE_REGISTERED));
-    }
-
-    public boolean isInError() {
-        String state = getRegistrationState();
-        return (state.contentEquals(AccountConfig.STATE_ERROR)
-                || state.contentEquals(AccountConfig.STATE_ERROR_AUTH)
-                || state.contentEquals(AccountConfig.STATE_ERROR_CONF_STUN)
-                || state.contentEquals(AccountConfig.STATE_ERROR_EXIST_STUN)
-                || state.contentEquals(AccountConfig.STATE_ERROR_GENERIC)
-                || state.contentEquals(AccountConfig.STATE_ERROR_HOST)
-                || state.contentEquals(AccountConfig.STATE_ERROR_NETWORK)
-                || state.contentEquals(AccountConfig.STATE_ERROR_NOT_ACCEPTABLE)
-                || state.contentEquals(AccountConfig.STATE_ERROR_SERVICE_UNAVAILABLE)
-                || state.contentEquals(AccountConfig.STATE_REQUEST_TIMEOUT));
-    }
-
-    public boolean isIP2IP() {
-        boolean emptyHost = getHost() == null || (getHost() != null && getHost().isEmpty());
-        return isSip() && emptyHost;
-    }
-
-    public boolean isAutoanswerEnabled() {
-        return mDetails.getBool(ConfigKey.ACCOUNT_AUTOANSWER);
-    }
-
-    public ArrayList<AccountCredentials> getCredentials() {
-        return credentialsDetails;
-    }
-
-    public void addCredential(AccountCredentials newValue) {
-        credentialsDetails.add(newValue);
-    }
-
-    public void removeCredential(AccountCredentials accountCredentials) {
-        credentialsDetails.remove(accountCredentials);
-    }
-
-    public List<Map<String, String>> getCredentialsHashMapList() {
-        ArrayList<Map<String, String>> result = new ArrayList<>(credentialsDetails.size());
-        for (AccountCredentials cred : credentialsDetails) {
-            result.add(cred.getDetails());
-        }
-        return result;
-    }
-
-    private String getUri(boolean display) {
-        String username = display ? getDisplayUsername() : getUsername();
-        if (isJami()) {
-            return username;
-        } else {
-            return username + "@" + getHost();
-        }
-    }
-
-    public String getUri() {
-        return getUri(false);
-    }
-
-    public String getDisplayUri() {
-        return getUri(true);
-    }
-    public String getDisplayUri(CharSequence defaultNameSip) {
-        return isIP2IP() ? defaultNameSip.toString() : getDisplayUri();
-    }
-
-    public boolean needsMigration() {
-        return AccountConfig.STATE_NEED_MIGRATION.equals(getRegistrationState());
-    }
-
-    public String getDeviceId() {
-        return getDetail(ConfigKey.ACCOUNT_DEVICE_ID);
-    }
-
-    public String getDeviceName() {
-        return getDetail(ConfigKey.ACCOUNT_DEVICE_NAME);
-    }
-
-    public Map<String, Contact> getContacts() {
-        return mContacts;
-    }
-
-    public List<Contact> getBannedContacts() {
-        ArrayList<Contact> banned = new ArrayList<>();
-        for (Contact contact : mContacts.values()) {
-            if (contact.isBanned()) {
-                banned.add(contact);
-            }
-        }
-        return banned;
-    }
-
-    public Contact getContact(String ringId) {
-        return mContacts.get(ringId);
-    }
-
-    public void addContact(String id, boolean confirmed) {
-        Contact contact = mContacts.get(id);
-        if (contact == null) {
-            contact = getContactFromCache(Uri.fromId(id));
-            mContacts.put(id, contact);
-        }
-        contact.setAddedDate(new Date());
-        if (confirmed) {
-            contact.setStatus(Contact.Status.CONFIRMED);
-        } else {
-            contact.setStatus(Contact.Status.REQUEST_SENT);
-        }
-        TrustRequest req = mRequests.get(id);
-        if (req != null) {
-            mRequests.remove(id);
-        }
-        contactAdded(contact);
-        contactListSubject.onNext(mContacts.values());
-    }
-
-    public void removeContact(String id, boolean banned) {
-        Contact contact = mContacts.get(id);
-        if (banned) {
-            if (contact == null) {
-                contact = getContactFromCache(Uri.fromId(id));
-                mContacts.put(id, contact);
-            }
-            contact.setStatus(Contact.Status.BANNED);
-        } else {
-            mContacts.remove(id);
-        }
-        TrustRequest req = mRequests.get(id);
-        if (req != null) {
-            mRequests.remove(id);
-        }
-        if (contact != null) {
-            contactRemoved(contact.getUri());
-        }
-        contactListSubject.onNext(mContacts.values());
-    }
-
-    private void addContact(Map<String, String> contact) {
-        String contactId = contact.get(CONTACT_ID);
-        Contact callContact = mContacts.get(contactId);
-        if (callContact == null) {
-            callContact = getContactFromCache(Uri.fromId(contactId));
-        }
-        String addedStr = contact.get(CONTACT_ADDED);
-        if (!StringUtils.isEmpty(addedStr)) {
-            long added = Long.parseLong(contact.get(CONTACT_ADDED));
-            callContact.setAddedDate(new Date(added * 1000));
-        }
-        if (contact.containsKey(CONTACT_BANNED) && contact.get(CONTACT_BANNED).equals("true")) {
-            callContact.setStatus(Contact.Status.BANNED);
-        } else if (contact.containsKey(CONTACT_CONFIRMED)) {
-            callContact.setStatus(Boolean.parseBoolean(contact.get(CONTACT_CONFIRMED)) ?
-                    Contact.Status.CONFIRMED :
-                    Contact.Status.REQUEST_SENT);
-        }
-        mContacts.put(contactId, callContact);
-        contactAdded(callContact);
-    }
-
-    public void setContacts(List<Map<String, String>> contacts) {
-        for (Map<String, String> contact : contacts) {
-            addContact(contact);
-        }
-        contactListSubject.onNext(mContacts.values());
-    }
-
-    public List<TrustRequest> getRequests() {
-        ArrayList<TrustRequest> requests = new ArrayList<>(mRequests.size());
-        for (TrustRequest request : mRequests.values()) {
-            if (request.isNameResolved()) {
-                requests.add(request);
-            }
-        }
-        return requests;
-    }
-
-    public TrustRequest getRequest(Uri uri) {
-        return mRequests.get(uri.getUri());
-    }
-
-    public void addRequest(TrustRequest request) {
-        synchronized (pending) {
-            String key = request.getUri().getUri();
-            mRequests.put(key, request);
-            Conversation conversation = pending.get(key);
-            if (conversation == null) {
-                conversation = getByKey(key);
-                pending.put(key, conversation);
-                if (!conversation.isSwarm()) {
-                    Contact contact = getContactFromCache(request.getUri());
-                    conversation.addRequestEvent(request, contact);
-                }
-                pendingChanged();
-            }
-        }
-    }
-
-    public void setRequests(List<TrustRequest> requests) {
-        Log.w(TAG, "setRequests " + requests.size());
-        synchronized (pending) {
-            for (TrustRequest request : requests) {
-                String key = request.getUri().getUri();
-                mRequests.put(key, request);
-                Conversation conversation = pending.get(key);
-                if (conversation == null) {
-                    conversation = getByKey(key);
-                    pending.put(key, conversation);
-                    Contact contact = getContactFromCache(request.getUri());
-                    conversation.addRequestEvent(request, contact);
-                }
-            }
-            pendingChanged();
-        }
-    }
-
-    public boolean removeRequest(Uri contact) {
-        synchronized (pending) {
-            String contactUri = contact.getUri();
-            TrustRequest request = mRequests.remove(contactUri);
-            if (pending.remove(contactUri) != null) {
-                pendingChanged();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean registeredNameFound(int state, String address, String name) {
-        Uri uri = Uri.fromString(address);
-        String key = uri.getUri();
-        Contact contact = getContactFromCache(key);
-        if (contact.setUsername(state == 0 ? name : null)) {
-            synchronized (conversations) {
-                Conversation conversation = conversations.get(key);
-                if (conversation != null)
-                    conversationRefreshed(conversation);
-            }
-            synchronized (pending) {
-                if (pending.containsKey(key))
-                    pendingRefreshed();
-            }
-            return true;
-        }
-        return false;
-    }
-
-    public Conversation getByUri(Uri uri) {
-        //Log.w(TAG, "getByUri " + getAccountID() + " " + uri);
-        if (uri == null || uri.isEmpty())
-            return null;
-        return uri.isSwarm()
-                ? getSwarm(uri.getRawRingId())
-                : getByKey(uri.getUri());
-    }
-
-    public Conversation getByUri(String uri) {
-        return getByUri(Uri.fromString(uri));
-    }
-
-    private Conversation getByKey(String key) {
-        Conversation conversation = cache.get(key);
-        if (conversation != null) {
-            return conversation;
-        }
-        Contact contact = getContactFromCache(key);
-        conversation = new Conversation(getAccountID(), contact);
-        //Log.w(TAG, "getByKey " + getAccountID() + " contact " + key);
-        cache.put(key, conversation);
-        return conversation;
-    }
-
-    public void setHistoryLoaded(List<Conversation> conversations) {
-        synchronized (this.conversations) {
-            if (historyLoaded)
-                return;
-            //Log.w(TAG, "setHistoryLoaded " + getAccountID() + " " + conversations.size());
-            for (Conversation c : conversations) {
-                Contact contact = c.getContact();
-                if (!c.isSwarm() && contact != null && contact.getConversationUri().blockingFirst().equals(c.getUri()))
-                    updated(c);
-            }
-            historyLoaded = true;
-            conversationChanged();
-            pendingChanged();
-        }
-    }
-
-    private List<Conversation> getSortedConversations() {
-        if (conversationsChanged) {
-            sortedConversations.clear();
-                sortedConversations.addAll(conversations.values());
-            for (Conversation c : sortedConversations)
-                c.sortHistory();
-            Collections.sort(sortedConversations, new ConversationComparator());
-            conversationsChanged = false;
-        }
-        return sortedConversations;
-    }
-
-    private List<Conversation> getSortedPending() {
-        if (pendingsChanged) {
-            sortedPending.clear();
-            sortedPending.addAll(pending.values());
-            for (Conversation c : sortedPending)
-                c.sortHistory();
-            Collections.sort(sortedPending, new ConversationComparator());
-            pendingsChanged = false;
-        }
-        return sortedPending;
-    }
-
-    private void contactAdded(Contact contact) {
-        Uri uri = contact.getUri();
-        String key = uri.getUri();
-        //Log.w(TAG, "contactAdded " + getAccountID() + " " + uri + " " + contact.getConversationUri().blockingFirst());
-        if (!contact.getConversationUri().blockingFirst().equals(uri)) {
-            // Don't add conversation if we have a swarm conversation
-            return;
-        }
-        synchronized (conversations) {
-            if (conversations.containsKey(key))
-                return;
-            synchronized (pending) {
-                Conversation pendingConversation = pending.get(key);
-                if (pendingConversation == null) {
-                    pendingConversation = getByKey(key);
-                    conversations.put(key, pendingConversation);
-                } else {
-                    pending.remove(key);
-                    conversations.put(key, pendingConversation);
-                    pendingChanged();
-                }
-                pendingConversation.addContactEvent(contact);
-            }
-            conversationChanged();
-        }
-    }
-
-    private void contactRemoved(Uri uri) {
-        String key = uri.getUri();
-        synchronized (conversations) {
-            synchronized (pending) {
-                if (pending.remove(key) != null)
-                    pendingChanged();
-            }
-            conversations.remove(key);
-            conversationChanged();
-        }
-    }
-
-    private Conversation getConversationByCallId(String callId) {
-        for (Conversation conversation : conversations.values()) {
-            Conference conf = conversation.getConference(callId);
-            if (conf != null) {
-                return conversation;
-            }
-        }
-        return null;
-    }
-
-    public void presenceUpdate(String contactUri, boolean isOnline) {
-        //Log.w(TAG, "presenceUpdate " + contactUri + " " + isOnline);
-        Contact contact = getContactFromCache(contactUri);
-        if (contact.isOnline() == isOnline)
-            return;
-        contact.setOnline(isOnline);
-        synchronized (conversations) {
-            Conversation conversation = conversations.get(contactUri);
-            if (conversation != null) {
-                conversationRefreshed(conversation);
-            }
-        }
-        synchronized (pending) {
-            if (pending.containsKey(contactUri))
-                pendingRefreshed();
-        }
-    }
-
-    public void composingStatusChanged(String conversationId, Uri contactUri, ComposingStatus status) {
-        boolean isSwarm = !StringUtils.isEmpty(conversationId);
-        Conversation conversation = isSwarm ? getSwarm(conversationId) : getByUri(contactUri);
-        if (conversation != null) {
-            Contact contact = isSwarm ? conversation.findContact(contactUri) : getContactFromCache(contactUri);
-            if (contact != null) {
-                conversation.composingStatusChanged(contact, status);
-            }
-        }
-    }
-
-    synchronized public long onLocationUpdate(AccountService.Location location) {
-        Log.w(TAG, "onLocationUpdate " + location.getPeer() + " " + location.getLatitude() + ",  " + location.getLongitude());
-        Contact contact = getContactFromCache(location.getPeer());
-
-        switch (location.getType()) {
-            case position:
-                ContactLocation cl = new ContactLocation();
-                cl.timestamp = location.getDate();
-                cl.latitude = location.getLatitude();
-                cl.longitude = location.getLongitude();
-                cl.receivedDate = new Date();
-
-                Observable<ContactLocation> ls = contactLocations.get(contact);
-                if (ls == null) {
-                    ls = BehaviorSubject.createDefault(cl);
-                    contactLocations.put(contact, ls);
-                    mLocationSubject.onNext(contactLocations);
-                    ContactLocationEntry entry = new ContactLocationEntry();
-                    entry.contact = contact;
-                    entry.location = ls;
-                    mLocationStartedSubject.onNext(entry);
-                } else {
-                    if (ls.blockingFirst().timestamp < cl.timestamp)
-                        ((Subject<ContactLocation>) ls).onNext(cl);
-                }
-                break;
-
-            case stop:
-                forceExpireContact(contact);
-                break;
-        }
-
-        return LOCATION_SHARING_EXPIRATION_MS;
-    }
-
-    synchronized private void forceExpireContact(Contact contact) {
-        Log.w(TAG, "forceExpireContact " + contactLocations.size());
-        Observable<ContactLocation> cl = contactLocations.remove(contact);
-        if (cl != null) {
-            Log.w(TAG, "Contact stopped sharing location: " + contact.getDisplayName());
-            ((Subject<ContactLocation>) cl).onComplete();
-            mLocationSubject.onNext(contactLocations);
-        }
-    }
-
-    synchronized public void maintainLocation() {
-        Log.w(TAG, "maintainLocation " + contactLocations.size());
-        if (contactLocations.isEmpty())
-            return;
-        boolean changed = false;
-
-        final Date expiration = new Date(System.currentTimeMillis() - LOCATION_SHARING_EXPIRATION_MS);
-        Iterator<Map.Entry<Contact, Observable<ContactLocation>>> it = contactLocations.entrySet().iterator();
-        while (it.hasNext())  {
-            Map.Entry<Contact, Observable<ContactLocation>> e = it.next();
-            if (e.getValue().blockingFirst().receivedDate.before(expiration)) {
-                Log.w(TAG, "maintainLocation clearing " + e.getKey().getDisplayName());
-                ((Subject<ContactLocation>) e.getValue()).onComplete();
-                changed = true;
-                it.remove();
-            }
-        }
-
-        if (changed)
-            mLocationSubject.onNext(contactLocations);
-    }
-
-    public Observable<ContactLocationEntry> getLocationUpdates() {
-        return mLocationStartedSubject;
-    }
-
-    public Observable<Map<Contact, Observable<ContactLocation>>> getLocationsUpdates() {
-        return mLocationSubject;
-    }
-
-    public Observable<Observable<ContactLocation>> getLocationUpdates(Uri contactId) {
-        Contact contact = getContactFromCache(contactId);
-        Log.w(TAG, "getLocationUpdates " + contactId + " " + contact);
-        if (contact == null || contact.isUser())
-            return Observable.empty();
-        return mLocationSubject
-                .flatMapMaybe(locations -> {
-                    Observable<ContactLocation> r = locations.get(contact);
-                    Log.w(TAG, "getLocationUpdates flatMapMaybe " + locations.size() + " " + r);
-                    return r == null ? Maybe.empty() : Maybe.just(r);
-                })
-                .distinctUntilChanged();
-    }
-
-    public Single<String> getAccountAlias() {
-        if (isJami()) {
-            if (mLoadedProfile == null)
-                return Single.just(getJamiAlias());
-            return mLoadedProfile.map(p -> StringUtils.isEmpty(p.first) ? getJamiAlias() : p.first);
-        } else {
-            if (mLoadedProfile == null)
-                return Single.just(getAlias());
-            return mLoadedProfile.map(p -> StringUtils.isEmpty(p.first) ? getAlias() : p.first);
-        }
-    }
-
-    /**
-     * Registered name, fallback to Alias
-     */
-    private String getJamiAlias() {
-        String registeredName = getRegisteredName();
-        if (StringUtils.isEmpty(registeredName))
-            return getAlias();
-        else
-            return registeredName;
-    }
-
-    public void resetProfile() {
-        mLoadedProfile = null;
-    }
-
-    public Single<Tuple<String, Object>> getLoadedProfile() {
-        return mLoadedProfile;
-    }
-
-    public void setLoadedProfile(Single<Tuple<String, Object>> profile) {
-        mLoadedProfile = profile;
-    }
-
-    public DataTransfer getDataTransfer(String id) {
-        return mDataTransfers.get(id);
-    }
-
-    public void putDataTransfer(String fileId, DataTransfer transfer) {
-        mDataTransfers.put(fileId, transfer);
-    }
-
-    private static class ConversationComparator implements Comparator<Conversation> {
-        @Override
-        public int compare(Conversation a, Conversation b) {
-            return Interaction.compare(b.getLastEvent(), a.getLastEvent());
-        }
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Account.kt b/ring-android/libringclient/src/main/java/net/jami/model/Account.kt
new file mode 100644
index 000000000..5b32d3296
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Account.kt
@@ -0,0 +1,995 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  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 net.jami.model
+
+import io.reactivex.rxjava3.core.*
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.model.Interaction.InteractionStatus
+import net.jami.services.AccountService
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.Log
+import net.jami.utils.StringUtils
+import net.jami.utils.Tuple
+import java.lang.IllegalStateException
+import java.util.*
+
+class Account(
+    bAccountID: String,
+    details: Map<String, String>,
+    credentials: List<Map<String, String>>,
+    volDetails: Map<String, String>
+) {
+    val accountID: String = bAccountID
+    private var mVolatileDetails: AccountConfig
+    var config: AccountConfig
+        private set
+    var username: String? = null
+        private set
+    val credentials = ArrayList<AccountCredentials>()
+    var devices: Map<String, String> = HashMap()
+    private val mContacts: MutableMap<String, Contact> = HashMap()
+    private val mRequests: MutableMap<String, TrustRequest> = HashMap()
+    private val mContactCache: MutableMap<String, Contact> = HashMap()
+    private val swarmConversations: MutableMap<String, Conversation> = HashMap()
+    private val mDataTransfers = HashMap<String, DataTransfer>()
+    private val conversations: MutableMap<String, Conversation> = HashMap()
+    private val pending: MutableMap<String, Conversation> = HashMap()
+    private val cache: MutableMap<String, Conversation> = HashMap()
+    private val sortedConversations: MutableList<Conversation> = ArrayList()
+    private val sortedPending: MutableList<Conversation> = ArrayList()
+    var registeringUsername = false
+    private var conversationsChanged = true
+    private var pendingsChanged = true
+    private var historyLoaded = false
+    private val conversationSubject: Subject<Conversation> = PublishSubject.create()
+    private val conversationsSubject: Subject<List<Conversation>> = BehaviorSubject.create()
+    private val pendingSubject: Subject<List<Conversation>> = BehaviorSubject.create()
+    private val unreadConversationsSubject: Subject<Int> = BehaviorSubject.create()
+    private val unreadPendingSubject: Subject<Int> = BehaviorSubject.create()
+    val unreadConversations: Observable<Int> = unreadConversationsSubject.distinctUntilChanged()
+    val unreadPending: Observable<Int> = unreadPendingSubject.distinctUntilChanged()
+    private val contactListSubject = BehaviorSubject.create<Collection<Contact>>()
+    private val contactLocations: MutableMap<Contact, Observable<ContactLocation>> = HashMap()
+    private val mLocationSubject: Subject<Map<Contact, Observable<ContactLocation>>> = BehaviorSubject.createDefault(contactLocations)
+    private val mLocationStartedSubject: Subject<ContactLocationEntry> = PublishSubject.create()
+
+    var historyLoader: Single<Account>? = null
+    var loadedProfile: Single<Tuple<String?, Any?>>? = null
+        set(profile) {
+            field = profile
+            mProfileSubject.onNext(profile)
+        }
+
+    private val mProfileSubject: Subject<Single<Tuple<String?, Any?>>> = BehaviorSubject.create()
+    val loadedProfileObservable: Observable<Tuple<String?, Any?>> = mProfileSubject.switchMapSingle { single -> single }
+
+    fun cleanup() {
+        conversationSubject.onComplete()
+        conversationsSubject.onComplete()
+        pendingSubject.onComplete()
+        contactListSubject.onComplete()
+        //trustRequestsSubject.onComplete();
+    }
+
+    fun canSearch(): Boolean {
+        return !StringUtils.isEmpty(getDetail(ConfigKey.MANAGER_URI))
+    }
+
+    fun isContact(conversation: Conversation): Boolean {
+        val contact = conversation.contact
+        return contact != null && getContact(contact.uri.rawRingId) != null
+    }
+
+    fun conversationStarted(conversation: Conversation) {
+        Log.w(TAG, "conversationStarted " + conversation.accountId + " " + conversation.uri + " " + conversation.isSwarm + " " + conversation.contacts.size + " " + conversation.mode.blockingFirst())
+        synchronized(conversations) {
+            if (conversation.isSwarm && conversation.mode.blockingFirst() === Conversation.Mode.OneToOne) {
+                val contact = conversation.contact
+                val key = contact!!.uri.uri
+                val removed = cache.remove(key)
+                conversations.remove(key)
+                //Conversation contactConversation = getByUri(contact.getPrimaryUri());
+                Log.w(
+                    TAG,
+                    "conversationStarted " + conversation.accountId + " contact " + key + " " + removed
+                )
+                /*if (contactConversation != null) {
+                    conversations.remove(contactConversation.getUri().getUri());
+                }*/contact.setConversationUri(conversation.uri)
+            }
+            conversations[conversation.uri.uri] = conversation
+            conversationChanged()
+        }
+    }
+
+    fun getSwarm(conversationId: String): Conversation? {
+        synchronized(conversations) { return swarmConversations[conversationId] }
+    }
+
+    fun newSwarm(conversationId: String, mode: Conversation.Mode?): Conversation {
+        synchronized(conversations) {
+            var c = swarmConversations[conversationId]
+            if (c == null) {
+                c = Conversation(accountID, Uri(Uri.SWARM_SCHEME, conversationId), mode!!)
+                swarmConversations[conversationId] = c
+            }
+            c.setMode(mode!!)
+            return c
+        }
+    }
+
+    fun removeSwarm(conversationId: String) {
+        Log.d(TAG, "removeSwarm $conversationId")
+        synchronized(conversations) {
+            val conversation = swarmConversations.remove(conversationId)
+            if (conversation != null) {
+                val c = conversations.remove(conversation.uri.uri)
+                try {
+                    val contact = c!!.contact
+                    Log.w(
+                        TAG,
+                        "removeSwarm: adding back contact conversation " + contact + " " + contact!!.conversationUri.blockingFirst() + " " + c.uri
+                    )
+                    if (contact.conversationUri.blockingFirst().equals(c.uri)) {
+                        contact.setConversationUri(contact.uri)
+                        contactAdded(contact)
+                    }
+                } catch (ignored: Exception) {
+                }
+                conversationChanged()
+            }
+        }
+    }
+
+    class ContactLocation (
+        val latitude: Double,
+        val longitude: Double,
+        val timestamp: Long,
+        val receivedDate: Date
+    )
+
+    class ContactLocationEntry (
+        val contact: Contact,
+        val location: Observable<ContactLocation>
+    )
+
+    enum class ComposingStatus {
+        Idle, Active;
+
+        companion object {
+            fun fromInt(status: Int): ComposingStatus {
+                return if (status == 1) Active else Idle
+            }
+        }
+    }
+
+    fun getConversationsSubject(): Observable<List<Conversation>> {
+        return conversationsSubject
+    }
+
+    fun getConversationsViewModels(withPresence: Boolean): Observable<MutableList<SmartListViewModel>> {
+        return conversationsSubject
+            .map { conversations: List<Conversation> ->
+                val viewModel = ArrayList<SmartListViewModel>(conversations.size)
+                for (c in conversations) viewModel.add(SmartListViewModel(c, withPresence))
+                viewModel
+            }
+    }
+
+    fun getConversationSubject(): Observable<Conversation> {
+        return conversationSubject
+    }
+
+    fun getPendingSubject(): Observable<List<Conversation>> {
+        return pendingSubject
+    }
+
+    fun getConversations(): Collection<Conversation> {
+        return conversations.values
+    }
+
+    fun getPending(): Collection<Conversation> {
+        return pending.values
+    }
+
+    private fun pendingRefreshed() {
+        if (historyLoaded) {
+            pendingSubject.onNext(getSortedPending())
+            updateUnreadPending()
+        }
+    }
+
+    private fun pendingChanged() {
+        pendingsChanged = true
+        pendingRefreshed()
+    }
+
+    private fun pendingUpdated(conversation: Conversation?) {
+        if (!historyLoaded) return
+        if (pendingsChanged) {
+            getSortedPending()
+        } else {
+            conversation?.sortHistory()
+            sortedPending.sortWith { a, b -> Interaction.compare(b.lastEvent, a.lastEvent) }
+        }
+        pendingSubject.onNext(getSortedPending())
+    }
+
+    private fun conversationRefreshed(conversation: Conversation) {
+        if (historyLoaded) {
+            conversationSubject.onNext(conversation)
+            updateUnreadConversations()
+        }
+    }
+
+    fun conversationChanged() {
+        synchronized(conversations) {
+            conversationsChanged = true
+            if (historyLoaded) {
+                conversationsSubject.onNext(ArrayList(getSortedConversations()))
+                updateUnreadConversations()
+            }
+        }
+    }
+
+    fun conversationUpdated(conversation: Conversation?) {
+        synchronized(conversations) {
+            if (!historyLoaded) return
+            if (conversationsChanged) {
+                getSortedConversations()
+            } else {
+                conversation?.sortHistory()
+                sortedConversations.sortWith { a: Conversation, b: Conversation ->
+                    Interaction.compare(b.lastEvent, a.lastEvent) }
+            }
+            conversationsSubject.onNext(ArrayList(sortedConversations))
+            updateUnreadConversations()
+        }
+    }
+
+    private fun updateUnreadConversations() {
+        var unread = 0
+        for (model in sortedConversations) {
+            val last = model.lastEvent
+            if (last != null && !last.isRead) unread++
+        }
+        // Log.w(TAG, "updateUnreadConversations " + unread);
+        unreadConversationsSubject.onNext(unread)
+    }
+
+    private fun updateUnreadPending() {
+        unreadPendingSubject.onNext(sortedPending.size)
+    }
+
+    /**
+     * Clears a conversation
+     *
+     * @param contact the contact
+     * @param delete  true if you want to remove the conversation
+     */
+    fun clearHistory(contact: Uri?, delete: Boolean) {
+        val conversation = getByUri(contact)
+        // if it is a sip account, we do not add a contact event
+        conversation!!.clearHistory(delete || isSip)
+        conversationChanged()
+    }
+
+    fun clearAllHistory() {
+        for (conversation in getConversations()) {
+            // if it is a sip account, we do not add a contact event
+            conversation.clearHistory(isSip)
+        }
+        for (conversation in pending.values) {
+            conversation.clearHistory(true)
+        }
+        conversationChanged()
+        pendingChanged()
+    }
+
+    fun updated(conversation: Conversation?) {
+        val key = conversation!!.uri.uri
+        synchronized(conversations) {
+            if (conversation == conversations[key]) {
+                conversationUpdated(conversation)
+                return
+            }
+        }
+        synchronized(pending) {
+            if (conversation == pending[key]) {
+                pendingUpdated(conversation)
+                return
+            }
+        }
+        if (conversation == cache[key]) {
+            if (isJami && !conversation.isSwarm
+                && conversation.contacts.size == 1
+                && !conversation.contact!!.conversationUri.blockingFirst().equals(conversation.uri)) {
+                return
+            }
+            if (mContacts.containsKey(key) || !isJami) {
+                Log.w(TAG, "updated " + conversation.accountId + " contact " + key)
+                conversations[key] = conversation
+                conversationChanged()
+            } else {
+                pending[key] = conversation
+                pendingChanged()
+            }
+        }
+    }
+
+    fun refreshed(conversation: Conversation) {
+        synchronized(conversations) {
+            if (conversations.containsValue(conversation)) {
+                conversationRefreshed(conversation)
+                return
+            }
+        }
+        synchronized(pending) {
+            if (pending.containsValue(conversation))
+                pendingRefreshed()
+        }
+    }
+
+    fun addTextMessage(txt: TextMessage) {
+        var conversation: Conversation? = null
+        val daemonId = txt.daemonIdString
+        if (daemonId != null && !StringUtils.isEmpty(daemonId)) {
+            conversation = getConversationByCallId(daemonId)
+        }
+        if (conversation == null) {
+            conversation = getByKey(txt.conversation!!.participant)
+            txt.contact = conversation.contact
+        }
+        conversation.addTextMessage(txt)
+        updated(conversation)
+    }
+
+    fun onDataTransferEvent(transfer: DataTransfer): Conversation {
+        Log.d(TAG, "Account onDataTransferEvent " + transfer.messageId)
+        val conversation = transfer.conversation as Conversation
+        val transferEventCode = transfer.status
+        if (transferEventCode == InteractionStatus.TRANSFER_CREATED) {
+            conversation.addFileTransfer(transfer)
+        } else {
+            conversation.updateFileTransfer(transfer, transferEventCode)
+        }
+        updated(conversation)
+        return conversation
+    }
+
+    val bannedContactsUpdates: Observable<Collection<Contact>>
+        get() = contactListSubject.concatMapSingle { list: Collection<Contact> ->
+            Observable.fromIterable(list).filter(Contact::isBanned).toList(list.size)
+        }
+
+    fun getContactFromCache(key: String): Contact {
+        if (key.isEmpty()) throw IllegalStateException()
+        synchronized(mContactCache) {
+            var contact = mContactCache[key]
+            if (contact == null) {
+                contact = if (isSip) Contact.buildSIP(Uri.fromString(key))
+                else Contact.build(key, isMe(key))
+                mContactCache[key] = contact
+            }
+            return contact
+        }
+    }
+
+    fun isMe(uri: String): Boolean {
+        //Log.w(TAG, "isMe " + uri + " " + getUsername());
+        return username == uri
+    }
+
+    fun getContactFromCache(uri: Uri): Contact {
+        return getContactFromCache(uri.uri)
+    }
+
+    fun dispose() {
+        contactListSubject.onComplete()
+        //trustRequestsSubject.onComplete();
+    }
+
+    fun setCredentials(creds: List<Map<String, String>>) {
+        credentials.clear()
+        credentials.ensureCapacity(creds.size)
+        creds.forEach { c -> credentials.add(AccountCredentials(c)) }
+    }
+
+    fun setDetails(details: Map<String, String>) {
+        config = AccountConfig(details)
+        username = config[ConfigKey.ACCOUNT_USERNAME]
+    }
+
+    fun setDetail(key: ConfigKey, value: String) {
+        config.put(key, value)
+    }
+
+    fun setDetail(key: ConfigKey, value: Boolean) {
+        config.put(key, value)
+    }
+
+    val displayname: String
+        get() = config[ConfigKey.ACCOUNT_DISPLAYNAME]
+    val displayUsername: String?
+        get() {
+            if (isJami) {
+                val registeredName: String? = registeredName
+                if (registeredName != null && registeredName.isNotEmpty()) {
+                    return registeredName
+                }
+            }
+            return username
+        }
+    var host: String?
+        get() = config[ConfigKey.ACCOUNT_HOSTNAME]
+        set(host) {
+            config.put(ConfigKey.ACCOUNT_HOSTNAME, host!!)
+        }
+    var proxy: String?
+        get() = config[ConfigKey.ACCOUNT_ROUTESET]
+        set(proxy) {
+            config.put(ConfigKey.ACCOUNT_ROUTESET, proxy!!)
+        }
+    var isDhtProxyEnabled: Boolean
+        get() = config.getBool(ConfigKey.PROXY_ENABLED)
+        set(active) {
+            config.put(ConfigKey.PROXY_ENABLED, if (active) "true" else "false")
+        }
+    val registrationState: String
+        get() = mVolatileDetails[ConfigKey.ACCOUNT_REGISTRATION_STATUS]
+
+    fun setRegistrationState(registeredState: String, code: Int) {
+        mVolatileDetails.put(ConfigKey.ACCOUNT_REGISTRATION_STATUS, registeredState)
+        mVolatileDetails.put(ConfigKey.ACCOUNT_REGISTRATION_STATE_CODE, code.toString())
+    }
+
+    fun setVolatileDetails(volatileDetails: Map<String, String>) {
+        mVolatileDetails = AccountConfig(volatileDetails)
+    }
+
+    val registeredName: String
+        get() = mVolatileDetails[ConfigKey.ACCOUNT_REGISTERED_NAME]
+    var alias: String?
+        get() = config[ConfigKey.ACCOUNT_ALIAS]
+        set(alias) {
+            config.put(ConfigKey.ACCOUNT_ALIAS, alias!!)
+        }
+    val isSip: Boolean
+        get() = config[ConfigKey.ACCOUNT_TYPE] == AccountConfig.ACCOUNT_TYPE_SIP
+    val isJami: Boolean
+        get() = config[ConfigKey.ACCOUNT_TYPE] == AccountConfig.ACCOUNT_TYPE_RING
+
+    private fun getDetail(key: ConfigKey): String {
+        return config[key]
+    }
+
+    fun getDetailBoolean(key: ConfigKey): Boolean {
+        return config.getBool(key)
+    }
+
+    var isEnabled: Boolean
+        get() = config.getBool(ConfigKey.ACCOUNT_ENABLE)
+        set(isChecked) {
+            config.put(ConfigKey.ACCOUNT_ENABLE, isChecked)
+        }
+    val isActive: Boolean
+        get() = mVolatileDetails.getBool(ConfigKey.ACCOUNT_ACTIVE)
+
+    fun hasPassword(): Boolean {
+        return config.getBool(ConfigKey.ARCHIVE_HAS_PASSWORD)
+    }
+
+    fun hasManager(): Boolean {
+        return config[ConfigKey.MANAGER_URI].isNotEmpty()
+    }
+
+    val details: HashMap<String, String>
+        get() = config.all
+    val isTrying: Boolean
+        get() = registrationState.contentEquals(AccountConfig.STATE_TRYING)
+    val isRegistered: Boolean
+        get() = registrationState.contentEquals(AccountConfig.STATE_READY) || registrationState.contentEquals(
+            AccountConfig.STATE_REGISTERED
+        )
+    val isInError: Boolean
+        get() {
+            val state = registrationState
+            return (state.contentEquals(AccountConfig.STATE_ERROR)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_AUTH)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_CONF_STUN)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_EXIST_STUN)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_GENERIC)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_HOST)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_NETWORK)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_NOT_ACCEPTABLE)
+                    || state.contentEquals(AccountConfig.STATE_ERROR_SERVICE_UNAVAILABLE)
+                    || state.contentEquals(AccountConfig.STATE_REQUEST_TIMEOUT))
+        }
+    val isIP2IP: Boolean
+        get() {
+            val emptyHost = host == null || host != null && host!!.isEmpty()
+            return isSip && emptyHost
+        }
+    val isAutoanswerEnabled: Boolean
+        get() = config.getBool(ConfigKey.ACCOUNT_AUTOANSWER)
+
+    fun addCredential(newValue: AccountCredentials) {
+        credentials.add(newValue)
+    }
+
+    fun removeCredential(accountCredentials: AccountCredentials) {
+        credentials.remove(accountCredentials)
+    }
+
+    val credentialsHashMapList: List<Map<String, String>>
+        get() {
+            val result = ArrayList<Map<String, String>>(
+                credentials.size
+            )
+            for (cred in credentials) {
+                result.add(cred.details)
+            }
+            return result
+        }
+
+    private fun getUri(display: Boolean): String? {
+        val username = if (display) displayUsername else username
+        return if (isJami) {
+            username
+        } else {
+            "$username@$host"
+        }
+    }
+
+    val uri: String?
+        get() = getUri(false)
+    val displayUri: String?
+        get() = getUri(true)
+
+    fun getDisplayUri(defaultNameSip: CharSequence): String {
+        return if (isIP2IP) defaultNameSip.toString() else displayUri!!
+    }
+
+    fun needsMigration(): Boolean {
+        return AccountConfig.STATE_NEED_MIGRATION == registrationState
+    }
+
+    val deviceId: String
+        get() = getDetail(ConfigKey.ACCOUNT_DEVICE_ID)
+    val deviceName: String
+        get() = getDetail(ConfigKey.ACCOUNT_DEVICE_NAME)
+    val contacts: Map<String, Contact>
+        get() = mContacts
+    val bannedContacts: List<Contact>
+        get() {
+            val banned = ArrayList<Contact>()
+            for (contact in mContacts.values) {
+                if (contact.isBanned) {
+                    banned.add(contact)
+                }
+            }
+            return banned
+        }
+
+    fun getContact(ringId: String?): Contact? {
+        return mContacts[ringId]
+    }
+
+    fun addContact(id: String, confirmed: Boolean) {
+        var contact = mContacts[id]
+        if (contact == null) {
+            contact = getContactFromCache(Uri.fromId(id))
+            mContacts[id] = contact
+        }
+        contact.addedDate = Date()
+        if (confirmed) {
+            contact.status = Contact.Status.CONFIRMED
+        } else {
+            contact.status = Contact.Status.REQUEST_SENT
+        }
+        val req = mRequests[id]
+        if (req != null) {
+            mRequests.remove(id)
+        }
+        contactAdded(contact)
+        contactListSubject.onNext(mContacts.values)
+    }
+
+    fun removeContact(id: String, banned: Boolean) {
+        var contact = mContacts[id]
+        if (banned) {
+            if (contact == null) {
+                contact = getContactFromCache(Uri.fromId(id))
+                mContacts[id] = contact
+            }
+            contact.status = Contact.Status.BANNED
+        } else {
+            mContacts.remove(id)
+        }
+        val req = mRequests[id]
+        if (req != null) {
+            mRequests.remove(id)
+        }
+        if (contact != null) {
+            contactRemoved(contact.uri)
+        }
+        contactListSubject.onNext(mContacts.values)
+    }
+
+    fun addContact(contact: Map<String, String>): Contact {
+        val contactId = contact[CONTACT_ID]!!
+        val callContact = mContacts[contactId] ?: getContactFromCache(Uri.fromId(contactId))
+        val addedStr = contact[CONTACT_ADDED]
+        if (!StringUtils.isEmpty(addedStr)) {
+            val added = contact[CONTACT_ADDED]!!.toLong()
+            callContact.addedDate = Date(added * 1000)
+        }
+        if (contact.containsKey(CONTACT_BANNED) && contact[CONTACT_BANNED] == "true") {
+            callContact.status = Contact.Status.BANNED
+        } else if (contact.containsKey(CONTACT_CONFIRMED)) {
+            callContact.status =
+                if (java.lang.Boolean.parseBoolean(contact[CONTACT_CONFIRMED])) Contact.Status.CONFIRMED else Contact.Status.REQUEST_SENT
+        }
+        val conversationUri = contact[CONTACT_CONVERSATION]
+        if (!StringUtils.isEmpty(conversationUri)) {
+            callContact.setConversationUri(Uri(Uri.SWARM_SCHEME, conversationUri!!))
+        }
+        mContacts[contactId] = callContact
+        contactAdded(callContact)
+        return callContact
+    }
+
+    fun setContacts(contacts: List<Map<String, String>>) {
+        for (contact in contacts) {
+            addContact(contact)
+        }
+        contactListSubject.onNext(mContacts.values)
+    }
+
+    var requests: List<TrustRequest>
+        get() {
+            val requests = ArrayList<TrustRequest>(mRequests.size)
+            for (request in mRequests.values) {
+                if (request.isNameResolved) {
+                    requests.add(request)
+                }
+            }
+            return requests
+        }
+        set(requests) {
+            Log.w(TAG, "setRequests " + requests.size)
+            synchronized(pending) {
+                for (request in requests) {
+                    val key = request.uri.uri
+                    mRequests[key] = request
+                    var conversation = pending[key]
+                    if (conversation == null) {
+                        conversation = getByKey(key)
+                        pending[key] = conversation
+                        val contact = getContactFromCache(request.uri)
+                        conversation.addRequestEvent(request, contact)
+                    }
+                }
+                pendingChanged()
+            }
+        }
+
+    fun getRequest(uri: Uri): TrustRequest? {
+        return mRequests[uri.uri]
+    }
+
+    fun addRequest(request: TrustRequest) {
+        synchronized(pending) {
+            val key = request.uri.uri
+            mRequests[key] = request
+            var conversation = pending[key]
+            if (conversation == null) {
+                conversation = getByKey(key)
+                pending[key] = conversation
+                if (!conversation.isSwarm) {
+                    val contact = getContactFromCache(request.uri)
+                    conversation.addRequestEvent(request, contact)
+                }
+                pendingChanged()
+            }
+        }
+    }
+
+    fun removeRequest(conversationUri: Uri): TrustRequest? {
+        synchronized(pending) {
+            val uri = conversationUri.uri
+            val request = mRequests.remove(uri)
+            if (pending.remove(uri) != null) {
+                pendingChanged()
+            }
+            return request
+        }
+    }
+
+    fun removeRequestPerConvId(conversationId: String) {
+        synchronized(pending) {
+            for ((_, request) in mRequests) {
+                if (request.conversationId != null && request.conversationId == conversationId) {
+                    removeRequest(request.uri)
+                    return
+                }
+            }
+        }
+    }
+
+    fun registeredNameFound(state: Int, address: String, name: String?): Boolean {
+        val uri = Uri.fromString(address)
+        val key = uri.uri
+        val contact = getContactFromCache(key)
+        if (contact.setUsername(if (state == 0) name else null)) {
+            synchronized(conversations) {
+                conversations[key]?.let { conversationRefreshed(it) }
+            }
+            synchronized(pending) { if (pending.containsKey(key)) pendingRefreshed() }
+            return true
+        }
+        return false
+    }
+
+    fun getByUri(uri: Uri?): Conversation? {
+        //Log.w(TAG, "getByUri " + getAccountID() + " " + uri);
+        if (uri == null || uri.isEmpty) return null
+        return if (uri.isSwarm) getSwarm(uri.rawRingId) else getByKey(uri.uri)
+    }
+
+    fun getByUri(uri: String?): Conversation? {
+        return if (uri != null) getByUri(Uri.fromString(uri)) else null
+    }
+
+    private fun getByKey(key: String): Conversation {
+        cache[key]?.let { return it }
+        val contact = getContactFromCache(key)
+        val conversation = Conversation(accountID, contact)
+        //Log.w(TAG, "getByKey " + getAccountID() + " contact " + key);
+        cache[key] = conversation
+        return conversation
+    }
+
+    fun setHistoryLoaded(conversations: List<Conversation>) {
+        synchronized(this.conversations) {
+            if (historyLoaded) return
+            //Log.w(TAG, "setHistoryLoaded " + getAccountID() + " " + conversations.size());
+            for (c in conversations) {
+                val contact = c.contact
+                if (!c.isSwarm && contact != null && contact.conversationUri.blockingFirst().equals(c.uri))
+                    updated(c)
+            }
+            historyLoaded = true
+            conversationChanged()
+            pendingChanged()
+        }
+    }
+
+    private fun getSortedConversations(): List<Conversation> {
+        if (conversationsChanged) {
+            sortedConversations.clear()
+            sortedConversations.addAll(conversations.values)
+            for (c in sortedConversations) c.sortHistory()
+            Collections.sort(sortedConversations, ConversationComparator())
+            conversationsChanged = false
+        }
+        return sortedConversations
+    }
+
+    private fun getSortedPending(): List<Conversation> {
+        if (pendingsChanged) {
+            sortedPending.clear()
+            sortedPending.addAll(pending.values)
+            for (c in sortedPending) c.sortHistory()
+            Collections.sort(sortedPending, ConversationComparator())
+            pendingsChanged = false
+        }
+        return sortedPending
+    }
+
+    private fun contactAdded(contact: Contact?) {
+        val uri = contact!!.uri
+        val key = uri.uri
+        Log.w(TAG, "contactAdded " + accountID + " " + uri + " " + contact.conversationUri.blockingFirst())
+        if (!contact.conversationUri.blockingFirst().equals(uri)) {
+            Log.w(TAG, "contactAdded Don't add conversation if we have a swarm conversation")
+            // Don't add conversation if we have a swarm conversation
+            return
+        }
+        synchronized(conversations) {
+            if (conversations.containsKey(key)) return
+            synchronized(pending) {
+                var pendingConversation = pending[key]
+                if (pendingConversation == null) {
+                    pendingConversation = getByKey(key)
+                    conversations[key] = pendingConversation
+                } else {
+                    pending.remove(key)
+                    conversations[key] = pendingConversation
+                    pendingChanged()
+                }
+                pendingConversation.addContactEvent(contact)
+            }
+            conversationChanged()
+        }
+    }
+
+    private fun contactRemoved(uri: Uri) {
+        val key = uri.uri
+        synchronized(conversations) {
+            synchronized(pending) { if (pending.remove(key) != null) pendingChanged() }
+            conversations.remove(key)
+            conversationChanged()
+        }
+    }
+
+    private fun getConversationByCallId(callId: String): Conversation? {
+        for (conversation in conversations.values) {
+            val conf = conversation.getConference(callId)
+            if (conf != null) {
+                return conversation
+            }
+        }
+        return null
+    }
+
+    fun presenceUpdate(contactUri: String, isOnline: Boolean) {
+        //Log.w(TAG, "presenceUpdate " + contactUri + " " + isOnline);
+        val contact = getContactFromCache(contactUri)
+        if (contact.isOnline == isOnline) return
+        contact.isOnline = isOnline
+        synchronized(conversations) {
+            val conversation = conversations[contactUri]
+            conversation?.let { conversationRefreshed(it) }
+        }
+        synchronized(pending) { if (pending.containsKey(contactUri)) pendingRefreshed() }
+    }
+
+    fun composingStatusChanged(conversationId: String, contactUri: Uri, status: ComposingStatus?) {
+        val isSwarm = !StringUtils.isEmpty(conversationId)
+        val conversation = if (isSwarm) getSwarm(conversationId) else getByUri(contactUri)
+        if (conversation != null) {
+            val contact = if (isSwarm) conversation.findContact(contactUri) else getContactFromCache(contactUri)
+            if (contact != null) {
+                conversation.composingStatusChanged(contact, status!!)
+            }
+        }
+    }
+
+    @Synchronized
+    fun onLocationUpdate(location: AccountService.Location): Long {
+        Log.w(TAG, "onLocationUpdate " + location.peer + " " + location.latitude + ",  " + location.longitude)
+        val contact = getContactFromCache(location.peer)
+        when (location.type) {
+            AccountService.Location.Type.Position -> {
+                val cl = ContactLocation(location.latitude, location.longitude, location.date, Date())
+                var ls = contactLocations[contact]
+                if (ls == null) {
+                    ls = BehaviorSubject.createDefault(cl)
+                    contactLocations[contact] = ls
+                    mLocationSubject.onNext(contactLocations)
+                    mLocationStartedSubject.onNext(ContactLocationEntry(contact, ls))
+                } else if (ls.blockingFirst().timestamp < cl.timestamp) {
+                    (ls as Subject<ContactLocation>).onNext(cl)
+                }
+            }
+            AccountService.Location.Type.Stop -> forceExpireContact(contact)
+        }
+        return LOCATION_SHARING_EXPIRATION_MS.toLong()
+    }
+
+    @Synchronized
+    private fun forceExpireContact(contact: Contact?) {
+        Log.w(TAG, "forceExpireContact " + contactLocations.size)
+        val cl = contactLocations.remove(contact)
+        if (cl != null) {
+            Log.w(TAG, "Contact stopped sharing location: " + contact!!.displayName)
+            (cl as Subject<ContactLocation>).onComplete()
+            mLocationSubject.onNext(contactLocations)
+        }
+    }
+
+    @Synchronized
+    fun maintainLocation() {
+        Log.w(TAG, "maintainLocation " + contactLocations.size)
+        if (contactLocations.isEmpty()) return
+        var changed = false
+        val expiration = Date(System.currentTimeMillis() - LOCATION_SHARING_EXPIRATION_MS)
+        val it: MutableIterator<Map.Entry<Contact, Observable<ContactLocation>>> =
+            contactLocations.entries.iterator()
+        while (it.hasNext()) {
+            val e = it.next()
+            if (e.value.blockingFirst().receivedDate!!.before(expiration)) {
+                Log.w(TAG, "maintainLocation clearing " + e.key.displayName)
+                (e.value as Subject<ContactLocation>?)!!.onComplete()
+                changed = true
+                it.remove()
+            }
+        }
+        if (changed) mLocationSubject.onNext(contactLocations)
+    }
+
+    val locationUpdates: Observable<ContactLocationEntry>
+        get() = mLocationStartedSubject
+    val locationsUpdates: Observable<Map<Contact, Observable<ContactLocation>>>
+        get() = mLocationSubject
+
+    fun getLocationUpdates(contactId: Uri): Observable<Observable<ContactLocation>> {
+        val contact = getContactFromCache(contactId)
+        return if (contact.isUser) Observable.empty() else mLocationSubject
+            .flatMapMaybe{ locations: Map<Contact, Observable<ContactLocation>> ->
+                val r = locations[contact]
+                if (r == null) Maybe.empty() else Maybe.just(r)
+            }
+            .distinctUntilChanged()
+    }
+
+    val accountAlias: Single<String>
+        get() = loadedProfileObservable.firstOrError()
+            .map { p: Tuple<String?, Any?> -> if (StringUtils.isEmpty(p.first)) if (isJami) jamiAlias else alias else p.first }
+
+    /**
+     * Registered name, fallback to Alias
+     */
+    private val jamiAlias: String
+        get() {
+            val registeredName = registeredName
+            return if (StringUtils.isEmpty(registeredName)) alias!! else registeredName
+        }
+
+    fun resetProfile() {
+        loadedProfile = null
+    }
+
+    fun getDataTransfer(id: String): DataTransfer? {
+        return mDataTransfers[id]
+    }
+
+    fun putDataTransfer(fileId: String, transfer: DataTransfer) {
+        mDataTransfers[fileId] = transfer
+    }
+
+    private class ConversationComparator : Comparator<Conversation> {
+        override fun compare(a: Conversation, b: Conversation): Int {
+            return Interaction.compare(b.lastEvent, a.lastEvent)
+        }
+    }
+
+    companion object {
+        private val TAG = Account::class.simpleName!!
+        private const val CONTACT_ADDED = "added"
+        private const val CONTACT_CONFIRMED = "confirmed"
+        private const val CONTACT_BANNED = "banned"
+        private const val CONTACT_ID = "id"
+        private const val CONTACT_CONVERSATION = "conversationId"
+        private const val LOCATION_SHARING_EXPIRATION_MS = 1000 * 60 * 2
+    }
+
+    init {
+        config = AccountConfig(details)
+        username = config[ConfigKey.ACCOUNT_USERNAME]
+        mVolatileDetails = AccountConfig(volDetails)
+        setCredentials(credentials)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.java b/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.java
deleted file mode 100644
index d41d0f416..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package net.jami.model;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class AccountConfig {
-
-    private static final String TAG = AccountConfig.class.getSimpleName();
-
-    public static final String TRUE_STR = "true";
-    public static final String FALSE_STR = "false";
-    public static final String ACCOUNT_TYPE_RING = "RING";
-    public static final String ACCOUNT_TYPE_SIP = "SIP";
-
-    public static final String STATE_REGISTERED = "REGISTERED";
-    public static final String STATE_READY = "READY";
-    public static final String STATE_UNREGISTERED = "UNREGISTERED";
-    public static final String STATE_TRYING = "TRYING";
-    public static final String STATE_ERROR = "ERROR";
-    public static final String STATE_ERROR_GENERIC = "ERROR_GENERIC";
-    public static final String STATE_ERROR_AUTH = "ERROR_AUTH";
-    public static final String STATE_ERROR_NETWORK = "ERROR_NETWORK";
-    public static final String STATE_ERROR_HOST = "ERROR_HOST";
-    public static final String STATE_ERROR_CONF_STUN = "ERROR_CONF_STUN";
-    public static final String STATE_ERROR_EXIST_STUN = "ERROR_EXIST_STUN";
-    public static final String STATE_ERROR_SERVICE_UNAVAILABLE = "ERROR_SERVICE_UNAVAILABLE";
-    public static final String STATE_ERROR_NOT_ACCEPTABLE = "ERROR_NOT_ACCEPTABLE";
-    public static final String STATE_REQUEST_TIMEOUT = "Request Timeout";
-    public static final String STATE_INITIALIZING = "INITIALIZING";
-    public static final String STATE_NEED_MIGRATION = "ERROR_NEED_MIGRATION";
-    public static final String STATE_SUCCESS = "SUCCESS";
-    public static final String STATE_INVALID = "INVALID";
-
-    private final Map<ConfigKey, String> mValues;
-
-    public AccountConfig() {
-        mValues = new HashMap<>();
-    }
-
-    public AccountConfig(Map<String, String> details) {
-        if (details != null) {
-            mValues = new HashMap<>(details.size());
-            for (Map.Entry<String, String> entry : details.entrySet()) {
-                ConfigKey confKey = ConfigKey.fromString(entry.getKey());
-                if (confKey != null) {
-                    mValues.put(confKey, entry.getValue());
-                }
-            }
-        } else {
-            mValues = new HashMap<>();
-        }
-    }
-
-    public String get(ConfigKey key) {
-        return mValues.get(key) != null ? mValues.get(key) : "";
-    }
-
-    public boolean getBool(ConfigKey key) {
-        return TRUE_STR.equals(get(key));
-    }
-
-    public HashMap<String, String> getAll() {
-        HashMap<String, String> details = new HashMap<>(mValues.size());
-        for (Map.Entry<ConfigKey, String> entry : mValues.entrySet()) {
-            details.put(entry.getKey().key(), entry.getValue());
-        }
-        return details;
-    }
-
-    void put(ConfigKey key, String value) {
-        mValues.put(key, value);
-    }
-
-    void put(ConfigKey key, boolean value) {
-        mValues.put(key, value ? TRUE_STR : FALSE_STR);
-    }
-
-    public Set<ConfigKey> getKeys() {
-        return mValues.keySet();
-    }
-
-    public Set<Map.Entry<ConfigKey, String>> getEntries() {
-        return mValues.entrySet();
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt b/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt
new file mode 100644
index 000000000..1ed759a18
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package net.jami.model
+
+import java.util.*
+
+class AccountConfig(details: Map<String, String>) {
+    private val mValues: MutableMap<ConfigKey, String> = EnumMap(ConfigKey::class.java)
+
+    operator fun get(key: ConfigKey): String {
+        return mValues[key] ?: ""
+    }
+
+    fun getBool(key: ConfigKey): Boolean {
+        return TRUE_STR == get(key)
+    }
+
+    val all: HashMap<String, String>
+        get() {
+            val details = HashMap<String, String>(mValues.size)
+            for ((key, value) in mValues) {
+                details[key.key()] = value
+            }
+            return details
+        }
+
+    fun put(key: ConfigKey, value: String) {
+        mValues[key] = value
+    }
+
+    fun put(key: ConfigKey, value: Boolean) {
+        mValues[key] = if (value) TRUE_STR else FALSE_STR
+    }
+
+    val keys: Set<ConfigKey>
+        get() = mValues.keys
+    val entries: Set<Map.Entry<ConfigKey, String>>
+        get() = mValues.entries
+
+    companion object {
+        private val TAG = AccountConfig::class.java.simpleName
+        const val TRUE_STR = "true"
+        const val FALSE_STR = "false"
+        const val ACCOUNT_TYPE_RING = "RING"
+        const val ACCOUNT_TYPE_SIP = "SIP"
+        const val STATE_REGISTERED = "REGISTERED"
+        const val STATE_READY = "READY"
+        const val STATE_UNREGISTERED = "UNREGISTERED"
+        const val STATE_TRYING = "TRYING"
+        const val STATE_ERROR = "ERROR"
+        const val STATE_ERROR_GENERIC = "ERROR_GENERIC"
+        const val STATE_ERROR_AUTH = "ERROR_AUTH"
+        const val STATE_ERROR_NETWORK = "ERROR_NETWORK"
+        const val STATE_ERROR_HOST = "ERROR_HOST"
+        const val STATE_ERROR_CONF_STUN = "ERROR_CONF_STUN"
+        const val STATE_ERROR_EXIST_STUN = "ERROR_EXIST_STUN"
+        const val STATE_ERROR_SERVICE_UNAVAILABLE = "ERROR_SERVICE_UNAVAILABLE"
+        const val STATE_ERROR_NOT_ACCEPTABLE = "ERROR_NOT_ACCEPTABLE"
+        const val STATE_REQUEST_TIMEOUT = "Request Timeout"
+        const val STATE_INITIALIZING = "INITIALIZING"
+        const val STATE_NEED_MIGRATION = "ERROR_NEED_MIGRATION"
+        const val STATE_SUCCESS = "SUCCESS"
+        const val STATE_INVALID = "INVALID"
+    }
+
+    init {
+        for ((key, value) in details) {
+            val confKey = ConfigKey.fromString(key)
+            if (confKey != null) {
+                mValues[confKey] = value
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Call.java b/ring-android/libringclient/src/main/java/net/jami/model/Call.java
deleted file mode 100644
index 0e9a14cd8..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Call.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-package net.jami.model;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-import net.jami.utils.Log;
-import net.jami.utils.ProfileChunk;
-import net.jami.utils.StringUtils;
-import net.jami.utils.VCardUtils;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-
-public class Call extends Interaction {
-    public final static String TAG = Call.class.getSimpleName();
-
-    public final static String KEY_ACCOUNT_ID = "ACCOUNTID";
-    public final static String KEY_AUDIO_ONLY = "AUDIO_ONLY";
-    public final static String KEY_CALL_TYPE = "CALL_TYPE";
-    public final static String KEY_CALL_STATE = "CALL_STATE";
-    public final static String KEY_PEER_NUMBER = "PEER_NUMBER";
-    public final static String KEY_PEER_HOLDING = "PEER_HOLDING";
-    public final static String KEY_AUDIO_MUTED = "PEER_NUMBER";
-    public final static String KEY_VIDEO_MUTED = "VIDEO_MUTED";
-    public final static String KEY_AUDIO_CODEC = "AUDIO_CODEC";
-    public final static String KEY_VIDEO_CODEC = "VIDEO_CODEC";
-    public final static String KEY_REGISTERED_NAME = "REGISTERED_NAME";
-    public final static String KEY_DURATION = "duration";
-    public final static String KEY_CONF_ID = "CONF_ID";
-
-    private final String mIdDaemon;
-
-    private boolean isPeerHolding = false;
-    private boolean isAudioMuted = false;
-    private boolean isVideoMuted = false;
-    private boolean isRecording = false;
-    private boolean isAudioOnly = false;
-
-    private CallStatus mCallStatus = CallStatus.NONE;
-
-    private long timestampEnd = 0;
-    private Long duration = null;
-    private boolean missed = true;
-    private String mAudioCodec;
-    private String mVideoCodec;
-    private String mContactNumber;
-    private String mConfId;
-
-    private ProfileChunk mProfileChunk = null;
-
-    public Call(String daemonId, String author, String account, ConversationHistory conversation, Contact contact, Direction direction) {
-        mIdDaemon = daemonId;
-        try {
-            mDaemonId = daemonId == null ? null : Long.parseLong(daemonId);
-        } catch (Exception e) {
-            Log.e(TAG, "Can't parse CallId " + mDaemonId);
-        }
-        mAuthor = direction == Direction.INCOMING ? author : null;
-        mAccount = account;
-        mConversation = conversation;
-        mIsIncoming = direction == Direction.INCOMING;
-        mTimestamp = System.currentTimeMillis();
-        mType = InteractionType.CALL.toString();
-        mContact = contact;
-        mIsRead = 1;
-    }
-
-    public Call(Interaction interaction) {
-        mId = interaction.getId();
-        mAuthor = interaction.getAuthor();
-        mConversation = interaction.getConversation();
-        mIsIncoming = mAuthor != null;
-        mTimestamp = interaction.getTimestamp();
-        mType = InteractionType.CALL.toString();
-        mStatus = interaction.getStatus().toString();
-        mDaemonId = interaction.getDaemonId();
-        mIdDaemon = super.getDaemonIdString();
-        mIsRead = interaction.isRead() ? 1 : 0;
-        mAccount = interaction.getAccount();
-        mExtraFlag = fromJson(interaction.getExtraFlag());
-        missed = getDuration() == 0;
-        mIsRead = 1;
-        mContact = interaction.getContact();
-    }
-
-    public Call(String daemonId, String account, String contactNumber, Direction direction, long timestamp) {
-        mIdDaemon = daemonId;
-        try {
-            mDaemonId = daemonId == null ? null : Long.parseLong(daemonId);
-        } catch (Exception e) {
-            Log.e(TAG, "Can't parse CallId " + mDaemonId);
-        }
-        mIsIncoming = direction == Direction.INCOMING;
-        mAccount = account;
-        mAuthor = direction == Direction.INCOMING ? contactNumber : null;
-        mContactNumber = contactNumber;
-        mTimestamp = timestamp;
-        mType = InteractionType.CALL.toString();
-        mIsRead = 1;
-    }
-
-    public Call(String daemonId, Map<String, String> call_details) {
-        this(daemonId, call_details.get(KEY_ACCOUNT_ID), call_details.get(KEY_PEER_NUMBER), Direction.fromInt(Integer.parseInt(call_details.get(KEY_CALL_TYPE))), System.currentTimeMillis());
-        setCallState(CallStatus.fromString(call_details.get(KEY_CALL_STATE)));
-        setDetails(call_details);
-    }
-
-    public void setDetails(Map<String, String> details) {
-        isPeerHolding = "true".equals(details.get(KEY_PEER_HOLDING));
-        isAudioMuted = "true".equals(details.get(KEY_AUDIO_MUTED));
-        isVideoMuted = "true".equals(details.get(KEY_VIDEO_MUTED));
-        isAudioOnly = "true".equals(details.get(KEY_AUDIO_ONLY));
-        mAudioCodec = details.get(KEY_AUDIO_CODEC);
-        mVideoCodec = details.get(KEY_VIDEO_CODEC);
-        String confId = details.get(KEY_CONF_ID);
-        mConfId = StringUtils.isEmpty(confId) ? null : confId;
-    }
-
-    @Override
-    public String getDaemonIdString() {
-        return mIdDaemon;
-    }
-
-    public boolean isConferenceParticipant() {
-        return mConfId != null;
-    }
-
-    public String getContactNumber() {
-        return mContactNumber;
-    }
-
-    public Long getDuration() {
-        if (duration == null) {
-            JsonElement element = toJson(mExtraFlag).get(KEY_DURATION);
-            if (element != null) {
-                duration = element.getAsLong();
-            }
-        }
-        return duration == null ? 0 : duration;
-    }
-
-    public void setDuration(Long value) {
-        if (Objects.equals(value, duration))
-            return;
-        duration = value;
-        if (duration != null && duration != 0) {
-            JsonObject jsonObject = getExtraFlag();
-            jsonObject.addProperty(KEY_DURATION, value);
-            mExtraFlag = fromJson(jsonObject);
-            missed = false;
-        }
-    }
-
-    public String getDurationString() {
-        long mDuration = getDuration() / 1000;
-        if (mDuration < 60) {
-            return String.format(Locale.getDefault(), "%02d secs", mDuration);
-        }
-
-        if (mDuration < 3600) {
-            return String.format(Locale.getDefault(), "%02d mins %02d secs", (mDuration % 3600) / 60, (mDuration % 60));
-        }
-
-        return String.format(Locale.getDefault(), "%d h %02d mins %02d secs", mDuration / 3600, (mDuration % 3600) / 60, (mDuration % 60));
-    }
-
-    public long getTimestampEnd() {
-        return timestampEnd;
-    }
-
-    public void setTimestampEnd(long timestampEnd) {
-        this.timestampEnd = timestampEnd;
-        if (timestampEnd != 0 && !isMissed())
-            setDuration(timestampEnd - mTimestamp);
-    }
-
-    public boolean isMissed() {
-        return missed;
-    }
-
-    public boolean isAudioOnly() {
-        return isAudioOnly;
-    }
-
-
-    public void muteVideo(boolean mute) {
-        isVideoMuted = mute;
-    }
-
-    public void muteAudio(boolean mute) {
-        isAudioMuted = mute;
-    }
-
-    public boolean isAudioMuted() {
-        return isAudioMuted;
-    }
-
-    public String getVideoCodec() {
-        return mVideoCodec;
-    }
-
-    public String getAudioCodec() {
-        return mAudioCodec;
-    }
-
-    public String getConfId() {
-        return mConfId;
-    }
-
-    public void setConfId(String confId) {
-        mConfId = confId;
-    }
-
-    public void setCallState(CallStatus callStatus) {
-        mCallStatus = callStatus;
-        if (callStatus == CallStatus.CURRENT) {
-            missed = false;
-            mStatus = InteractionStatus.SUCCESS.toString();
-        } else if (isRinging() || isOnGoing()) {
-            mStatus = InteractionStatus.SUCCESS.toString();
-        } else if (mCallStatus == CallStatus.FAILURE) {
-            mStatus = InteractionStatus.FAILURE.toString();
-        }
-    }
-
-    public CallStatus getCallStatus() {
-        return mCallStatus;
-    }
-
-    public void setTimestamp(long timestamp) {
-        mTimestamp = timestamp;
-    }
-
-    public boolean isRinging() {
-        return mCallStatus == CallStatus.CONNECTING || mCallStatus == CallStatus.RINGING || mCallStatus == CallStatus.NONE || mCallStatus == CallStatus.SEARCHING;
-    }
-
-    public boolean isOnGoing() {
-        return mCallStatus == CallStatus.CURRENT || mCallStatus == CallStatus.HOLD || mCallStatus == CallStatus.UNHOLD;
-    }
-
-    public void setIsIncoming(Direction direction) {
-        mIsIncoming = (direction == Direction.INCOMING);
-    }
-
-    public VCard appendToVCard(Map<String, String> messages) {
-        for (Map.Entry<String, String> message : messages.entrySet()) {
-            HashMap<String, String> messageKeyValue = VCardUtils.parseMimeAttributes(message.getKey());
-            String mimeType = messageKeyValue.get(VCardUtils.VCARD_KEY_MIME_TYPE);
-            if (!VCardUtils.MIME_PROFILE_VCARD.equals(mimeType)) {
-                continue;
-            }
-            int part = Integer.parseInt(messageKeyValue.get(VCardUtils.VCARD_KEY_PART));
-            int nbPart = Integer.parseInt(messageKeyValue.get(VCardUtils.VCARD_KEY_OF));
-            if (null == mProfileChunk) {
-                mProfileChunk = new ProfileChunk(nbPart);
-            }
-            mProfileChunk.addPartAtIndex(message.getValue(), part);
-            if (mProfileChunk.isProfileComplete()) {
-                VCard ret = Ezvcard.parse(mProfileChunk.getCompleteProfile()).first();
-                mProfileChunk = null;
-                return ret;
-            }
-        }
-        return null;
-    }
-
-    public enum CallStatus {
-        NONE,
-        SEARCHING,
-        CONNECTING,
-        RINGING,
-        CURRENT,
-        HUNGUP,
-        BUSY,
-        FAILURE,
-        HOLD,
-        UNHOLD,
-        INACTIVE,
-        OVER;
-
-        public static CallStatus fromString(String state) {
-            switch (state) {
-                case "SEARCHING":
-                    return SEARCHING;
-                case "CONNECTING":
-                    return CONNECTING;
-                case "INCOMING":
-                case "RINGING":
-                    return RINGING;
-                case "CURRENT":
-                    return CURRENT;
-                case "HUNGUP":
-                    return HUNGUP;
-                case "BUSY":
-                    return BUSY;
-                case "FAILURE":
-                    return FAILURE;
-                case "HOLD":
-                    return HOLD;
-                case "UNHOLD":
-                    return UNHOLD;
-                case "INACTIVE":
-                    return INACTIVE;
-                case "OVER":
-                    return OVER;
-                case "NONE":
-                default:
-                    return NONE;
-            }
-        }
-
-        public static CallStatus fromConferenceString(String state) {
-            switch (state) {
-                case "ACTIVE_ATTACHED":
-                    return CURRENT;
-                case "ACTIVE_DETACHED":
-                case "HOLD":
-                    return HOLD;
-                default:
-                    return NONE;
-            }
-        }
-
-    }
-
-    public enum  Direction {
-        INCOMING(0),
-        OUTGOING(1);
-
-        private final int value;
-        Direction(int v) {
-            value = v;
-        }
-        int getValue() {
-            return value;
-        }
-        static Direction fromInt(int value) {
-            return value == INCOMING.value ? INCOMING : OUTGOING;
-        }
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Call.kt b/ring-android/libringclient/src/main/java/net/jami/model/Call.kt
new file mode 100644
index 000000000..02eaf8823
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Call.kt
@@ -0,0 +1,291 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package net.jami.model
+
+import net.jami.utils.StringUtils.isEmpty
+import net.jami.utils.ProfileChunk
+import ezvcard.VCard
+import net.jami.utils.VCardUtils
+import ezvcard.Ezvcard
+import net.jami.utils.Log
+import java.lang.Exception
+import java.util.*
+
+class Call : Interaction {
+    override val daemonIdString: String?
+    private var isPeerHolding = false
+    var isAudioMuted = false
+        private set
+    private var isVideoMuted = false
+    private val isRecording = false
+    var isAudioOnly = false
+        private set
+    var callStatus = CallStatus.NONE
+        private set
+    var timestampEnd: Long = 0
+        set(timestampEnd) {
+            field = timestampEnd
+            if (timestampEnd != 0L && !isMissed) duration = timestampEnd - timestamp
+        }
+    var duration: Long? = null
+        get() {
+            if (field == null) {
+                val element = toJson(mExtraFlag)[KEY_DURATION]
+                if (element != null) {
+                    field = element.asLong
+                }
+            }
+            return if (field == null) 0 else field
+        }
+        set(value) {
+            if (value == duration) return
+            field = value
+            if (duration != null && duration != 0L) {
+                val jsonObject = extraFlag
+                jsonObject.addProperty(KEY_DURATION, value)
+                mExtraFlag = fromJson(jsonObject)
+                isMissed = false
+            }
+        }
+    var isMissed = true
+        private set
+    var audioCodec: String? = null
+        private set
+    var videoCodec: String? = null
+        private set
+    var contactNumber: String? = null
+        private set
+    var confId: String? = null
+    private var mProfileChunk: ProfileChunk? = null
+
+    constructor(
+        daemonId: String?,
+        author: String?,
+        account: String?,
+        conversation: ConversationHistory?,
+        contact: Contact?,
+        direction: Direction
+    ) {
+        daemonIdString = daemonId
+        try {
+            this.daemonId = daemonId?.toLong()
+        } catch (e: Exception) {
+            Log.e(TAG, "Can't parse CallId $daemonId")
+        }
+        this.author = if (direction == Direction.INCOMING) author else null
+        this.account = account
+        this.conversation = conversation
+        isIncoming = direction == Direction.INCOMING
+        timestamp = System.currentTimeMillis()
+        mType = InteractionType.CALL.toString()
+        this.contact = contact
+        mIsRead = 1
+    }
+
+    constructor(interaction: Interaction) {
+        id = interaction.id
+        author = interaction.author
+        conversation = interaction.conversation
+        isIncoming = author != null
+        timestamp = interaction.timestamp
+        mType = InteractionType.CALL.toString()
+        mStatus = interaction.status.toString()
+        daemonId = interaction.daemonId
+        daemonIdString = super.daemonIdString
+        mIsRead = if (interaction.isRead) 1 else 0
+        account = interaction.account
+        mExtraFlag = fromJson(interaction.extraFlag)
+        isMissed = duration == 0L
+        mIsRead = 1
+        contact = interaction.contact
+    }
+
+    constructor(daemonId: String?, account: String?, contactNumber: String?, direction: Direction, timestamp: Long) {
+        daemonIdString = daemonId
+        try {
+            this.daemonId = daemonId?.toLong()
+        } catch (e: Exception) {
+            Log.e(TAG, "Can't parse CallId $daemonId")
+        }
+        isIncoming = direction == Direction.INCOMING
+        this.account = account
+        author = if (direction == Direction.INCOMING) contactNumber else null
+        this.contactNumber = contactNumber
+        this.timestamp = timestamp
+        mType = InteractionType.CALL.toString()
+        mIsRead = 1
+    }
+
+    constructor(daemonId: String?, call_details: Map<String?, String>) : this(
+        daemonId, call_details[KEY_ACCOUNT_ID], call_details[KEY_PEER_NUMBER], Direction.fromInt(
+            call_details[KEY_CALL_TYPE]!!.toInt()
+        ), System.currentTimeMillis()
+    ) {
+        setCallState(CallStatus.fromString(call_details[KEY_CALL_STATE]))
+        setDetails(call_details)
+    }
+
+    fun setDetails(details: Map<String?, String>) {
+        isPeerHolding = "true" == details[KEY_PEER_HOLDING]
+        isAudioMuted = "true" == details[KEY_AUDIO_MUTED]
+        isVideoMuted = "true" == details[KEY_VIDEO_MUTED]
+        isAudioOnly = "true" == details[KEY_AUDIO_ONLY]
+        audioCodec = details[KEY_AUDIO_CODEC]
+        videoCodec = details[KEY_VIDEO_CODEC]
+        val confId = details[KEY_CONF_ID]
+        this.confId = if (isEmpty(confId)) null else confId
+    }
+
+    val isConferenceParticipant: Boolean
+        get() = confId != null
+
+    val durationString: String
+        get() {
+            val mDuration = duration!! / 1000
+            if (mDuration < 60) {
+                return String.format(Locale.getDefault(), "%02d secs", mDuration)
+            }
+            return if (mDuration < 3600)
+                String.format(Locale.getDefault(), "%02d mins %02d secs", mDuration % 3600 / 60, mDuration % 60)
+            else
+                String.format(Locale.getDefault(), "%d h %02d mins %02d secs", mDuration / 3600, mDuration % 3600 / 60, mDuration % 60)
+        }
+
+    fun muteVideo(mute: Boolean) {
+        isVideoMuted = mute
+    }
+
+    fun muteAudio(mute: Boolean) {
+        isAudioMuted = mute
+    }
+
+    fun setCallState(callStatus: CallStatus) {
+        this.callStatus = callStatus
+        if (callStatus == CallStatus.CURRENT) {
+            isMissed = false
+            mStatus = InteractionStatus.SUCCESS.toString()
+        } else if (isRinging || isOnGoing) {
+            mStatus = InteractionStatus.SUCCESS.toString()
+        } else if (this.callStatus == CallStatus.FAILURE) {
+            mStatus = InteractionStatus.FAILURE.toString()
+        }
+    }
+
+    /*override var timestamp: Long
+        get() = super.timestamp
+        set(timestamp) {
+            var timestamp = timestamp
+            timestamp = timestamp
+        }*/
+    val isRinging: Boolean
+        get() = callStatus == CallStatus.CONNECTING || callStatus == CallStatus.RINGING || callStatus == CallStatus.NONE || callStatus == CallStatus.SEARCHING
+    val isOnGoing: Boolean
+        get() = callStatus == CallStatus.CURRENT || callStatus == CallStatus.HOLD || callStatus == CallStatus.UNHOLD
+    /*override var isIncoming: Direction
+        get() = super.isIncoming
+        set(direction) {
+            field = direction == Direction.INCOMING
+        }*/
+
+    fun appendToVCard(messages: Map<String, String>): VCard? {
+        for ((key, value) in messages) {
+            val messageKeyValue = VCardUtils.parseMimeAttributes(key)
+            val mimeType = messageKeyValue[VCardUtils.VCARD_KEY_MIME_TYPE]
+            if (VCardUtils.MIME_PROFILE_VCARD != mimeType) {
+                continue
+            }
+            val part = messageKeyValue[VCardUtils.VCARD_KEY_PART]!!.toInt()
+            val nbPart = messageKeyValue[VCardUtils.VCARD_KEY_OF]!!.toInt()
+            if (null == mProfileChunk) {
+                mProfileChunk = ProfileChunk(nbPart)
+            }
+            mProfileChunk?.let { profile ->
+                profile.addPartAtIndex(value, part)
+                if (profile.isProfileComplete) {
+                    val ret = Ezvcard.parse(profile.completeProfile).first()
+                    mProfileChunk = null
+                    return@appendToVCard ret
+                }
+            }
+        }
+        return null
+    }
+
+    enum class CallStatus {
+        NONE, SEARCHING, CONNECTING, RINGING, CURRENT, HUNGUP, BUSY, FAILURE, HOLD, UNHOLD, INACTIVE, OVER;
+
+        companion object {
+            @JvmStatic
+            fun fromString(state: String?): CallStatus {
+                return when (state) {
+                    "SEARCHING" -> SEARCHING
+                    "CONNECTING" -> CONNECTING
+                    "INCOMING", "RINGING" -> RINGING
+                    "CURRENT" -> CURRENT
+                    "HUNGUP" -> HUNGUP
+                    "BUSY" -> BUSY
+                    "FAILURE" -> FAILURE
+                    "HOLD" -> HOLD
+                    "UNHOLD" -> UNHOLD
+                    "INACTIVE" -> INACTIVE
+                    "OVER" -> OVER
+                    "NONE" -> NONE
+                    else -> NONE
+                }
+            }
+
+            @JvmStatic
+            fun fromConferenceString(state: String?): CallStatus {
+                return when (state) {
+                    "ACTIVE_ATTACHED" -> CURRENT
+                    "ACTIVE_DETACHED", "HOLD" -> HOLD
+                    else -> NONE
+                }
+            }
+        }
+    }
+
+    enum class Direction(val value: Int) {
+        INCOMING(0), OUTGOING(1);
+
+        companion object {
+            fun fromInt(value: Int): Direction {
+                return if (value == INCOMING.value) INCOMING else OUTGOING
+            }
+        }
+    }
+
+    companion object {
+        val TAG = Call::class.simpleName!!
+        const val KEY_ACCOUNT_ID = "ACCOUNTID"
+        const val KEY_AUDIO_ONLY = "AUDIO_ONLY"
+        const val KEY_CALL_TYPE = "CALL_TYPE"
+        const val KEY_CALL_STATE = "CALL_STATE"
+        const val KEY_PEER_NUMBER = "PEER_NUMBER"
+        const val KEY_PEER_HOLDING = "PEER_HOLDING"
+        const val KEY_AUDIO_MUTED = "PEER_NUMBER"
+        const val KEY_VIDEO_MUTED = "VIDEO_MUTED"
+        const val KEY_AUDIO_CODEC = "AUDIO_CODEC"
+        const val KEY_VIDEO_CODEC = "VIDEO_CODEC"
+        const val KEY_REGISTERED_NAME = "REGISTERED_NAME"
+        const val KEY_DURATION = "duration"
+        const val KEY_CONF_ID = "CONF_ID"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conference.java b/ring-android/libringclient/src/main/java/net/jami/model/Conference.java
deleted file mode 100644
index efc9b29d9..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Conference.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package net.jami.model;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class Conference {
-
-    public static class ParticipantInfo {
-        public final Call call;
-        public final Contact contact;
-        public int x, y, w, h;
-        public boolean videoMuted, audioMuted, isModerator;
-
-        public ParticipantInfo(Call call, Contact c, Map<String, String> i) {
-            this.call = call;
-            contact = c;
-            x = Integer.parseInt(i.get("x"));
-            y = Integer.parseInt(i.get("y"));
-            w = Integer.parseInt(i.get("w"));
-            h = Integer.parseInt(i.get("h"));
-            videoMuted = Boolean.parseBoolean(i.get("videoMuted"));
-            audioMuted = Boolean.parseBoolean(i.get("audioMuted"));
-            isModerator = Boolean.parseBoolean(i.get("isModerator"));
-        }
-
-        public boolean isEmpty() {
-            return x == 0 && y == 0 && w == 0 && h == 0;
-        }
-    }
-    private final Subject<List<ParticipantInfo>> mParticipantInfo = BehaviorSubject.createDefault(Collections.emptyList());
-
-    private final Set<Contact> mParticipantRecordingSet = new HashSet<>();
-    private final Subject<Set<Contact>> mParticipantRecording = BehaviorSubject.createDefault(Collections.emptySet());
-
-    private final String mId;
-    private Call.CallStatus mConfState;
-    private final ArrayList<Call> mParticipants;
-    private boolean mRecording;
-    private Contact mMaximizedParticipant;
-    private boolean isModerator;
-
-    public Conference(Call call) {
-        this(call.getDaemonIdString());
-        mParticipants.add(call);
-    }
-
-    public Conference(String cID) {
-        mId = cID;
-        mParticipants = new ArrayList<>();
-        mRecording = false;
-    }
-
-    public Conference(Conference c) {
-        mId = c.mId;
-        mConfState = c.mConfState;
-        mParticipants = new ArrayList<>(c.mParticipants);
-        mRecording = c.mRecording;
-    }
-
-    public boolean isRinging() {
-        return !mParticipants.isEmpty() && mParticipants.get(0).isRinging();
-    }
-
-    public boolean isConference() {
-        return mParticipants.size() > 1;
-    }
-
-    public Call getCall() {
-        if (!isConference()) {
-            return getFirstCall();
-        }
-        return null;
-    }
-    public Call getFirstCall() {
-        if (!mParticipants.isEmpty()) {
-            return mParticipants.get(0);
-        }
-        return null;
-    }
-
-    public String getId() {
-        return mId;
-    }
-
-    public void setMaximizedParticipant(Contact contact) {
-        mMaximizedParticipant = contact;
-    }
-
-    public Contact getMaximizedParticipant() {
-        return mMaximizedParticipant;
-    }
-
-    public String getPluginId() {
-        return "local";
-    }
-
-    public String getConfId() {
-        return mId;
-    }
-
-    public void setIsModerator(boolean isModerator) {
-        this.isModerator = isModerator;
-    }
-
-    public boolean getIsModerator() {
-        return isModerator;
-    }
-
-    public Call.CallStatus getState() {
-        if (isSimpleCall()) {
-            return mParticipants.get(0).getCallStatus();
-        }
-        return mConfState;
-    }
-
-    public Call.CallStatus getConfState() {
-        if (mParticipants.size() == 1) {
-            return mParticipants.get(0).getCallStatus();
-        }
-        return mConfState;
-    }
-
-    public boolean isSimpleCall() {
-        return mParticipants.size() == 1 && mId.equals(mParticipants.get(0).getDaemonIdString());
-    }
-
-    public void setState(String state) {
-        mConfState = Call.CallStatus.fromConferenceString(state);
-    }
-
-    public List<Call> getParticipants() {
-        return mParticipants;
-    }
-
-    public void addParticipant(Call part) {
-        mParticipants.add(part);
-    }
-
-    public boolean removeParticipant(Call toRemove) {
-        return mParticipants.remove(toRemove);
-    }
-
-    public boolean contains(String callID) {
-        for (Call participant : mParticipants) {
-            if (participant.getDaemonIdString().contentEquals(callID))
-                return true;
-        }
-        return false;
-    }
-
-    public Call getCallById(String callID) {
-        for (Call participant : mParticipants) {
-            if (participant.getDaemonIdString().contentEquals(callID))
-                return participant;
-        }
-        return null;
-    }
-
-    public Call findCallByContact(Uri uri) {
-        for (Call call : mParticipants) {
-            if (call.getContact().getUri().toString().equals(uri.toString()))
-                return call;
-        }
-        return null;
-    }
-
-    public boolean isIncoming() {
-        return mParticipants.size() == 1 && mParticipants.get(0).isIncoming();
-    }
-
-    public boolean isOnGoing() {
-        return mParticipants.size() == 1 && mParticipants.get(0).isOnGoing() || mParticipants.size() > 1;
-    }
-
-    public boolean hasVideo() {
-        for (Call call : mParticipants)
-            if (!call.isAudioOnly())
-                return true;
-        return false;
-    }
-
-    public long getTimestampStart() {
-        long t = Long.MAX_VALUE;
-        for (Call call : mParticipants)
-            t = Math.min(call.getTimestamp(), t);
-        return t;
-    }
-
-    public void removeParticipants() {
-        mParticipants.clear();
-    }
-
-    public void setInfo(List<ParticipantInfo> info) {
-        mParticipantInfo.onNext(info);
-    }
-
-    public Observable<List<ParticipantInfo>> getParticipantInfo() {
-        return mParticipantInfo;
-    }
-    public Observable<Set<Contact>> getParticipantRecording() {
-        return mParticipantRecording;
-    }
-
-    public void setParticipantRecording(Contact contact, boolean state) {
-        if (state) {
-            mParticipantRecordingSet.add(contact);
-        } else {
-            mParticipantRecordingSet.remove(contact);
-        }
-        mParticipantRecording.onNext(mParticipantRecordingSet);
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conference.kt b/ring-android/libringclient/src/main/java/net/jami/model/Conference.kt
new file mode 100644
index 000000000..df220e288
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Conference.kt
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.model.Call.CallStatus
+import net.jami.model.Call.CallStatus.Companion.fromConferenceString
+import java.util.*
+import kotlin.math.min
+
+class Conference {
+    class ParticipantInfo(val call: Call?, val contact: Contact, i: Map<String, String>) {
+        var x: Int = i["x"]?.toInt() ?: 0
+        var y: Int = i["y"]?.toInt() ?: 0
+        var w: Int = i["w"]?.toInt() ?: 0
+        var h: Int = i["h"]?.toInt() ?: 0
+        var videoMuted: Boolean = java.lang.Boolean.parseBoolean(i["videoMuted"])
+        var audioMuted: Boolean = java.lang.Boolean.parseBoolean(i["audioMuted"])
+        var isModerator: Boolean = java.lang.Boolean.parseBoolean(i["isModerator"])
+        val isEmpty: Boolean
+            get() = x == 0 && y == 0 && w == 0 && h == 0
+    }
+
+    private val mParticipantInfo: Subject<List<ParticipantInfo>> = BehaviorSubject.createDefault(emptyList())
+    private val mParticipantRecordingSet: MutableSet<Contact> = HashSet()
+    private val mParticipantRecording: Subject<Set<Contact>> = BehaviorSubject.createDefault(emptySet())
+    val id: String
+    private var mConfState: CallStatus? = null
+    private val mParticipants: ArrayList<Call>
+    private var mRecording: Boolean
+    var maximizedParticipant: Contact? = null
+    var isModerator = false
+
+    constructor(call: Call) : this(call.daemonIdString!!) {
+        mParticipants.add(call)
+    }
+
+    constructor(cID: String) {
+        id = cID
+        mParticipants = ArrayList()
+        mRecording = false
+    }
+
+    constructor(c: Conference) {
+        id = c.id
+        mConfState = c.mConfState
+        mParticipants = ArrayList(c.mParticipants)
+        mRecording = c.mRecording
+    }
+
+    val isRinging: Boolean
+        get() = mParticipants.isNotEmpty() && mParticipants[0].isRinging
+    val isConference: Boolean
+        get() = mParticipants.size > 1
+    val call: Call?
+        get() = if (!isConference) {
+            firstCall
+        } else null
+    val firstCall: Call?
+        get() = if (mParticipants.isNotEmpty()) {
+            mParticipants[0]
+        } else null
+    val pluginId: String
+        get() = "local"
+    val state: CallStatus?
+        get() = if (isSimpleCall) {
+            mParticipants[0].callStatus
+        } else mConfState
+    val confState: CallStatus?
+        get() = if (mParticipants.size == 1) {
+            mParticipants[0].callStatus
+        } else mConfState
+
+    val isSimpleCall: Boolean
+        get() = mParticipants.size == 1 && id == mParticipants[0].daemonIdString
+
+    fun setState(state: String?) {
+        mConfState = fromConferenceString(state)
+    }
+
+    val participants: MutableList<Call>
+        get() = mParticipants
+
+    fun addParticipant(part: Call) {
+        mParticipants.add(part)
+    }
+
+    fun removeParticipant(toRemove: Call): Boolean {
+        return mParticipants.remove(toRemove)
+    }
+
+    operator fun contains(callID: String?): Boolean {
+        for (participant in mParticipants) {
+            if (participant.daemonIdString.contentEquals(callID)) return true
+        }
+        return false
+    }
+
+    fun getCallById(callID: String?): Call? {
+        for (participant in mParticipants) {
+            if (participant.daemonIdString.contentEquals(callID)) return participant
+        }
+        return null
+    }
+
+    fun findCallByContact(uri: Uri): Call? {
+        for (call in mParticipants) {
+            if (call.contact!!.uri.toString() == uri.toString()) return call
+        }
+        return null
+    }
+
+    val isIncoming: Boolean
+        get() = mParticipants.size == 1 && mParticipants[0].isIncoming
+    val isOnGoing: Boolean
+        get() = mParticipants.size == 1 && mParticipants[0].isOnGoing || mParticipants.size > 1
+
+    fun hasVideo(): Boolean {
+        for (call in mParticipants) if (!call.isAudioOnly) return true
+        return false
+    }
+
+    val timestampStart: Long
+        get() {
+            var t = Long.MAX_VALUE
+            for (call in mParticipants) t = min(call.timestamp, t)
+            return t
+        }
+
+    fun removeParticipants() {
+        mParticipants.clear()
+    }
+
+    fun setInfo(info: List<ParticipantInfo>) {
+        mParticipantInfo.onNext(info)
+    }
+
+    val participantInfo: Observable<List<ParticipantInfo>>
+        get() = mParticipantInfo
+    val participantRecording: Observable<Set<Contact>>
+        get() = mParticipantRecording
+
+    fun setParticipantRecording(contact: Contact, state: Boolean) {
+        if (state) {
+            mParticipantRecordingSet.add(contact)
+        } else {
+            mParticipantRecordingSet.remove(contact)
+        }
+        mParticipantRecording.onNext(mParticipantRecordingSet)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.java b/ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.kt
similarity index 84%
rename from ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.java
rename to ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.kt
index d6b6bc63a..82927d0ab 100644
--- a/ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.java
+++ b/ring-android/libringclient/src/main/java/net/jami/model/ConfigKey.kt
@@ -17,9 +17,9 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package net.jami.model;
+package net.jami.model
 
-public enum ConfigKey {
+enum class ConfigKey {
     MAILBOX("Account.mailbox"),
     REGISTRATION_EXPIRE("Account.registrationExpire"),
     CREDENTIAL_NUMBER("Credential.count"),
@@ -57,7 +57,7 @@ public enum ConfigKey {
     ACCOUNT_ACTIVE("Account.active", true),
     ACCOUNT_DEVICE_ID("Account.deviceID"),
     ACCOUNT_DEVICE_NAME("Account.deviceName"),
-    ACCOUNT_PEER_DISCOVERY("Account.peerDiscovery",true),
+    ACCOUNT_PEER_DISCOVERY("Account.peerDiscovery", true),
     ACCOUNT_DISCOVERY("Account.accountDiscovery", true),
     ACCOUNT_PUBLISH("Account.accountPublish", true),
     ACCOUNT_DISPLAYNAME("Account.displayName"),
@@ -103,36 +103,36 @@ public enum ConfigKey {
     MANAGER_URI("Account.managerUri"),
     MANAGER_USERNAME("Account.managerUsername");
 
-    private final String mKey;
-    private final boolean mIsBool;
+    private val mKey: String
+    val isTwoState: Boolean
 
-    ConfigKey(String key) {
-        mKey = key;
-        mIsBool = false;
-    }
-    ConfigKey(String key, boolean isBool) {
-        mKey = key;
-        mIsBool = isBool;
+    constructor(key: String) {
+        mKey = key
+        isTwoState = false
     }
 
-    public String key() {
-        return mKey;
+    constructor(key: String, isBool: Boolean) {
+        mKey = key
+        isTwoState = isBool
     }
 
-    public boolean equals(ConfigKey other) {
-        return other != null && mKey.equals(other.mKey);
+    fun key(): String {
+        return mKey
     }
 
-    public boolean isTwoState() {
-        return mIsBool;
+    fun equals(other: ConfigKey?): Boolean {
+        return other != null && mKey == other.mKey
     }
 
-    public static ConfigKey fromString(String stringKey) {
-        for (ConfigKey confKey : ConfigKey.values()) {
-            if (stringKey.contentEquals(confKey.mKey) || stringKey.equals(confKey.mKey)) {
-                return confKey;
+    companion object {
+        @JvmStatic
+        fun fromString(stringKey: String): ConfigKey? {
+            for (confKey in values()) {
+                if (stringKey.contentEquals(confKey.mKey) || stringKey == confKey.mKey) {
+                    return confKey
+                }
             }
+            return null
         }
-        return null;
     }
-}
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Contact.java b/ring-android/libringclient/src/main/java/net/jami/model/Contact.java
deleted file mode 100644
index 4740a0438..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Contact.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-import net.jami.utils.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Date;
-
-import io.reactivex.rxjava3.core.Emitter;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class Contact {
-    protected static final String TAG = Contact.class.getSimpleName();
-
-    public static final int UNKNOWN_ID = -1;
-    public static final int DEFAULT_ID = 0;
-    public static final String PREFIX_RING = Uri.RING_URI_SCHEME;
-
-    public enum Status {BANNED, REQUEST_SENT, CONFIRMED, NO_REQUEST}
-
-    private final Uri mUri;
-
-    private String mUsername = null;
-    private long mPhotoId;
-    private final ArrayList<Phone> mPhones = new ArrayList<>();
-    private final boolean isUser;
-    private boolean stared = false;
-    private boolean isFromSystem = false;
-    private Status mStatus = Status.NO_REQUEST;
-    private Date mAddedDate = null;
-    private boolean mOnline = false;
-
-    private long mId;
-    private String mLookupKey;
-
-    private boolean usernameLoaded = false;
-    public boolean detailsLoaded = false;
-    //private Uri mConversationUri = null;
-    private final BehaviorSubject<Uri> mConversationUri;
-
-    // Profile
-    private String mDisplayName;
-    private Object mContactPhoto = null;
-
-    private final Subject<Contact> mContactUpdates = BehaviorSubject.create();
-    private Observable<Contact> mContactObservable;
-
-    private Observable<Boolean> mContactPresenceObservable;
-    private Emitter<Boolean> mContactPresenceEmitter;
-
-    public Contact(Uri uri) {
-        this(uri, false);
-    }
-
-    public Contact(Uri uri, boolean user) {
-        this(uri, null, user);
-    }
-
-    private Contact(Uri uri, String displayName, boolean user) {
-        mUri = uri;
-        mDisplayName = displayName;
-        isUser = user;
-        mConversationUri = BehaviorSubject.createDefault(mUri);
-        /*if (cID != UNKNOWN_ID && (displayName == null || !displayName.contains(PREFIX_RING))) {
-            mStatus = Status.CONFIRMED;
-        }*/
-    }
-
-    public void setConversationUri(Uri conversationUri) {
-        mConversationUri.onNext(conversationUri);
-    }
-
-    public Observable<Uri> getConversationUri() {
-        return mConversationUri;
-    }
-
-    public static Contact buildSIP(Uri to) {
-        Contact contact = new Contact(to);
-        contact.usernameLoaded = true;
-        return contact;
-    }
-
-    public static Contact build(String uri, boolean isUser) {
-        return new Contact(Uri.fromString(uri), isUser);
-    }
-    public static Contact build(String uri) {
-        return build(uri, false);
-    }
-
-    public Observable<Contact> getUpdatesSubject() {
-        return mContactUpdates;
-    }
-    public Observable<Contact> getUpdates() {
-        return mContactObservable;
-    }
-    public void setUpdates(Observable<Contact> observable) {
-        mContactObservable = observable;
-    }
-
-    public Observable<Boolean> getPresenceUpdates() {
-        return mContactPresenceObservable;
-    }
-    public void setPresenceUpdates(Observable<Boolean> observable) {
-        mContactPresenceObservable = observable;
-    }
-    public void setPresenceEmitter(Emitter<Boolean> emitter) {
-        if (mContactPresenceEmitter != null && mContactPresenceEmitter != emitter) {
-            mContactPresenceEmitter.onComplete();
-        }
-        mContactPresenceEmitter = emitter;
-    }
-
-    public boolean matches(String query) {
-        return (mDisplayName != null && mDisplayName.toLowerCase().contains(query))
-                || (mUsername != null && mUsername.contains(query))
-                || (getPrimaryNumber().contains(query));
-    }
-
-    public boolean isOnline() {
-        return mOnline;
-    }
-
-    public void setOnline(boolean present) {
-        mOnline = present;
-        if (mContactPresenceEmitter != null)
-            mContactPresenceEmitter.onNext(present);
-    }
-
-    public void setSystemId(long id) {
-        mId = id;
-    }
-
-    public void setSystemContactInfo(long id, String k, String displayName, long photo_id) {
-        mId = id;
-        mLookupKey = k;
-        mDisplayName = displayName;
-        this.mPhotoId = photo_id;
-        if (mUsername == null && displayName.contains(PREFIX_RING)) {
-            mUsername = displayName;
-        }
-    }
-
-    public static String canonicalNumber(String number) {
-        if (number == null || number.isEmpty())
-            return null;
-        return Uri.fromString(number).getRawUriString();
-    }
-
-    public ArrayList<String> getIds() {
-        ArrayList<String> ret = new ArrayList<>(mPhones.size() + (mId == UNKNOWN_ID ? 0 : 1));
-        if (mId != UNKNOWN_ID)
-            ret.add("c:" + Long.toHexString(mId));
-        for (Phone p : mPhones)
-            ret.add(p.getNumber().getRawUriString());
-        return ret;
-    }
-
-    public static long contactIdFromId(String id) {
-        if (!id.startsWith("c:"))
-            return UNKNOWN_ID;
-        try {
-            return Long.parseLong(id.substring(2), 16);
-        } catch (Exception e) {
-            return UNKNOWN_ID;
-        }
-    }
-
-    public long getId() {
-        return mId;
-    }
-
-    public String getDisplayName() {
-        return !StringUtils.isEmpty(mDisplayName) ? mDisplayName : getRingUsername();
-    }
-
-    public String getProfileName() {
-        return mDisplayName;
-    }
-
-    public long getPhotoId() {
-        return mPhotoId;
-    }
-
-    public ArrayList<Phone> getPhones() {
-        return mPhones;
-    }
-
-    public boolean hasNumber(String number) {
-        return hasNumber(Uri.fromString(number));
-    }
-
-    public boolean hasNumber(Uri number) {
-        if (number == null || number.isEmpty())
-            return false;
-        for (Phone p : mPhones)
-            if (p.getNumber().toString().equals(number.toString()))
-                return true;
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        if (!StringUtils.isEmpty(mUsername)) {
-            return mUsername;
-        } else {
-            return getUri().getRawUriString();
-        }
-    }
-
-    public void setId(long id) {
-        this.mId = id;
-    }
-
-    /*public String getKey() {
-        return mKey;
-    }*/
-
-    public String getPrimaryNumber() {
-        return getUri().getRawRingId();
-    }
-    public Uri getUri() {
-        return mUri;
-    }
-
-    public void setStared() {
-        this.stared = true;
-    }
-
-    public boolean isStared() {
-        return stared;
-    }
-
-    public void addPhoneNumber(Uri tel, int cat, String label) {
-        if (!hasNumber(tel))
-            mPhones.add(new Phone(tel, cat, label));
-    }
-
-    public void addNumber(String tel, int cat, String label, Phone.NumberType type) {
-        if (!hasNumber(tel))
-            mPhones.add(new Phone(tel, cat, label, type));
-    }
-
-    public void addNumber(Uri tel, int cat, String label, Phone.NumberType type) {
-        if (!hasNumber(tel))
-            mPhones.add(new Phone(tel, cat, label, type));
-    }
-
-    public boolean isUser() {
-        return isUser;
-    }
-
-    public boolean hasPhoto() {
-        return mContactPhoto != null;
-    }
-
-    public Object getPhoto() {
-        return mContactPhoto;
-    }
-
-    public void setPhoto(Object externalArray) {
-        mContactPhoto = externalArray;
-    }
-
-    public boolean isFromSystem() {
-        return isFromSystem;
-    }
-
-    public Status getStatus() {
-        return mStatus;
-    }
-
-    public void setStatus(Status status) {
-        mStatus = status;
-    }
-
-    public boolean isBanned() { return  mStatus == Status.BANNED; }
-
-    public void setFromSystem(boolean fromSystem) {
-        isFromSystem = fromSystem;
-    }
-
-    public void setAddedDate(Date addedDate) {
-        mAddedDate = addedDate;
-    }
-    public Date getAddedDate() {
-        return mAddedDate;
-    }
-
-    /**
-     * A contact is Unknown when his name == his phone number
-     *
-     * @return true when Name == Number
-     */
-    public boolean isUnknown() {
-        return mDisplayName == null || mDisplayName.contentEquals(mPhones.get(0).getNumber().getRawUriString());
-    }
-
-    public void setDisplayName(String displayName) {
-        mDisplayName = displayName;
-    }
-
-    public String getRingUsername() {
-        if (!StringUtils.isEmpty(mUsername)) {
-            return mUsername;
-        } else if (usernameLoaded) {
-            return getUri().getRawUriString();
-        } else {
-            return "";
-        }
-    }
-
-    public String getUsername() {
-        return mUsername;
-    }
-
-    public boolean setUsername(String name) {
-        if (!usernameLoaded || (name != null && !name.equals(mUsername))) {
-            mUsername = name;
-            usernameLoaded = true;
-            mContactUpdates.onNext(this);
-            return true;
-        }
-        return false;
-    }
-
-    public boolean isUsernameLoaded() {
-        return usernameLoaded;
-    }
-
-    public void setProfile(String name, Object photo) {
-        if (!StringUtils.isEmpty(name) && !name.startsWith(Uri.RING_URI_SCHEME)) {
-            setDisplayName(name);
-        }
-        if (photo != null) {
-            setPhoto(photo);
-        }
-        detailsLoaded = true;
-        mContactUpdates.onNext(this);
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt b/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt
new file mode 100644
index 000000000..dcaf93995
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt
@@ -0,0 +1,245 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import io.reactivex.rxjava3.core.Emitter
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.utils.StringUtils
+import java.lang.Exception
+import java.util.*
+import kotlin.jvm.JvmOverloads
+
+class Contact private constructor(
+    val uri: Uri, // Profile
+    var profileName: String?,
+    val isUser: Boolean
+) {
+    enum class Status {
+        BANNED, REQUEST_SENT, CONFIRMED, NO_REQUEST
+    }
+
+    var username: String? = null
+        private set
+    var photoId: Long = 0
+        private set
+    val phones = ArrayList<Phone>()
+    var isStared = false
+        private set
+    var isFromSystem = false
+    var status = Status.NO_REQUEST
+    var addedDate: Date? = null
+    private var mOnline = false
+    var id: Long = 0
+    private var mLookupKey: String? = null
+    var isUsernameLoaded = false
+        private set
+    @JvmField
+    var detailsLoaded = false
+    private val mConversationUri: BehaviorSubject<Uri> = BehaviorSubject.createDefault(uri)
+    var photo: Any? = null
+    private val mContactUpdates: Subject<Contact> = BehaviorSubject.create()
+    var updates: Observable<Contact>? = null
+    var presenceUpdates: Observable<Boolean>? = null
+    private var mContactPresenceEmitter: Emitter<Boolean>? = null
+
+    @JvmOverloads
+    constructor(uri: Uri, user: Boolean = false) : this(uri, null, user) {
+    }
+
+    fun setConversationUri(conversationUri: Uri) {
+        mConversationUri.onNext(conversationUri)
+    }
+
+    val conversationUri: Observable<Uri>
+        get() = mConversationUri
+    val updatesSubject: Observable<Contact>
+        get() = mContactUpdates
+
+    fun setPresenceEmitter(emitter: Emitter<Boolean>?) {
+        mContactPresenceEmitter?.let { e ->
+            if (e != emitter)
+                e.onComplete()
+        }
+        mContactPresenceEmitter = emitter
+    }
+
+    fun matches(query: String): Boolean {
+        return (profileName != null && profileName!!.lowercase().contains(query)
+                || username != null && username!!.contains(query)
+                || primaryNumber.contains(query))
+    }
+
+    var isOnline: Boolean
+        get() = mOnline
+        set(present) {
+            mOnline = present
+            if (mContactPresenceEmitter != null) mContactPresenceEmitter!!.onNext(present)
+        }
+
+    fun setSystemId(id: Long) {
+        this.id = id
+    }
+
+    fun setSystemContactInfo(id: Long, k: String?, displayName: String, photo_id: Long) {
+        this.id = id
+        mLookupKey = k
+        profileName = displayName
+        photoId = photo_id
+        if (username == null && displayName.contains(PREFIX_RING)) {
+            username = displayName
+        }
+    }
+
+    val ids: ArrayList<String>
+        get() {
+            val ret = ArrayList<String>(phones.size + if (id == UNKNOWN_ID.toLong()) 0 else 1)
+            if (id != UNKNOWN_ID.toLong()) ret.add(
+                "c:" + java.lang.Long.toHexString(
+                    id
+                )
+            )
+            for (p in phones) ret.add(p.number.rawUriString)
+            return ret
+        }
+    var displayName: String
+        get() {
+            val profileName = profileName
+            return if (profileName != null && profileName.isNotEmpty()) profileName else ringUsername
+        }
+        set(displayName) {
+            profileName = displayName
+        }
+
+    fun hasNumber(number: String): Boolean {
+        return hasNumber(Uri.fromString(number))
+    }
+
+    fun hasNumber(number: Uri?): Boolean {
+        if (number == null || number.isEmpty) return false
+        for (p in phones) if (p.number.toString() == number.toString()) return true
+        return false
+    }
+
+    override fun toString(): String {
+        username?.let { username -> if (username.isNotEmpty()) return@toString username }
+        return uri.rawUriString
+    }
+
+    val primaryNumber: String
+        get() = uri.rawRingId
+
+    fun setStared() {
+        isStared = true
+    }
+
+    fun addPhoneNumber(tel: Uri, cat: Int, label: String?) {
+        if (!hasNumber(tel)) phones.add(Phone(tel, cat, label))
+    }
+
+    fun addNumber(tel: String, cat: Int, label: String?, type: Phone.NumberType?) {
+        if (!hasNumber(tel)) phones.add(Phone(tel, cat, label, type))
+    }
+
+    fun addNumber(tel: Uri, cat: Int, label: String?, type: Phone.NumberType?) {
+        if (!hasNumber(tel)) phones.add(Phone(tel, cat, label, type))
+    }
+
+    fun hasPhoto(): Boolean {
+        return photo != null
+    }
+
+    val isBanned: Boolean
+        get() = status == Status.BANNED
+
+    /**
+     * A contact is Unknown when his name == his phone number
+     *
+     * @return true when Name == Number
+     */
+    val isUnknown: Boolean
+        get() = profileName == null || profileName.contentEquals(phones[0].number.rawUriString)
+    val ringUsername: String
+        get() {
+            val username = username
+            return if (username != null && username.isNotEmpty()) {
+                username
+            } else if (isUsernameLoaded) {
+                uri.rawUriString
+            } else {
+                ""
+            }
+        }
+
+    fun setUsername(name: String?): Boolean {
+        if (!isUsernameLoaded || name != null && name != username) {
+            username = name
+            isUsernameLoaded = true
+            mContactUpdates.onNext(this)
+            return true
+        }
+        return false
+    }
+
+    fun setProfile(name: String?, photo: Any?) {
+        if (name != null && name.isNotEmpty() && !name.startsWith(Uri.RING_URI_SCHEME)) {
+            profileName = name
+        }
+        if (photo != null) {
+            this.photo = photo
+        }
+        detailsLoaded = true
+        mContactUpdates.onNext(this)
+    }
+
+    companion object {
+        private val TAG = Contact::class.simpleName!!
+        const val UNKNOWN_ID = -1
+        const val DEFAULT_ID = 0
+        const val PREFIX_RING = Uri.RING_URI_SCHEME
+
+        @JvmStatic
+        fun buildSIP(to: Uri): Contact {
+            val contact = Contact(to)
+            contact.isUsernameLoaded = true
+            return contact
+        }
+
+        @JvmStatic
+        @JvmOverloads
+        fun build(uri: String, isUser: Boolean = false): Contact {
+            return Contact(Uri.fromString(uri), isUser)
+        }
+
+        fun canonicalNumber(number: String?): String? {
+            return if (number == null || number.isEmpty()) null else Uri.fromString(number).rawUriString
+        }
+
+        fun contactIdFromId(id: String): Long {
+            return if (!id.startsWith("c:")) UNKNOWN_ID.toLong() else try {
+                id.substring(2).toLong(16)
+            } catch (e: Exception) {
+                UNKNOWN_ID.toLong()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.java b/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.java
deleted file mode 100644
index c3682e938..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-public class ContactEvent extends Interaction {
-
-    public TrustRequest request;
-    public Event event;
-
-
-    public ContactEvent(Interaction interaction) {
-        mId = interaction.getId();
-        mConversation = interaction.getConversation();
-        mAuthor = interaction.getAuthor();
-        mType = InteractionType.CONTACT.toString();
-        mTimestamp = interaction.getTimestamp();
-        mStatus = interaction.getStatus().toString();
-        mIsRead = 1;
-        mContact = interaction.getContact();
-        event = getEventFromStatus(interaction.getStatus());
-    }
-
-    public ContactEvent() {
-        mAuthor = null;
-        event = Event.ADDED;
-        mType = InteractionType.CONTACT.toString();
-        mTimestamp = System.currentTimeMillis();
-        mStatus = InteractionStatus.SUCCESS.toString();
-        mIsRead = 1;
-    }
-
-    public ContactEvent(Contact contact) {
-        mContact = contact;
-        mAuthor = contact.getUri().getUri();
-        mType = InteractionType.CONTACT.toString();
-        event = Event.ADDED;
-        mStatus = InteractionStatus.SUCCESS.toString();
-        mTimestamp = contact.getAddedDate().getTime();
-        mIsRead = 1;
-    }
-
-    public ContactEvent(Contact contact, TrustRequest request) {
-        this.request = request;
-        mContact = contact;
-        mAuthor = contact.getUri().getUri();
-        mTimestamp = request.getTimestamp();
-        mType = InteractionType.CONTACT.toString();
-        event = Event.INCOMING_REQUEST;
-        mStatus = InteractionStatus.UNKNOWN.toString();
-        mIsRead = 1;
-    }
-
-    public enum Event {
-        UNKNOWN,
-        INCOMING_REQUEST,
-        ADDED,
-        REMOVED,
-        BANNED
-    }
-
-    public void setEvent(Event event) {
-        this.event = event;
-    }
-
-    public void setRequest(TrustRequest request) {
-        this.request = request;
-    }
-
-    private Event getEventFromStatus(InteractionStatus status) {
-        // success for added contacts
-        if (status == InteractionStatus.SUCCESS)
-            return Event.ADDED;
-        // storage is unknown status for trust requests
-        else if (status == InteractionStatus.UNKNOWN)
-            return Event.INCOMING_REQUEST;
-
-        return Event.UNKNOWN;
-    }
-
-    public void setTimestamp(long timestamp) {
-        mTimestamp = timestamp;
-    }
-
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.kt b/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.kt
new file mode 100644
index 000000000..c5e6222c6
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/ContactEvent.kt
@@ -0,0 +1,96 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+class ContactEvent : Interaction {
+    var request: TrustRequest? = null
+    @JvmField
+    var event: Event
+
+    constructor(interaction: Interaction) {
+        id = interaction.id
+        conversation = interaction.conversation
+        author = interaction.author
+        mType = InteractionType.CONTACT.toString()
+        timestamp = interaction.timestamp
+        mStatus = interaction.status.toString()
+        mIsRead = 1
+        contact = interaction.contact
+        event = getEventFromStatus(interaction.status)
+    }
+
+    constructor() {
+        author = null
+        event = Event.ADDED
+        mType = InteractionType.CONTACT.toString()
+        timestamp = System.currentTimeMillis()
+        mStatus = InteractionStatus.SUCCESS.toString()
+        mIsRead = 1
+    }
+
+    constructor(contact: Contact) {
+        this.contact = contact
+        author = contact.uri.uri
+        mType = InteractionType.CONTACT.toString()
+        event = Event.ADDED
+        mStatus = InteractionStatus.SUCCESS.toString()
+        timestamp = contact.addedDate!!.time
+        mIsRead = 1
+    }
+
+    constructor(contact: Contact, request: TrustRequest) {
+        this.request = request
+        this.contact = contact
+        author = contact.uri.uri
+        timestamp = request.timestamp
+        mType = InteractionType.CONTACT.toString()
+        event = Event.INCOMING_REQUEST
+        mStatus = InteractionStatus.UNKNOWN.toString()
+        mIsRead = 1
+    }
+
+    enum class Event {
+        UNKNOWN, INCOMING_REQUEST, INVITED, ADDED, REMOVED, BANNED;
+
+        companion object {
+            fun fromConversationAction(action: String): Event {
+                return when (action) {
+                    "add" -> INVITED
+                    "join" -> ADDED
+                    "remove" -> REMOVED
+                    "ban" -> BANNED
+                    else -> UNKNOWN
+                }
+            }
+        }
+    }
+
+    fun setEvent(event: Event): ContactEvent {
+        this.event = event
+        return this
+    }
+
+    private fun getEventFromStatus(status: InteractionStatus): Event {
+        // success for added contacts
+        if (status === InteractionStatus.SUCCESS) return Event.ADDED else if (status === InteractionStatus.UNKNOWN) return Event.INCOMING_REQUEST
+        return Event.UNKNOWN
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java
deleted file mode 100644
index b8c74e702..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java
+++ /dev/null
@@ -1,763 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package net.jami.model;
-
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Set;
-import java.util.TreeMap;
-
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.SingleSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class Conversation extends ConversationHistory {
-    private static final String TAG = Conversation.class.getSimpleName();
-
-    private final String mAccountId;
-    private final Uri mKey;
-    private final List<Contact> mContacts;
-
-    private final NavigableMap<Long, Interaction> mHistory = new TreeMap<>();
-    private final ArrayList<Conference> mCurrentCalls = new ArrayList<>();
-    private final ArrayList<Interaction> mAggregateHistory = new ArrayList<>(32);
-    private Interaction lastDisplayed = null;
-
-    private final Subject<Tuple<Interaction, ElementStatus>> updatedElementSubject = PublishSubject.create();
-    private final Subject<Interaction> lastDisplayedSubject = BehaviorSubject.create();
-    private final Subject<List<Interaction>> clearedSubject = PublishSubject.create();
-    private final Subject<List<Conference>> callsSubject = BehaviorSubject.create();
-    private final Subject<Account.ComposingStatus> composingStatusSubject = BehaviorSubject.createDefault(Account.ComposingStatus.Idle);
-    private final Subject<Integer> color = BehaviorSubject.create();
-    private final Subject<CharSequence> symbol = BehaviorSubject.create();
-    private final Subject<List<Contact>> mContactSubject = BehaviorSubject.create();
-
-    private Single<Conversation> isLoaded = null;
-    private Completable lastElementLoaded = null;
-
-    private final Set<String> mRoots = new HashSet<>(2);
-    private final Map<String, Interaction> mMessages = new HashMap<>(16);
-    private String lastRead = null;
-    private final Mode mMode;
-
-    // runtime flag set to true if the user is currently viewing this conversation
-    private boolean mVisible = false;
-    private final Subject<Boolean> mVisibleSubject = BehaviorSubject.createDefault(mVisible);
-
-    // indicate the list needs sorting
-    private boolean mDirty = false;
-    private SingleSubject<Conversation> mLoadingSubject = null;
-
-    public Conversation(String accountId, Contact contact) {
-        mAccountId = accountId;
-        mContacts = Collections.singletonList(contact);
-        mKey = contact.getUri();
-        mParticipant = contact.getUri().getUri();
-        mContactSubject.onNext(mContacts);
-        mMode = null;
-    }
-
-    public Conversation(String accountId, Uri uri, Mode mode) {
-        mAccountId = accountId;
-        mKey = uri;
-        mContacts = new ArrayList<>(3);
-        mMode = mode;
-    }
-
-    public Conference getConference(String id) {
-        for (Conference c : mCurrentCalls)
-            if (c.getId().contentEquals(id) || c.getCallById(id) != null) {
-                return c;
-            }
-        return null;
-    }
-
-    public void composingStatusChanged(Contact contact, Account.ComposingStatus composing) {
-        composingStatusSubject.onNext(composing);
-    }
-
-    public Uri getUri() {
-        return mKey;
-    }
-
-    public Mode getMode() { return mMode; }
-
-    public boolean isSwarm() {
-        return Uri.SWARM_SCHEME.equals(getUri().getScheme());
-    }
-
-    public boolean matches(String query) {
-        for (Contact contact : getContacts()) {
-            if (contact.matches(query))
-                return true;
-        }
-        return false;
-    }
-
-    public String getDisplayName() {
-        return mContacts.get(0).getDisplayName();
-    }
-
-    public void addContact(Contact contact) {
-        mContacts.add(contact);
-        mContactSubject.onNext(mContacts);
-    }
-
-    public void removeContact(Contact contact)  {
-        mContacts.remove(contact);
-        mContactSubject.onNext(mContacts);
-    }
-
-    public String getTitle() {
-        if (mContacts.isEmpty()) {
-            return null;
-        } else if (mContacts.size() == 1) {
-            return mContacts.get(0).getDisplayName();
-        }
-        ArrayList<String> names = new ArrayList<>(mContacts.size());
-        int target = mContacts.size();
-        for (Contact c : mContacts) {
-            if (c.isUser()) {
-                target--;
-                continue;
-            }
-            String displayName = c.getDisplayName();
-            if (!StringUtils.isEmpty(displayName)) {
-                names.add(displayName);
-                if (names.size() == 3)
-                    break;
-            }
-        }
-        StringBuilder ret = new StringBuilder();
-        ret.append(StringUtils.join(", ", names));
-        if (!names.isEmpty() && names.size() < target) {
-            ret.append(" + ").append(mContacts.size() - names.size());
-        }
-        String result = ret.toString();
-        return result.isEmpty() ? mKey.getRawUriString() : result;
-    }
-
-    public String getUriTitle() {
-        if (mContacts.isEmpty()) {
-            return null;
-        } else if (mContacts.size() == 1) {
-            return mContacts.get(0).getRingUsername();
-        }
-        ArrayList<String> names = new ArrayList<>(mContacts.size());
-        for (Contact c : mContacts) {
-            if (c.isUser())
-                continue;
-            names.add(c.getRingUsername());
-        }
-        return StringUtils.join(", ", names);
-    }
-
-    public Observable<List<Contact>> getContactUpdates() {
-        return mContactSubject;
-    }
-
-    public synchronized String readMessages() {
-        Interaction interaction = null;
-        //for (String branch : mBranches) {
-            Interaction i = mAggregateHistory.get(mAggregateHistory.size() - 1);
-            if (i != null && !i.isRead()) {
-                i.read();
-                interaction = i;
-                lastRead = i.getMessageId();
-            }
-        //}
-        return interaction == null ? null : interaction.getMessageId();
-    }
-
-    public synchronized Interaction getMessage(String messageId) {
-        return mMessages.get(messageId);
-    }
-
-    public void setLastMessageRead(String lastMessageRead) {
-        lastRead = lastMessageRead;
-    }
-
-    public String getLastRead() {
-        return lastRead;
-    }
-
-    public SingleSubject<Conversation> getLoading() {
-        return mLoadingSubject;
-    }
-
-    public boolean stopLoading() {
-        SingleSubject<Conversation> ret = mLoadingSubject;
-        mLoadingSubject = null;
-        if (ret != null) {
-            ret.onSuccess(this);
-            return true;
-        }
-        return false;
-    }
-
-    public void setLoading(SingleSubject<Conversation> l) {
-        if (mLoadingSubject != null) {
-            if (!mLoadingSubject.hasValue() && !mLoadingSubject.hasThrowable())
-                mLoadingSubject.onError(new IllegalStateException());
-        }
-        mLoadingSubject = l;
-    }
-
-    public Completable getLastElementLoaded() {
-        return lastElementLoaded;
-    }
-
-    public void setLastElementLoaded(Completable c) {
-        lastElementLoaded = c;
-    }
-
-    public enum ElementStatus {
-        UPDATE, REMOVE, ADD
-    }
-
-    public Observable<Tuple<Interaction, ElementStatus>> getUpdatedElements() {
-        return updatedElementSubject;
-    }
-
-    public Observable<Interaction> getLastDisplayed() {
-        return lastDisplayedSubject;
-    }
-
-    public Observable<List<Interaction>> getCleared() {
-        return clearedSubject;
-    }
-
-    public Observable<List<Conference>> getCalls() {
-        return callsSubject;
-    }
-
-    public Observable<Account.ComposingStatus> getComposingStatus() {
-        return composingStatusSubject;
-    }
-
-    public void addConference(final Conference conference) {
-        if (conference == null) {
-            return;
-        }
-        for (int i = 0; i < mCurrentCalls.size(); i++) {
-            final Conference currentConference = mCurrentCalls.get(i);
-            if (currentConference == conference) {
-                return;
-            }
-            if (currentConference.getId().equals(conference.getId())) {
-                mCurrentCalls.set(i, conference);
-                return;
-            }
-        }
-        mCurrentCalls.add(conference);
-        callsSubject.onNext(mCurrentCalls);
-    }
-
-    public void removeConference(Conference c) {
-        mCurrentCalls.remove(c);
-        callsSubject.onNext(mCurrentCalls);
-    }
-
-    public boolean isVisible() {
-        return mVisible;
-    }
-
-    public Observable<Boolean> getVisible()  {
-        return mVisibleSubject;
-    }
-
-    public void setLoaded(Single<Conversation> loaded) {
-        isLoaded = loaded;
-    }
-
-    public Single<Conversation> getLoaded() {
-        return isLoaded;
-    }
-
-    public void setVisible(boolean visible) {
-        mVisible = visible;
-        mVisibleSubject.onNext(mVisible);
-    }
-
-    public List<Contact> getContacts() {
-        return mContacts;
-    }
-
-    public Contact getContact() {
-        if (mContacts.size() == 1)
-            return mContacts.get(0);
-        if (isSwarm()) {
-            if (mContacts.size() > 2)
-                throw new IllegalStateException("getContact() called for group conversation of size " + mContacts.size());
-        }
-        for (Contact contact : mContacts) {
-            if (!contact.isUser())
-                return contact;
-        }
-        return null;
-    }
-
-    public void addCall(Call call) {
-        if (!isSwarm() && getCallHistory().contains(call)) {
-            return;
-        }
-        mDirty = true;
-        mAggregateHistory.add(call);
-        updatedElementSubject.onNext(new Tuple<>(call, ElementStatus.ADD));
-    }
-
-    private void setInteractionProperties(Interaction interaction) {
-        interaction.setAccount(getAccountId());
-        if (interaction.getContact() == null) {
-            if (mContacts.size() == 1)
-                interaction.setContact(mContacts.get(0));
-            else {
-                if (interaction.getAuthor() == null)  {
-                    Log.e(TAG, "Can't set interaction properties: no author for type:" + interaction.getType() + " id:" + interaction.getId() + " status:" + interaction.mStatus);
-                } else {
-                    interaction.setContact(findContact(Uri.fromString(interaction.getAuthor())));
-                }
-            }
-        }
-    }
-
-    public Contact findContact(Uri uri) {
-        for (Contact contact : mContacts)  {
-            if (contact.getUri().equals(uri)) {
-                return contact;
-            }
-        }
-        return null;
-    }
-
-    public void addTextMessage(TextMessage txt) {
-        if (mVisible) {
-            txt.read();
-        }
-        if (txt.getConversation() == null) {
-            Log.e(TAG, "Error in conversation class... No conversation is attached to this interaction");
-        }
-        setInteractionProperties(txt);
-        mHistory.put(txt.getTimestamp(), txt);
-        mDirty = true;
-        mAggregateHistory.add(txt);
-        updatedElementSubject.onNext(new Tuple<>(txt, ElementStatus.ADD));
-    }
-
-    public void addRequestEvent(TrustRequest request, Contact contact) {
-        if (isSwarm())
-            return;
-        ContactEvent event = new ContactEvent(contact, request);
-        mDirty = true;
-        mAggregateHistory.add(event);
-        updatedElementSubject.onNext(new Tuple<>(event, ElementStatus.ADD));
-    }
-
-    public void addContactEvent(Contact contact) {
-        ContactEvent event = new ContactEvent(contact);
-        mDirty = true;
-        mAggregateHistory.add(event);
-        updatedElementSubject.onNext(new Tuple<>(event, ElementStatus.ADD));
-    }
-
-    public void addContactEvent(ContactEvent contactEvent) {
-        mDirty = true;
-        mAggregateHistory.add(contactEvent);
-        updatedElementSubject.onNext(new Tuple<>(contactEvent, ElementStatus.ADD));
-    }
-
-    public void addFileTransfer(DataTransfer dataTransfer) {
-        if (mAggregateHistory.contains(dataTransfer)) {
-            return;
-        }
-        mDirty = true;
-        mAggregateHistory.add(dataTransfer);
-        updatedElementSubject.onNext(new Tuple<>(dataTransfer, ElementStatus.ADD));
-    }
-
-    boolean isAfter(Interaction previous, Interaction query) {
-        if (isSwarm()) {
-            while (query != null && query.getParentIds() != null && !query.getParentIds().isEmpty()) {
-                if (query.getParentIds().contains(previous.getMessageId()))
-                    return true;
-                query = mMessages.get(query.getParentIds().get(0));
-            }
-            return false;
-        } else {
-            return previous.getTimestamp() < query.getTimestamp();
-        }
-    }
-
-    public void updateInteraction(Interaction element) {
-        Log.e(TAG, "updateInteraction: " + element.getMessageId() + " " + element.getStatus());
-        if (isSwarm()) {
-            Interaction e = mMessages.get(element.getMessageId());
-            if (e != null) {
-                e.setStatus(element.getStatus());
-                updatedElementSubject.onNext(new Tuple<>(e, ElementStatus.UPDATE));
-                if (e.getStatus() == Interaction.InteractionStatus.DISPLAYED) {
-                    if (lastDisplayed == null || isAfter(lastDisplayed, e)) {
-                        lastDisplayed = e;
-                        lastDisplayedSubject.onNext(e);
-                    }
-                }
-            } else {
-                Log.e(TAG, "Can't find swarm message to update: " + element.getMessageId());
-            }
-        } else {
-            setInteractionProperties(element);
-            long time = element.getTimestamp();
-            NavigableMap<Long, Interaction> msgs = mHistory.subMap(time, true, time, true);
-            for (Interaction txt : msgs.values()) {
-                if (txt.getId() == element.getId()) {
-                    txt.setStatus(element.getStatus());
-                    updatedElementSubject.onNext(new Tuple<>(txt, ElementStatus.UPDATE));
-                    if (element.getStatus() == Interaction.InteractionStatus.DISPLAYED) {
-                        if (lastDisplayed == null || isAfter(lastDisplayed, element)) {
-                            lastDisplayed = element;
-                            lastDisplayedSubject.onNext(element);
-                        }
-                    }
-                    return;
-                }
-            }
-            Log.e(TAG, "Can't find message to update: " + element.getId());
-        }
-    }
-
-    public ArrayList<Interaction> getAggregateHistory() {
-        return mAggregateHistory;
-    }
-
-    private final Single<List<Interaction>> sortedHistory = Single.fromCallable(() -> {
-        sortHistory();
-        return mAggregateHistory;
-    });
-
-    public void sortHistory() {
-        if (mDirty) {
-            Log.w(TAG, "sortHistory()");
-            synchronized (mAggregateHistory) {
-                Collections.sort(mAggregateHistory, (c1, c2) -> Long.compare(c1.getTimestamp(), c2.getTimestamp()));
-            }
-            mDirty = false;
-        }
-    }
-
-    public Single<List<Interaction>> getSortedHistory() {
-        return sortedHistory;
-    }
-
-    public Interaction getLastEvent() {
-        sortHistory();
-        return mAggregateHistory.isEmpty() ? null : mAggregateHistory.get(mAggregateHistory.size() - 1);
-    }
-
-    public Conference getCurrentCall() {
-        if (mCurrentCalls.isEmpty()) {
-            return null;
-        }
-        return mCurrentCalls.get(0);
-    }
-
-    public ArrayList<Conference> getCurrentCalls() {
-        return mCurrentCalls;
-    }
-
-    public Collection<Call> getCallHistory() {
-        List<Call> result = new ArrayList<>();
-        for (Interaction interaction : mAggregateHistory) {
-            if (interaction.getType() == Interaction.InteractionType.CALL) {
-                result.add((Call) interaction);
-            }
-        }
-        return result;
-    }
-
-    public TreeMap<Long, TextMessage> getUnreadTextMessages() {
-        TreeMap<Long, TextMessage> texts = new TreeMap<>();
-        if (isSwarm()) {
-            for(int j = mAggregateHistory.size() - 1; j >= 0; j--) {
-                Interaction i = mAggregateHistory.get(j);
-                if (i.isRead())
-                    break;
-                if (i instanceof TextMessage)
-                    texts.put(i.getTimestamp(), (TextMessage) i);
-            }
-        } else {
-            for (Map.Entry<Long, Interaction> entry : mHistory.descendingMap().entrySet()) {
-                Interaction value = entry.getValue();
-                if (value.getType() == Interaction.InteractionType.TEXT) {
-                    TextMessage message = (TextMessage) value;
-                    if (message.isRead())
-                        break;
-                    texts.put(entry.getKey(), message);
-                }
-            }
-        }
-        return texts;
-    }
-
-    public NavigableMap<Long, Interaction> getRawHistory() {
-        return mHistory;
-    }
-
-
-    private Interaction findConversationElement(int transferId) {
-        for (Interaction interaction : mAggregateHistory) {
-            if (interaction != null && interaction.getType() == (Interaction.InteractionType.DATA_TRANSFER)) {
-                if (transferId == (interaction.getId())) {
-                    return interaction;
-                }
-            }
-        }
-        return null;
-    }
-
-    private boolean removeSwarmInteraction(String messageId) {
-        Interaction i = mMessages.remove(messageId);
-        if (i != null) {
-            mAggregateHistory.remove(i);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean removeInteraction(long interactionId) {
-        Iterator<Interaction> it = mAggregateHistory.iterator();
-        while (it.hasNext()) {
-            Interaction interaction = it.next();
-            Integer id = interaction == null ? null : interaction.getId();
-            if (id != null && interactionId == id) {
-                it.remove();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Clears the conversation cache.
-     * @param delete true if you do not want to re-add contact events
-     */
-    public void clearHistory(boolean delete) {
-        mAggregateHistory.clear();
-        mHistory.clear();
-        mDirty = false;
-        if (!delete && mContacts.size() == 1)
-            mAggregateHistory.add(new ContactEvent(mContacts.get(0)));
-        clearedSubject.onNext(mAggregateHistory);
-    }
-
-    static private Interaction getTypedInteraction(Interaction interaction) {
-        switch (interaction.getType()) {
-            case TEXT:
-                return new TextMessage(interaction);
-            case CALL:
-                return new Call(interaction);
-            case CONTACT:
-                return new ContactEvent(interaction);
-            case DATA_TRANSFER:
-                return new DataTransfer(interaction);
-        }
-        return interaction;
-    }
-
-    public void setHistory(List<Interaction> loadedConversation) {
-        mAggregateHistory.ensureCapacity(loadedConversation.size());
-        Interaction last = null;
-        for (Interaction i : loadedConversation) {
-            Interaction interaction = getTypedInteraction(i);
-            setInteractionProperties(interaction);
-            mAggregateHistory.add(interaction);
-            mHistory.put(interaction.getTimestamp(), interaction);
-            if (!i.isIncoming() && i.getStatus() == Interaction.InteractionStatus.DISPLAYED)
-                last = i;
-        }
-        if (last != null) {
-            lastDisplayed = last;
-            lastDisplayedSubject.onNext(last);
-        }
-        mDirty = false;
-    }
-
-    public void addElement(Interaction interaction) {
-        setInteractionProperties(interaction);
-        if (interaction.getType() == Interaction.InteractionType.TEXT) {
-            TextMessage msg = new TextMessage(interaction);
-            addTextMessage(msg);
-        } else if (interaction.getType() == Interaction.InteractionType.CALL) {
-            Call call = new Call(interaction);
-            addCall(call);
-        } else if (interaction.getType() == Interaction.InteractionType.CONTACT) {
-            ContactEvent event = new ContactEvent(interaction);
-            addContactEvent(event);
-        } else if (interaction.getType() == Interaction.InteractionType.DATA_TRANSFER) {
-            DataTransfer dataTransfer = new DataTransfer(interaction);
-            addFileTransfer(dataTransfer);
-        }
-    }
-
-    public boolean addSwarmElement(Interaction interaction) {
-        if (mMessages.containsKey(interaction.getMessageId())) {
-            return false;
-        }
-        mMessages.put(interaction.getMessageId(), interaction);
-        mRoots.remove(interaction.getMessageId());
-        for (String parent : interaction.getParentIds())
-            if (!mMessages.containsKey(parent)) {
-                mRoots.add(parent);
-                // Log.w(TAG, "@@@ Found new root for " + getUri() + " " + parent + " -> " + mRoots);
-            }
-        if (lastRead != null && lastRead.equals(interaction.getMessageId()))
-            interaction.read();
-        boolean newLeaf = false;
-        boolean added = false;
-        if (mAggregateHistory.isEmpty() || interaction.getParentIds().contains(mAggregateHistory.get(mAggregateHistory.size()-1).getMessageId())) {
-            // New leaf
-            // Log.w(TAG, "@@@ New end LEAF");
-            added = true;
-            newLeaf = true;
-            mAggregateHistory.add(interaction);
-            updatedElementSubject.onNext(new Tuple<>(interaction, ElementStatus.ADD));
-        } else {
-            // New root or normal node
-            for (int i = 0; i < mAggregateHistory.size(); i++) {
-                if (mAggregateHistory.get(i).getParentIds() != null && mAggregateHistory.get(i).getParentIds().contains(interaction.getMessageId())) {
-                    //Log.w(TAG, "@@@ New root node at " + i);
-                    mAggregateHistory.add(i, interaction);
-                    updatedElementSubject.onNext(new Tuple<>(interaction, ElementStatus.ADD));
-                    added = true;
-                    break;
-                }
-            }
-            if (!added) {
-                for (int i = mAggregateHistory.size()-1; i >= 0; i--) {
-                    if (interaction.getParentIds().contains(mAggregateHistory.get(i).getMessageId())) {
-                        //Log.w(TAG, "@@@ New leaf at " + (i+1));
-                        added = true;
-                        newLeaf = true;
-                        mAggregateHistory.add(i+1, interaction);
-                        updatedElementSubject.onNext(new Tuple<>(interaction, ElementStatus.ADD));
-                        break;
-                    }
-                }
-
-            }
-        }
-        if (newLeaf) {
-            if (isVisible()) {
-                interaction.read();
-                setLastMessageRead(interaction.getMessageId());
-            }
-        }
-        if (!added) {
-            Log.e(TAG, "Can't attach interaction " + interaction.getMessageId() + " with parents " + interaction.getParentIds());
-        }
-        return newLeaf;
-    }
-
-    public boolean isLoaded()  {
-        return !mMessages.isEmpty() && mRoots.isEmpty();
-    }
-
-    public Collection<String> getSwarmRoot() {
-        return mRoots;
-    }
-
-    public void updateFileTransfer(DataTransfer transfer, Interaction.InteractionStatus eventCode) {
-        DataTransfer dataTransfer = (DataTransfer) (isSwarm() ? transfer : findConversationElement(transfer.getId()));
-        if (dataTransfer != null) {
-            dataTransfer.setStatus(eventCode);
-            updatedElementSubject.onNext(new Tuple<>(dataTransfer, ElementStatus.UPDATE));
-        }
-    }
-
-    public void removeInteraction(Interaction interaction) {
-        if (isSwarm()) {
-            if (removeSwarmInteraction(interaction.getMessageId()))
-                updatedElementSubject.onNext(new Tuple<>(interaction, ElementStatus.REMOVE));
-        } else {
-            if (removeInteraction(interaction.getId()))
-                updatedElementSubject.onNext(new Tuple<>(interaction, ElementStatus.REMOVE));
-        }
-    }
-
-    public void removeAll() {
-        mAggregateHistory.clear();
-        mCurrentCalls.clear();
-        mHistory.clear();
-        mDirty = true;
-    }
-
-    public void setColor(int c) {
-        color.onNext(c);
-    }
-
-    public void setSymbol(CharSequence s) {
-        symbol.onNext(s);
-    }
-
-    public Observable<Integer> getColor() {
-        return color;
-    }
-    public Observable<CharSequence> getSymbol() {
-        return symbol;
-    }
-
-
-    public String getAccountId() {
-        return mAccountId;
-    }
-
-    public enum Mode {
-        OneToOne,
-        AdminInvitesOnly,
-        InvitesOnly,
-        Public
-    }
-
-    public interface ConversationActionCallback {
-
-        void removeConversation(Uri callContact);
-
-        void clearConversation(Uri callContact);
-
-        void copyContactNumberToClipboard(String contactNumber);
-
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt
new file mode 100644
index 000000000..6ebbbc7a2
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt
@@ -0,0 +1,660 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.SingleSubject
+import io.reactivex.rxjava3.subjects.Subject
+import kotlin.jvm.Synchronized
+import net.jami.utils.Log
+import net.jami.utils.StringUtils
+import net.jami.utils.Tuple
+import java.lang.IllegalStateException
+import java.lang.StringBuilder
+import java.util.*
+
+class Conversation : ConversationHistory {
+    val accountId: String
+    val uri: Uri
+    val contacts: MutableList<Contact>
+    val rawHistory: NavigableMap<Long, Interaction> = TreeMap()
+    val currentCalls = ArrayList<Conference>()
+    val aggregateHistory = ArrayList<Interaction>(32)
+    private var lastDisplayed: Interaction? = null
+    private val updatedElementSubject: Subject<Tuple<Interaction, ElementStatus>> = PublishSubject.create()
+    private val lastDisplayedSubject: Subject<Interaction> = BehaviorSubject.create()
+    private val clearedSubject: Subject<List<Interaction>> = PublishSubject.create()
+    private val callsSubject: Subject<List<Conference>> = BehaviorSubject.create()
+    private val composingStatusSubject: Subject<Account.ComposingStatus> = BehaviorSubject.createDefault(Account.ComposingStatus.Idle)
+    private val color: Subject<Int> = BehaviorSubject.create()
+    private val symbol: Subject<CharSequence> = BehaviorSubject.create()
+    private val mContactSubject: Subject<List<Contact>> = BehaviorSubject.create()
+    var loaded: Single<Conversation>? = null
+    var lastElementLoaded: Completable? = null
+    private val mRoots: MutableSet<String> = HashSet(2)
+    private val mMessages: MutableMap<String, Interaction> = HashMap(16)
+    var lastRead: String? = null
+        private set
+    private val mMode: Subject<Mode>
+
+    // runtime flag set to true if the user is currently viewing this conversation
+    private var mVisible = false
+    private val mVisibleSubject: Subject<Boolean> = BehaviorSubject.createDefault(mVisible)
+
+    // indicate the list needs sorting
+    private var mDirty = false
+    private var mLoadingSubject: SingleSubject<Conversation>? = null
+
+    constructor(accountId: String, contact: Contact) {
+        this.accountId = accountId
+        contacts = mutableListOf(contact)
+        uri = contact.uri
+        mParticipant = contact.uri.uri
+        mContactSubject.onNext(contacts)
+        mMode = BehaviorSubject.createDefault(Mode.Legacy)
+    }
+
+    constructor(accountId: String, uri: Uri, mode: Mode) {
+        this.accountId = accountId
+        this.uri = uri
+        contacts = ArrayList(3)
+        mMode = BehaviorSubject.createDefault(mode)
+    }
+
+    fun getConference(id: String?): Conference? {
+        for (c in currentCalls) if (c.id.contentEquals(id) || c.getCallById(id) != null) {
+            return c
+        }
+        return null
+    }
+
+    fun composingStatusChanged(contact: Contact?, composing: Account.ComposingStatus) {
+        composingStatusSubject.onNext(composing)
+    }
+
+    val mode: Observable<Mode>
+        get() = mMode
+    val isSwarm: Boolean
+        get() = Uri.SWARM_SCHEME == uri.scheme
+
+    fun matches(query: String?): Boolean {
+        for (contact in contacts) {
+            if (contact.matches(query!!)) return true
+        }
+        return false
+    }
+
+    val displayName: String?
+        get() = contacts[0].displayName
+
+    fun addContact(contact: Contact) {
+        contacts.add(contact)
+        mContactSubject.onNext(contacts)
+    }
+
+    fun removeContact(contact: Contact) {
+        contacts.remove(contact)
+        mContactSubject.onNext(contacts)
+    }
+
+    val title: String?
+        get() {
+            if (contacts.isEmpty()) {
+                return if (mMode.blockingFirst() == Mode.Syncing) { "(Syncing)" } else null
+            } else if (contacts.size == 1) {
+                return contacts[0].displayName
+            }
+            val names = ArrayList<String>(contacts.size)
+            var target = contacts.size
+            for (c in contacts) {
+                if (c.isUser) {
+                    target--
+                    continue
+                }
+                val displayName = c.displayName
+                if (displayName.isNotEmpty()) {
+                    names.add(displayName)
+                    if (names.size == 3) break
+                }
+            }
+            val ret = StringBuilder()
+            ret.append(StringUtils.join(", ", names))
+            if (names.isNotEmpty() && names.size < target) {
+                ret.append(" + ").append(contacts.size - names.size)
+            }
+            val result = ret.toString()
+            return if (result.isEmpty()) uri.rawUriString else result
+        }
+    val uriTitle: String?
+        get() {
+            if (contacts.isEmpty()) {
+                return null
+            } else if (contacts.size == 1) {
+                return contacts[0].ringUsername
+            }
+            val names = ArrayList<String>(contacts.size)
+            for (c in contacts) {
+                if (c.isUser) continue
+                c.ringUsername.let { names.add(it) }
+            }
+            return StringUtils.join(", ", names)
+        }
+    val contactUpdates: Observable<List<Contact>>
+        get() = mContactSubject
+
+    @Synchronized
+    fun readMessages(): String? {
+        var interaction: Interaction? = null
+        if (aggregateHistory.isNotEmpty()) {
+            val i = aggregateHistory[aggregateHistory.size - 1]
+            if (!i.isRead) {
+                i.read()
+                interaction = i
+                lastRead = i.messageId
+            }
+        }
+        return interaction?.messageId
+    }
+
+    @Synchronized
+    fun getMessage(messageId: String): Interaction? {
+        return mMessages[messageId]
+    }
+
+    fun setLastMessageRead(lastMessageRead: String?) {
+        lastRead = lastMessageRead
+    }
+
+    var loading: SingleSubject<Conversation>?
+        get() = mLoadingSubject
+        set(l) {
+            if (mLoadingSubject != null) {
+                if (!mLoadingSubject!!.hasValue() && !mLoadingSubject!!.hasThrowable()) mLoadingSubject!!.onError(
+                    IllegalStateException()
+                )
+            }
+            mLoadingSubject = l
+        }
+
+    fun stopLoading(): Boolean {
+        val ret = mLoadingSubject
+        mLoadingSubject = null
+        if (ret != null) {
+            ret.onSuccess(this)
+            return true
+        }
+        return false
+    }
+
+    fun setMode(mode: Mode) {
+        mMode.onNext(mode)
+    }
+
+    enum class ElementStatus {
+        UPDATE, REMOVE, ADD
+    }
+
+    val updatedElements: Observable<Tuple<Interaction, ElementStatus>>
+        get() = updatedElementSubject
+
+    fun getLastDisplayed(): Observable<Interaction> {
+        return lastDisplayedSubject
+    }
+
+    val cleared: Observable<List<Interaction>>
+        get() = clearedSubject
+    val calls: Observable<List<Conference>>
+        get() = callsSubject
+    val composingStatus: Observable<Account.ComposingStatus>
+        get() = composingStatusSubject
+
+    fun addConference(conference: Conference?) {
+        if (conference == null) {
+            return
+        }
+        for (i in currentCalls.indices) {
+            val currentConference = currentCalls[i]
+            if (currentConference === conference) {
+                return
+            }
+            if (currentConference.id == conference.id) {
+                currentCalls[i] = conference
+                return
+            }
+        }
+        currentCalls.add(conference)
+        callsSubject.onNext(currentCalls)
+    }
+
+    fun removeConference(c: Conference) {
+        currentCalls.remove(c)
+        callsSubject.onNext(currentCalls)
+    }
+
+    var isVisible: Boolean
+        get() = mVisible
+        set(visible) {
+            mVisible = visible
+            mVisibleSubject.onNext(mVisible)
+        }
+
+    fun getVisible(): Observable<Boolean> {
+        return mVisibleSubject
+    }
+
+    val contact: Contact?
+        get() {
+            if (contacts.size == 1) return contacts[0]
+            if (isSwarm) {
+                check(contacts.size <= 2) { "getContact() called for group conversation of size " + contacts.size }
+            }
+            for (contact in contacts) {
+                if (!contact.isUser) return contact
+            }
+            return null
+        }
+
+    fun addCall(call: Call) {
+        if (!isSwarm && callHistory.contains(call)) {
+            return
+        }
+        mDirty = true
+        aggregateHistory.add(call)
+        updatedElementSubject.onNext(Tuple(call, ElementStatus.ADD))
+    }
+
+    private fun setInteractionProperties(interaction: Interaction) {
+        interaction.account = accountId
+        if (interaction.contact == null) {
+            if (contacts.size == 1) interaction.contact = contacts[0] else {
+                if (interaction.author == null) {
+                    Log.e(TAG, "Can't set interaction properties: no author for type:" + interaction.type + " id:" + interaction.id + " status:" + interaction.mStatus)
+                } else {
+                    interaction.contact = findContact(Uri.fromString(interaction.author!!))
+                }
+            }
+        }
+    }
+
+    fun findContact(uri: Uri): Contact? {
+        for (contact in contacts) {
+            if (contact.uri == uri) {
+                return contact
+            }
+        }
+        return null
+    }
+
+    fun addTextMessage(txt: TextMessage) {
+        if (mVisible) {
+            txt.read()
+        }
+        if (txt.conversation == null) {
+            Log.e(
+                TAG,
+                "Error in conversation class... No conversation is attached to this interaction"
+            )
+        }
+        setInteractionProperties(txt)
+        rawHistory[txt.timestamp] = txt
+        mDirty = true
+        aggregateHistory.add(txt)
+        updatedElementSubject.onNext(Tuple(txt, ElementStatus.ADD))
+    }
+
+    fun addRequestEvent(request: TrustRequest, contact: Contact) {
+        if (isSwarm) return
+        val event = ContactEvent(contact, request)
+        mDirty = true
+        aggregateHistory.add(event)
+        updatedElementSubject.onNext(Tuple(event, ElementStatus.ADD))
+    }
+
+    fun addContactEvent(contact: Contact) {
+        val event = ContactEvent(contact)
+        mDirty = true
+        aggregateHistory.add(event)
+        updatedElementSubject.onNext(Tuple(event, ElementStatus.ADD))
+    }
+
+    fun addContactEvent(contactEvent: ContactEvent) {
+        mDirty = true
+        aggregateHistory.add(contactEvent)
+        updatedElementSubject.onNext(Tuple(contactEvent, ElementStatus.ADD))
+    }
+
+    fun addFileTransfer(dataTransfer: DataTransfer) {
+        if (aggregateHistory.contains(dataTransfer)) {
+            return
+        }
+        mDirty = true
+        aggregateHistory.add(dataTransfer)
+        updatedElementSubject.onNext(Tuple(dataTransfer, ElementStatus.ADD))
+    }
+
+    private fun isAfter(previous: Interaction, query: Interaction?): Boolean {
+        var query = query
+        return if (isSwarm) {
+            while (query != null && query.parentId != null) {
+                if (query.parentId == previous.messageId) return true
+                query = mMessages[query.parentId]
+            }
+            false
+        } else {
+            previous.timestamp < query!!.timestamp
+        }
+    }
+
+    fun updateInteraction(element: Interaction) {
+        Log.e(TAG, "updateInteraction: " + element.messageId + " " + element.status)
+        if (isSwarm) {
+            val e = mMessages[element.messageId]
+            if (e != null) {
+                e.status = element.status
+                updatedElementSubject.onNext(Tuple(e, ElementStatus.UPDATE))
+                if (e.status == Interaction.InteractionStatus.DISPLAYED) {
+                    if (lastDisplayed == null || isAfter(lastDisplayed!!, e)) {
+                        lastDisplayed = e
+                        lastDisplayedSubject.onNext(e)
+                    }
+                }
+            } else {
+                Log.e(TAG, "Can't find swarm message to update: " + element.messageId)
+            }
+        } else {
+            setInteractionProperties(element)
+            val time = element.timestamp
+            val msgs = rawHistory.subMap(time, true, time, true)
+            for (txt in msgs.values) {
+                if (txt.id == element.id) {
+                    txt.status = element.status
+                    updatedElementSubject.onNext(Tuple(txt, ElementStatus.UPDATE))
+                    if (element.status == Interaction.InteractionStatus.DISPLAYED) {
+                        if (lastDisplayed == null || isAfter(lastDisplayed!!, element)) {
+                            lastDisplayed = element
+                            lastDisplayedSubject.onNext(element)
+                        }
+                    }
+                    return
+                }
+            }
+            Log.e(TAG, "Can't find message to update: " + element.id)
+        }
+    }
+
+    val sortedHistory: Single<List<Interaction>> = Single.fromCallable {
+        sortHistory()
+        aggregateHistory
+    }
+
+    fun sortHistory() {
+        if (mDirty) {
+            Log.w(TAG, "sortHistory()")
+            synchronized(aggregateHistory) {
+                aggregateHistory.sortWith { c1: Interaction, c2: Interaction ->
+                    java.lang.Long.compare(c1.timestamp, c2.timestamp)
+                }
+            }
+            mDirty = false
+        }
+    }
+
+    val lastEvent: Interaction?
+        get() {
+            sortHistory()
+            return if (aggregateHistory.isEmpty()) null else aggregateHistory[aggregateHistory.size - 1]
+        }
+    val currentCall: Conference?
+        get() = if (currentCalls.isEmpty()) null else currentCalls[0]
+    private val callHistory: Collection<Call>
+        get() {
+            val result: MutableList<Call> = ArrayList()
+            for (interaction in aggregateHistory) {
+                if (interaction.type == Interaction.InteractionType.CALL) {
+                    result.add(interaction as Call)
+                }
+            }
+            return result
+        }
+    val unreadTextMessages: TreeMap<Long, TextMessage>
+        get() {
+            val texts = TreeMap<Long, TextMessage>()
+            if (isSwarm) {
+                for (j in aggregateHistory.indices.reversed()) {
+                    val i = aggregateHistory[j]
+                    if (i.isRead) break
+                    if (i is TextMessage) texts[i.timestamp] = i
+                }
+            } else {
+                for ((key, value) in rawHistory.descendingMap()) {
+                    if (value.type == Interaction.InteractionType.TEXT) {
+                        val message = value as TextMessage
+                        if (message.isRead) break
+                        texts[key] = message
+                    }
+                }
+            }
+            return texts
+        }
+
+    private fun findConversationElement(transferId: Int): Interaction? {
+        for (interaction in aggregateHistory) {
+            if (interaction.type == Interaction.InteractionType.DATA_TRANSFER) {
+                if (transferId == interaction.id) {
+                    return interaction
+                }
+            }
+        }
+        return null
+    }
+
+    private fun removeSwarmInteraction(messageId: String): Boolean {
+        val i = mMessages.remove(messageId)
+        if (i != null) {
+            aggregateHistory.remove(i)
+            return true
+        }
+        return false
+    }
+
+    private fun removeInteraction(interactionId: Long): Boolean {
+        val it = aggregateHistory.iterator()
+        while (it.hasNext()) {
+            val interaction = it.next()
+            if (interactionId == interaction.id.toLong()) {
+                it.remove()
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * Clears the conversation cache.
+     * @param delete true if you do not want to re-add contact events
+     */
+    fun clearHistory(delete: Boolean) {
+        aggregateHistory.clear()
+        rawHistory.clear()
+        mDirty = false
+        if (!delete && contacts.size == 1) aggregateHistory.add(ContactEvent(contacts[0]))
+        clearedSubject.onNext(aggregateHistory)
+    }
+
+    fun setHistory(loadedConversation: List<Interaction>) {
+        aggregateHistory.ensureCapacity(loadedConversation.size)
+        var last: Interaction? = null
+        for (i in loadedConversation) {
+            val interaction = getTypedInteraction(i)
+            setInteractionProperties(interaction)
+            aggregateHistory.add(interaction)
+            rawHistory[interaction.timestamp] = interaction
+            if (!i.isIncoming && i.status == Interaction.InteractionStatus.DISPLAYED) last = i
+        }
+        if (last != null) {
+            lastDisplayed = last
+            lastDisplayedSubject.onNext(last)
+        }
+        mDirty = false
+    }
+
+    fun addElement(interaction: Interaction) {
+        setInteractionProperties(interaction)
+        when (interaction.type) {
+            Interaction.InteractionType.TEXT -> addTextMessage(TextMessage(interaction))
+            Interaction.InteractionType.CALL -> addCall(Call(interaction))
+            Interaction.InteractionType.CONTACT -> addContactEvent(ContactEvent(interaction))
+            Interaction.InteractionType.DATA_TRANSFER -> addFileTransfer(DataTransfer(interaction))
+        }
+    }
+
+    fun addSwarmElement(interaction: Interaction): Boolean {
+        if (mMessages.containsKey(interaction.messageId)) {
+            return false
+        }
+        mMessages[interaction.messageId!!] = interaction
+        mRoots.remove(interaction.messageId)
+        if (interaction.parentId != null && !mMessages.containsKey(interaction.parentId)) {
+            mRoots.add(interaction.parentId!!)
+            // Log.w(TAG, "@@@ Found new root for " + getUri() + " " + parent + " -> " + mRoots);
+        }
+        if (lastRead != null && lastRead == interaction.messageId) interaction.read()
+        var newLeaf = false
+        var added = false
+        if (aggregateHistory.isEmpty() || aggregateHistory[aggregateHistory.size - 1].messageId == interaction.parentId) {
+            // New leaf
+            // Log.w(TAG, "@@@ New end LEAF");
+            added = true
+            newLeaf = true
+            aggregateHistory.add(interaction)
+            updatedElementSubject.onNext(Tuple(interaction, ElementStatus.ADD))
+        } else {
+            // New root or normal node
+            for (i in aggregateHistory.indices) {
+                if (interaction.messageId == aggregateHistory[i].parentId) {
+                    //Log.w(TAG, "@@@ New root node at " + i);
+                    aggregateHistory.add(i, interaction)
+                    updatedElementSubject.onNext(Tuple(interaction, ElementStatus.ADD))
+                    added = true
+                    break
+                }
+            }
+            if (!added) {
+                for (i in aggregateHistory.indices.reversed()) {
+                    if (aggregateHistory[i].messageId == interaction.parentId) {
+                        //Log.w(TAG, "@@@ New leaf at " + (i+1));
+                        added = true
+                        newLeaf = true
+                        aggregateHistory.add(i + 1, interaction)
+                        updatedElementSubject.onNext(Tuple(interaction, ElementStatus.ADD))
+                        break
+                    }
+                }
+            }
+        }
+        if (newLeaf) {
+            if (isVisible) {
+                interaction.read()
+                setLastMessageRead(interaction.messageId)
+            }
+        }
+        if (!added) {
+            Log.e(
+                TAG,
+                "Can't attach interaction " + interaction.messageId + " with parent " + interaction.parentId
+            )
+        }
+        return newLeaf
+    }
+
+    fun isLoaded(): Boolean {
+        return mMessages.isNotEmpty() && mRoots.isEmpty()
+    }
+
+    val swarmRoot: Collection<String>
+        get() = mRoots
+
+    fun updateFileTransfer(transfer: DataTransfer, eventCode: Interaction.InteractionStatus) {
+        val dataTransfer = (if (isSwarm) transfer else findConversationElement(transfer.id)) as DataTransfer?
+        if (dataTransfer != null) {
+            dataTransfer.status = eventCode
+            updatedElementSubject.onNext(Tuple(dataTransfer, ElementStatus.UPDATE))
+        }
+    }
+
+    fun removeInteraction(interaction: Interaction) {
+        if (isSwarm) {
+            if (removeSwarmInteraction(interaction.messageId!!)) updatedElementSubject.onNext(Tuple(interaction, ElementStatus.REMOVE))
+        } else {
+            if (removeInteraction(interaction.id.toLong())) updatedElementSubject.onNext(Tuple(interaction, ElementStatus.REMOVE))
+        }
+    }
+
+    fun removeAll() {
+        aggregateHistory.clear()
+        currentCalls.clear()
+        rawHistory.clear()
+        mDirty = true
+    }
+
+    fun setColor(c: Int) {
+        color.onNext(c)
+    }
+
+    fun setSymbol(s: CharSequence) {
+        symbol.onNext(s)
+    }
+
+    fun getColor(): Observable<Int> {
+        return color
+    }
+
+    fun getSymbol(): Observable<CharSequence> {
+        return symbol
+    }
+
+    enum class Mode {
+        OneToOne, AdminInvitesOnly, InvitesOnly,  // Non-daemon modes
+        Syncing, Public, Legacy
+    }
+
+    interface ConversationActionCallback {
+        fun removeConversation(callContact: Uri)
+        fun clearConversation(callContact: Uri)
+        fun copyContactNumberToClipboard(contactNumber: String)
+    }
+
+    companion object {
+        private val TAG = Conversation::class.simpleName!!
+        private fun getTypedInteraction(interaction: Interaction): Interaction {
+            return when (interaction.type) {
+                Interaction.InteractionType.TEXT -> TextMessage(interaction)
+                Interaction.InteractionType.CALL -> Call(interaction)
+                Interaction.InteractionType.CONTACT -> ContactEvent(interaction)
+                Interaction.InteractionType.DATA_TRANSFER -> DataTransfer(interaction)
+                else -> interaction
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java b/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java
deleted file mode 100644
index 31a8ceaeb..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-import net.jami.utils.HashUtils;
-import net.jami.utils.StringUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Set;
-
-public class DataTransfer extends Interaction {
-
-    private long mTotalSize;
-    private long mBytesProgress;
-    //private final String mPeerId;
-    private String mExtension;
-    private String mFileId;
-    public File destination;
-    private File mDaemonPath;
-
-    private static final Set<String> IMAGE_EXTENSIONS = HashUtils.asSet("jpg", "jpeg", "png", "gif");
-    private static final Set<String> AUDIO_EXTENSIONS = HashUtils.asSet("ogg", "mp3", "aac", "flac", "m4a");
-    private static final Set<String> VIDEO_EXTENSIONS = HashUtils.asSet("webm", "mp4", "mkv");
-    private static final int MAX_SIZE = 32 * 1024 * 1024;
-    private static final int UNLIMITED_SIZE = 256 * 1024 * 1024;
-
-    /* Legacy constructor */
-    public DataTransfer(ConversationHistory conversation, String peer, String account, String displayName, boolean isOutgoing, long totalSize, long bytesProgress, String fileId) {
-        mAuthor = isOutgoing ? null : peer;
-        mAccount = account;
-        mConversation = conversation;
-        mTotalSize = totalSize;
-        mBytesProgress = bytesProgress;
-        mBody = displayName;
-        mStatus = InteractionStatus.TRANSFER_CREATED.toString();
-        mType = InteractionType.DATA_TRANSFER.toString();
-        mTimestamp = System.currentTimeMillis();
-        mIsRead = 1;
-        mIsIncoming = !isOutgoing;
-        if (fileId != null) {
-            mFileId = fileId;
-            try {
-                mDaemonId = Long.parseUnsignedLong(fileId);
-            } catch (Exception e) {
-
-            }
-        }
-    }
-
-    public DataTransfer(Interaction interaction) {
-        mId = interaction.getId();
-        mDaemonId = interaction.getDaemonId();
-        mAuthor = interaction.getAuthor();
-        mConversation = interaction.getConversation();
-        // mPeerId = interaction.getConversation().getParticipant();
-        mBody = interaction.getBody();
-        mStatus = interaction.getStatus().toString();
-        mType = interaction.getType().toString();
-        mTimestamp = interaction.getTimestamp();
-        mAccount = interaction.getAccount();
-        mContact = interaction.getContact();
-        mIsRead = 1;
-        mIsIncoming = interaction.mIsIncoming;//mAuthor != null;
-    }
-
-    public DataTransfer(String fileId, String accountId, String peerUri, String displayName, boolean isOutgoing, long timestamp, long totalSize, long bytesProgress) {
-        mAccount = accountId;
-        mFileId = fileId;
-        mBody = displayName;
-        mAuthor = peerUri;
-        mIsIncoming = !isOutgoing;
-        mTotalSize = totalSize;
-        mBytesProgress = bytesProgress;
-        mTimestamp = timestamp;
-        mType = InteractionType.DATA_TRANSFER.toString();
-    }
-
-    public String getExtension() {
-        if (mBody == null)
-            return null;
-        if (mExtension == null)
-            mExtension = StringUtils.getFileExtension(mBody).toLowerCase();
-        return mExtension;
-    }
-
-
-    public boolean isPicture() {
-        return IMAGE_EXTENSIONS.contains(getExtension());
-    }
-    public boolean isAudio() {
-        return AUDIO_EXTENSIONS.contains(getExtension());
-    }
-    public boolean isVideo() {
-        return VIDEO_EXTENSIONS.contains(getExtension());
-    }
-
-    public boolean isComplete() {
-        return isOutgoing() || InteractionStatus.TRANSFER_FINISHED.toString().equals(mStatus);
-    }
-    public boolean showPicture() {
-        return isPicture() && isComplete();
-    }
-
-    public String getStoragePath() {
-        if (StringUtils.isEmpty(mBody)) {
-            return getFileId();
-        } else {
-            String ext = StringUtils.getFileExtension(mBody);
-            if (ext.length() > 8)
-                ext = ext.substring(0, 8);
-            if (mDaemonId == null || mDaemonId == 0) {
-                return Long.toString(mId) + '_' + HashUtils.sha1(mBody) + '.' + ext;
-            } else {
-                return Long.toString(mDaemonId) + '_' + HashUtils.sha1(mBody) + '.' + ext;
-            }
-        }
-    }
-
-    public void setSize(long size) {
-        mTotalSize = size;
-    }
-
-    public String getDisplayName() {
-        return mBody;
-    }
-
-    public boolean isOutgoing() {
-        return !mIsIncoming;
-    }
-
-    public long getTotalSize() {
-        return mTotalSize;
-    }
-
-    public long getBytesProgress() {
-        return mBytesProgress;
-    }
-
-    public void setBytesProgress(long bytesProgress) {
-        mBytesProgress = bytesProgress;
-    }
-
-    public boolean isError() {
-        return getStatus().isError();
-    }
-
-    public boolean canAutoAccept(int maxSize) {
-        return maxSize == UNLIMITED_SIZE || getTotalSize() <= maxSize;
-    }
-
-    public String getFileId() {
-        return mFileId;
-    }
-
-    public void setDaemonPath(File file) {
-        mDaemonPath = file;
-    }
-
-    public File getDaemonPath() {
-        return mDaemonPath;
-    }
-
-    public File getPublicPath() {
-        if (mDaemonPath == null) {
-            return  null;
-        }
-        try {
-            return mDaemonPath.getCanonicalFile();
-        } catch (IOException e) {
-            return null;
-        }
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.kt b/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.kt
new file mode 100644
index 000000000..7cefd83ea
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.kt
@@ -0,0 +1,176 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *          Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import net.jami.utils.HashUtils
+import net.jami.utils.StringUtils
+import java.io.File
+import java.io.IOException
+import java.lang.Exception
+
+class DataTransfer : Interaction {
+    var totalSize: Long = 0
+        private set
+    var bytesProgress: Long = 0
+
+    //private final String mPeerId;
+    private var mExtension: String? = null
+    var fileId: String? = null
+        private set
+    var destination: File? = null
+    var daemonPath: File? = null
+
+    /* Legacy constructor */
+    constructor(
+        conversation: ConversationHistory?,
+        peer: String?,
+        account: String?,
+        displayName: String,
+        isOutgoing: Boolean,
+        totalSize: Long,
+        bytesProgress: Long,
+        fileId: String?
+    ) {
+        author = if (isOutgoing) null else peer
+        this.account = account
+        this.conversation = conversation
+        this.totalSize = totalSize
+        this.bytesProgress = bytesProgress
+        body = displayName
+        mStatus = InteractionStatus.TRANSFER_CREATED.toString()
+        mType = InteractionType.DATA_TRANSFER.toString()
+        timestamp = System.currentTimeMillis()
+        mIsRead = 1
+        isIncoming = !isOutgoing
+        if (fileId != null) {
+            this.fileId = fileId
+            try {
+                daemonId = fileId.toULong().toLong()
+            } catch (e: Exception) {
+            }
+        }
+    }
+
+    constructor(interaction: Interaction) {
+        id = interaction.id
+        daemonId = interaction.daemonId
+        author = interaction.author
+        conversation = interaction.conversation
+        // mPeerId = interaction.getConversation().getParticipant();
+        body = interaction.body
+        mStatus = interaction.status.toString()
+        mType = interaction.type.toString()
+        timestamp = interaction.timestamp
+        account = interaction.account
+        contact = interaction.contact
+        mIsRead = 1
+        isIncoming = interaction.isIncoming //mAuthor != null;
+    }
+
+    constructor(
+        fileId: String?,
+        accountId: String,
+        peerUri: String,
+        displayName: String,
+        isOutgoing: Boolean,
+        timestamp: Long,
+        totalSize: Long,
+        bytesProgress: Long
+    ) {
+        account = accountId
+        this.fileId = fileId
+        body = displayName
+        author = peerUri
+        isIncoming = !isOutgoing
+        this.totalSize = totalSize
+        this.bytesProgress = bytesProgress
+        this.timestamp = timestamp
+        mType = InteractionType.DATA_TRANSFER.toString()
+    }
+
+    val extension: String?
+        get() {
+            if (body == null) return null
+            if (mExtension == null) mExtension = StringUtils.getFileExtension(body!!).lowercase()
+            return mExtension
+        }
+    val isPicture: Boolean
+        get() = IMAGE_EXTENSIONS.contains(extension)
+    val isAudio: Boolean
+        get() = AUDIO_EXTENSIONS.contains(extension)
+    val isVideo: Boolean
+        get() = VIDEO_EXTENSIONS.contains(extension)
+    val isComplete: Boolean
+        get() = conversationId == null && isOutgoing || InteractionStatus.TRANSFER_FINISHED.toString() == mStatus
+
+    fun showPicture(): Boolean {
+        return isPicture && isComplete
+    }
+
+    val storagePath: String
+        get() {
+            val b = body
+            return if (b == null) {
+                if (StringUtils.isEmpty(fileId)) { "Error" } else fileId!!
+            } else {
+                var ext = StringUtils.getFileExtension(b)
+                if (ext.length > 8) ext = ext.substring(0, 8)
+                val dId = daemonId
+                if (dId == null || dId == 0L) {
+                    id.toLong().toString() + '_' + HashUtils.sha1(b) + '.' + ext
+                } else {
+                    dId.toString() + '_' + HashUtils.sha1(b) + '.' + ext
+                }
+            }
+        }
+
+    fun setSize(size: Long) {
+        totalSize = size
+    }
+
+    val displayName: String
+        get() = body!!
+    val isOutgoing: Boolean
+        get() = !isIncoming
+    val isError: Boolean
+        get() = status.isError
+
+    fun canAutoAccept(maxSize: Int): Boolean {
+        return maxSize == UNLIMITED_SIZE || totalSize <= maxSize
+    }
+
+    val publicPath: File?
+        get() = if (daemonPath == null) {
+            null
+        } else try {
+            daemonPath!!.canonicalFile
+        } catch (e: IOException) {
+            null
+        }
+
+    companion object {
+        private val IMAGE_EXTENSIONS = setOf("jpg", "jpeg", "png", "gif")
+        private val AUDIO_EXTENSIONS = setOf("ogg", "mp3", "aac", "flac", "m4a")
+        private val VIDEO_EXTENSIONS = setOf("webm", "mp4", "mkv")
+        private const val MAX_SIZE = 32 * 1024 * 1024
+        private const val UNLIMITED_SIZE = 256 * 1024 * 1024
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java b/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java
deleted file mode 100644
index ddfa85cd5..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.j256.ormlite.field.DatabaseField;
-import com.j256.ormlite.table.DatabaseTable;
-
-import java.util.List;
-
-@DatabaseTable(tableName = Interaction.TABLE_NAME)
-public class Interaction {
-
-    public static final String TABLE_NAME = "interactions";
-    public static final String COLUMN_ID = "id";
-    public static final String COLUMN_AUTHOR = "author";
-    public static final String COLUMN_CONVERSATION = "conversation";
-    public static final String COLUMN_TIMESTAMP = "timestamp";
-    public static final String COLUMN_BODY = "body";
-    public static final String COLUMN_TYPE = "type";
-    public static final String COLUMN_STATUS = "status";
-    public static final String COLUMN_DAEMON_ID = "daemon_id";
-    public static final String COLUMN_IS_READ = "is_read";
-    public static final String COLUMN_EXTRA_FLAG = "extra_data";
-    protected String mAccount;
-    boolean mIsIncoming;
-    Contact mContact = null;
-
-    @DatabaseField(generatedId = true, columnName = COLUMN_ID, index = true)
-    int mId;
-    @DatabaseField(columnName = COLUMN_AUTHOR, index = true)
-    String mAuthor;
-    @DatabaseField(columnName = COLUMN_CONVERSATION, foreignColumnName = ConversationHistory.COLUMN_CONVERSATION_ID, foreign = true)
-    ConversationHistory mConversation;
-    @DatabaseField(columnName = COLUMN_TIMESTAMP, index = true)
-    long mTimestamp;
-    @DatabaseField(columnName = COLUMN_BODY)
-    String mBody;
-    @DatabaseField(columnName = COLUMN_TYPE)
-    String mType;
-    @DatabaseField(columnName = COLUMN_STATUS)
-    String mStatus = InteractionStatus.UNKNOWN.toString();
-    @DatabaseField(columnName = COLUMN_DAEMON_ID)
-    Long mDaemonId = null;
-    @DatabaseField(columnName = COLUMN_IS_READ)
-    int mIsRead = 0;
-    @DatabaseField(columnName = COLUMN_EXTRA_FLAG)
-    String mExtraFlag = new JsonObject().toString();
-
-    // Swarm
-    private String mConversationId = null;
-    private String mMessageId = null;
-    private List<String> mParentIds = null;
-
-    /* Needed by ORMLite */
-    public Interaction() {
-    }
-    public Interaction(String accountId) {
-        mAccount = accountId;
-        setType(InteractionType.INVALID);
-    }
-
-    public Interaction(Conversation conversation, InteractionType type) {
-        mConversation = conversation;
-        mAccount = conversation.getAccountId();
-        mType = type.toString();
-    }
-
-    public Interaction(String id, String author, ConversationHistory conversation, String timestamp, String body, String type, String status, String daemonId, String isRead, String extraFlag) {
-        mId = Integer.parseInt(id);
-        mAuthor = author;
-        mConversation = conversation;
-        mTimestamp = Long.parseLong(timestamp);
-        mBody = body;
-        mType = type;
-        mStatus = status;
-        try {
-            mDaemonId = daemonId == null ? null : Long.parseLong(daemonId);
-        }
-        catch (NumberFormatException e) {
-            mDaemonId = 0L;
-        }
-        mIsRead = Integer.parseInt(isRead);
-        mExtraFlag = extraFlag;
-    }
-
-    static int compare(Interaction a, Interaction b) {
-        if (a == null)
-            return b == null ? 0 : -1;
-        if (b == null) return 1;
-        return Long.compare(a.getTimestamp(), b.getTimestamp());
-    }
-
-    public String getAccount() {
-        return mAccount;
-    }
-
-    public void setAccount(String account) {
-        mAccount = account;
-    }
-
-    public int getId() {
-        return mId;
-    }
-
-    public void read() {
-        mIsRead = 1;
-    }
-
-    public String getAuthor() {
-        return mAuthor;
-    }
-
-    public void setAuthor(String author) {
-        mAuthor = author;
-    }
-
-    public ConversationHistory getConversation() {
-        return mConversation;
-    }
-
-    public void setConversation(ConversationHistory conversation) {
-        mConversation = conversation;
-    }
-
-    public Long getTimestamp() {
-        return mTimestamp;
-    }
-
-    public String getBody() {
-        return mBody;
-    }
-
-    public InteractionType getType() {
-        return InteractionType.fromString(mType);
-    }
-
-    public void setType(InteractionType type) {
-        mType = type.toString();
-    }
-
-    public InteractionStatus getStatus() {
-        return InteractionStatus.fromString(mStatus);
-    }
-
-    public void setStatus(InteractionStatus status) {
-        if (status == InteractionStatus.DISPLAYED)
-            mIsRead = 1;
-        mStatus = status.toString();
-    }
-
-    JsonObject getExtraFlag() {
-        return toJson(mExtraFlag);
-    }
-
-    JsonObject toJson(String value) {
-        return JsonParser.parseString(value).getAsJsonObject();
-    }
-
-    String fromJson(JsonObject json) {
-        return json.toString();
-    }
-
-    public Long getDaemonId() {
-        return mDaemonId;
-    }
-
-    public String getDaemonIdString() {
-        return mDaemonId == null ? null : Long.toString(mDaemonId);
-    }
-
-    public void setDaemonId(long daemonId) {
-        mDaemonId = daemonId;
-    }
-
-    public String getMessageId() {
-        return mMessageId;
-    }
-
-    public String getConversationId() {
-        return mConversationId;
-    }
-
-    public List<String> getParentIds() {
-        return mParentIds;
-    }
-
-    public boolean isIncoming() {
-        return mIsIncoming;
-    }
-
-    public boolean isRead() {
-        return mIsRead == 1;
-    }
-
-    public Contact getContact() {
-        return mContact;
-    }
-
-    public void setContact(Contact contact) {
-        mContact = contact;
-    }
-
-    public void setSwarmInfo(String conversationId) {
-        mConversationId = conversationId;
-        mMessageId = null;
-        mParentIds = null;
-    }
-    public void setSwarmInfo(String conversationId, String messageId, List<String> parents) {
-        mConversationId = conversationId;
-        mMessageId = messageId;
-        mParentIds = parents;
-    }
-
-    public enum InteractionStatus {
-        UNKNOWN, SENDING, SUCCESS, DISPLAYED, INVALID, FAILURE,
-
-        TRANSFER_CREATED,
-        TRANSFER_ACCEPTED,
-        TRANSFER_CANCELED,
-        TRANSFER_ERROR,
-        TRANSFER_UNJOINABLE_PEER,
-        TRANSFER_ONGOING,
-        TRANSFER_AWAITING_PEER,
-        TRANSFER_AWAITING_HOST,
-        TRANSFER_TIMEOUT_EXPIRED,
-        TRANSFER_FINISHED,
-        FILE_AVAILABLE;
-
-        static InteractionStatus fromString(String str) {
-            for (InteractionStatus s : values()) {
-                if (s.name().equals(str)) {
-                    return s;
-                }
-            }
-            return INVALID;
-        }
-
-        public static InteractionStatus fromIntTextMessage(int n) {
-            try {
-                return values()[n];
-            } catch (ArrayIndexOutOfBoundsException e) {
-                return INVALID;
-            }
-        }
-
-        public static InteractionStatus fromIntFile(int n) {
-            switch (n) {
-                case 0:
-                    return INVALID;
-                case 1:
-                    return TRANSFER_CREATED;
-                case 2:
-                case 9:
-                    return TRANSFER_ERROR;
-                case 3:
-                    return TRANSFER_AWAITING_PEER;
-                case 4:
-                    return TRANSFER_AWAITING_HOST;
-                case 5:
-                    return TRANSFER_ONGOING;
-                case 6:
-                    return TRANSFER_FINISHED;
-                case 7:
-                case 8:
-                case 10:
-                    return TRANSFER_UNJOINABLE_PEER;
-                case 11:
-                    return TRANSFER_TIMEOUT_EXPIRED;
-                default:
-                    return UNKNOWN;
-            }
-        }
-
-        public boolean isError() {
-            return this == TRANSFER_ERROR || this == TRANSFER_UNJOINABLE_PEER || this == TRANSFER_CANCELED || this == TRANSFER_TIMEOUT_EXPIRED || this == FAILURE;
-        }
-
-        public boolean isOver() {
-            return isError() || this == TRANSFER_FINISHED;
-        }
-
-    }
-
-    public enum InteractionType {
-        INVALID,
-        TEXT,
-        CALL,
-        CONTACT,
-        DATA_TRANSFER;
-
-        static InteractionType fromString(String str) {
-            for (InteractionType type : values()) {
-                if (type.name().equals(str)) {
-                    return type;
-                }
-            }
-            return INVALID;
-        }
-
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Interaction.kt b/ring-android/libringclient/src/main/java/net/jami/model/Interaction.kt
new file mode 100644
index 000000000..a7d36eb20
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Interaction.kt
@@ -0,0 +1,232 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.j256.ormlite.field.DatabaseField
+import com.j256.ormlite.table.DatabaseTable
+
+@DatabaseTable(tableName = Interaction.TABLE_NAME)
+open class Interaction {
+    var account: String? = null
+    var isIncoming = false
+    var contact: Contact? = null
+
+    @DatabaseField(generatedId = true, columnName = COLUMN_ID, index = true)
+    var id = 0
+
+    @DatabaseField(columnName = COLUMN_AUTHOR, index = true)
+    var author: String? = null
+
+    @DatabaseField(columnName = COLUMN_CONVERSATION, foreignColumnName = ConversationHistory.COLUMN_CONVERSATION_ID, foreign = true)
+    var conversation: ConversationHistory? = null
+
+    @DatabaseField(columnName = COLUMN_TIMESTAMP, index = true)
+    var timestamp: Long = 0
+
+    @DatabaseField(columnName = COLUMN_BODY)
+    var body: String? = null
+
+    @DatabaseField(columnName = COLUMN_TYPE)
+    var mType: String? = null
+
+    @DatabaseField(columnName = COLUMN_STATUS)
+    var mStatus = InteractionStatus.UNKNOWN.toString()
+
+    @DatabaseField(columnName = COLUMN_DAEMON_ID)
+    var daemonId: Long? = null
+
+    @DatabaseField(columnName = COLUMN_IS_READ)
+    var mIsRead = 0
+
+    @DatabaseField(columnName = COLUMN_EXTRA_FLAG)
+    var mExtraFlag = JsonObject().toString()
+
+    // Swarm
+    var conversationId: String? = null
+        private set
+    var messageId: String? = null
+        private set
+    var parentId: String? = null
+        private set
+
+    /* Needed by ORMLite */
+    constructor()
+    constructor(accountId: String) {
+        account = accountId
+        type = InteractionType.INVALID
+    }
+
+    constructor(conversation: Conversation, type: InteractionType) {
+        this.conversation = conversation
+        account = conversation.accountId
+        mType = type.toString()
+    }
+
+    constructor(
+        id: String,
+        author: String?,
+        conversation: ConversationHistory?,
+        timestamp: String,
+        body: String?,
+        type: String?,
+        status: String,
+        daemonId: String?,
+        isRead: String,
+        extraFlag: String
+    ) {
+        this.id = id.toInt()
+        this.author = author
+        this.conversation = conversation
+        this.timestamp = timestamp.toLong()
+        this.body = body
+        mType = type
+        mStatus = status
+        try {
+            this.daemonId = daemonId?.toLong()
+        } catch (e: NumberFormatException) {
+            this.daemonId = 0L
+        }
+        mIsRead = isRead.toInt()
+        mExtraFlag = extraFlag
+    }
+
+    fun read() {
+        mIsRead = 1
+    }
+
+    var type: InteractionType
+        get() = InteractionType.fromString(mType)
+        set(type) {
+            mType = type.toString()
+        }
+    var status: InteractionStatus
+        get() = InteractionStatus.fromString(mStatus)
+        set(status) {
+            if (status == InteractionStatus.DISPLAYED) mIsRead = 1
+            mStatus = status.toString()
+        }
+    val extraFlag: JsonObject
+        get() = toJson(mExtraFlag)
+
+    fun toJson(value: String?): JsonObject {
+        return JsonParser.parseString(value).asJsonObject
+    }
+
+    fun fromJson(json: JsonObject): String {
+        return json.toString()
+    }
+
+    open val daemonIdString: String?
+        get() = daemonId?.toString()
+
+    val isRead: Boolean
+        get() = mIsRead == 1
+
+    fun setSwarmInfo(conversationId: String) {
+        this.conversationId = conversationId
+        messageId = null
+        parentId = null
+    }
+
+    fun setSwarmInfo(conversationId: String, messageId: String, parent: String?) {
+        this.conversationId = conversationId
+        this.messageId = messageId
+        parentId = parent
+    }
+
+    enum class InteractionStatus {
+        UNKNOWN, SENDING, SUCCESS, DISPLAYED, INVALID, FAILURE, TRANSFER_CREATED, TRANSFER_ACCEPTED, TRANSFER_CANCELED, TRANSFER_ERROR, TRANSFER_UNJOINABLE_PEER, TRANSFER_ONGOING, TRANSFER_AWAITING_PEER, TRANSFER_AWAITING_HOST, TRANSFER_TIMEOUT_EXPIRED, TRANSFER_FINISHED, FILE_AVAILABLE;
+
+        val isError: Boolean
+            get() = this == TRANSFER_ERROR || this == TRANSFER_UNJOINABLE_PEER || this == TRANSFER_CANCELED || this == TRANSFER_TIMEOUT_EXPIRED || this == FAILURE
+        val isOver: Boolean
+            get() = isError || this == TRANSFER_FINISHED
+
+        companion object {
+            fun fromString(str: String): InteractionStatus {
+                for (s in values()) {
+                    if (s.name == str) {
+                        return s
+                    }
+                }
+                return INVALID
+            }
+
+            fun fromIntTextMessage(n: Int): InteractionStatus {
+                return try {
+                    values()[n]
+                } catch (e: ArrayIndexOutOfBoundsException) {
+                    INVALID
+                }
+            }
+
+            fun fromIntFile(n: Int): InteractionStatus {
+                return when (n) {
+                    0 -> INVALID
+                    1 -> TRANSFER_CREATED
+                    2, 9 -> TRANSFER_ERROR
+                    3 -> TRANSFER_AWAITING_PEER
+                    4 -> TRANSFER_AWAITING_HOST
+                    5 -> TRANSFER_ONGOING
+                    6 -> TRANSFER_FINISHED
+                    7, 8, 10 -> TRANSFER_UNJOINABLE_PEER
+                    11 -> TRANSFER_TIMEOUT_EXPIRED
+                    else -> UNKNOWN
+                }
+            }
+        }
+    }
+
+    enum class InteractionType {
+        INVALID, TEXT, CALL, CONTACT, DATA_TRANSFER;
+
+        companion object {
+            fun fromString(str: String?): InteractionType {
+                for (type in values()) {
+                    if (type.name == str) {
+                        return type
+                    }
+                }
+                return INVALID
+            }
+        }
+    }
+
+    companion object {
+        const val TABLE_NAME = "interactions"
+        const val COLUMN_ID = "id"
+        const val COLUMN_AUTHOR = "author"
+        const val COLUMN_CONVERSATION = "conversation"
+        const val COLUMN_TIMESTAMP = "timestamp"
+        const val COLUMN_BODY = "body"
+        const val COLUMN_TYPE = "type"
+        const val COLUMN_STATUS = "status"
+        const val COLUMN_DAEMON_ID = "daemon_id"
+        const val COLUMN_IS_READ = "is_read"
+        const val COLUMN_EXTRA_FLAG = "extra_data"
+
+        fun compare(a: Interaction?, b: Interaction?): Int {
+            if (a == null) return if (b == null) 0 else -1
+            return if (b == null) 1 else java.lang.Long.compare(a.timestamp, b.timestamp)
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.java b/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.java
deleted file mode 100644
index e9a4d87cd..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-public class TextMessage extends Interaction {
-
-    private boolean mNotified;
-
-    public TextMessage(String author, String account, String daemonId, ConversationHistory conversation, String message) {
-        mAuthor = author;
-        mAccount = account;
-        if (daemonId != null) {
-            try {
-                mDaemonId = Long.parseLong(daemonId);
-            } catch (NumberFormatException e) {
-                try {
-                    mDaemonId = Long.parseLong(daemonId, 16);
-                } catch (NumberFormatException e2) {
-                     mDaemonId = 0L;
-                }
-            }
-        }
-        mTimestamp = System.currentTimeMillis();
-        mType = InteractionType.TEXT.toString();
-        mConversation = conversation;
-        mIsIncoming = author != null;
-        mBody = message;
-    }
-
-    public TextMessage(String author, String account, long timestamp, ConversationHistory conversation, String message, boolean isIncoming) {
-        mAuthor = author;
-        mAccount = account;
-        mTimestamp = timestamp;
-        mType = InteractionType.TEXT.toString();
-        mConversation = conversation;
-        mIsIncoming = isIncoming;
-        mBody = message;
-    }
-
-    public TextMessage(Interaction interaction) {
-        mId = interaction.getId();
-        mAuthor = interaction.getAuthor();
-        mTimestamp = interaction.getTimestamp();
-        mType = interaction.getType().toString();
-        mStatus = interaction.getStatus().toString();
-        mConversation = interaction.getConversation();
-        mIsIncoming = mAuthor != null;
-        mDaemonId = interaction.getDaemonId();
-        mBody = interaction.getBody();
-        mIsRead = interaction.isRead() ? 1 : 0;
-        mAccount = interaction.getAccount();
-        mContact = interaction.getContact();
-    }
-
-    public boolean isNotified() {
-        return mNotified;
-    }
-
-    public void setNotified(boolean notified) {
-        mNotified = notified;
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.kt b/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.kt
new file mode 100644
index 000000000..2eb1f4f87
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/TextMessage.kt
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import java.lang.NumberFormatException
+
+class TextMessage : Interaction {
+    var isNotified = false
+
+    constructor(author: String?, account: String, daemonId: String?, conversation: ConversationHistory?, message: String) {
+        this.author = author
+        this.account = account
+        if (daemonId != null) {
+            try {
+                this.daemonId = daemonId.toLong()
+            } catch (e: NumberFormatException) {
+                try {
+                    this.daemonId = daemonId.toLong(16)
+                } catch (e2: NumberFormatException) {
+                    this.daemonId = 0L
+                }
+            }
+        }
+        timestamp = System.currentTimeMillis()
+        mType = InteractionType.TEXT.toString()
+        this.conversation = conversation
+        isIncoming = author != null
+        body = message
+    }
+
+    constructor(author: String?, account: String, timestamp: Long, conversation: ConversationHistory?, message: String, isIncoming: Boolean) {
+        this.author = author
+        this.account = account
+        this.timestamp = timestamp
+        mType = InteractionType.TEXT.toString()
+        this.conversation = conversation
+        this.isIncoming = isIncoming
+        body = message
+    }
+
+    constructor(interaction: Interaction) {
+        id = interaction.id
+        author = interaction.author
+        timestamp = interaction.timestamp
+        mType = interaction.type.toString()
+        mStatus = interaction.status.toString()
+        conversation = interaction.conversation
+        isIncoming = author != null
+        daemonId = interaction.daemonId
+        body = interaction.body
+        mIsRead = if (interaction.isRead) 1 else 0
+        account = interaction.account
+        contact = interaction.contact
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.java b/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.java
deleted file mode 100644
index 0c807adf8..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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 <http://www.gnu.org/licenses/>.
- */
-
-package net.jami.model;
-
-import net.jami.utils.StringUtils;
-
-import java.util.Map;
-
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-
-public class TrustRequest {
-    private static final String TAG = TrustRequest.class.getSimpleName();
-
-    private final String mAccountId;
-    private String mContactUsername = null;
-    private final Uri mRequestUri;
-    private String mConversationId;
-    private VCard mVcard;
-    private String mMessage;
-    private final long mTimestamp;
-    private boolean mUsernameResolved = false;
-
-    public TrustRequest(String accountId, Uri uri, long received, String payload, String conversationId) {
-        mAccountId = accountId;
-        mRequestUri = uri;
-        mConversationId = StringUtils.isEmpty(conversationId) ? null : conversationId;
-        mTimestamp = received;
-        mVcard = Ezvcard.parse(payload).first();
-        mMessage = null;
-    }
-
-    public TrustRequest(String accountId, Map<String, String> info) {
-        this(accountId, Uri.fromId(info.get("from")), Long.decode(info.get("received")) * 1000L, info.get("payload"), info.get("conversationId"));
-    }
-
-    public TrustRequest(String accountId, Uri contactUri, String conversationId) {
-        mAccountId = accountId;
-        mRequestUri = contactUri;
-        mConversationId = conversationId;
-        mTimestamp = 0;
-    }
-
-    public String getAccountId() {
-        return mAccountId;
-    }
-
-    public Uri getUri() {
-        return mRequestUri;
-    }
-
-    public String getConversationId() {
-        return mConversationId;
-    }
-
-    public String getFullname() {
-        String fullname = "";
-        if (mVcard != null && mVcard.getFormattedName() != null) {
-            fullname = mVcard.getFormattedName().getValue();
-        }
-        return fullname;
-    }
-
-    public String getDisplayname() {
-        boolean hasUsername = mContactUsername != null && !mContactUsername.isEmpty();
-        return hasUsername ? mContactUsername : mRequestUri.toString();
-    }
-
-    public boolean isNameResolved() {
-        return mUsernameResolved;
-    }
-
-    public void setUsername(String username) {
-        mContactUsername = username;
-        mUsernameResolved = true;
-    }
-
-    public long getTimestamp() {
-        return mTimestamp;
-    }
-
-    public VCard getVCard() {
-        return mVcard;
-    }
-
-    public void setVCard(VCard vcard) {
-        mVcard = vcard;
-    }
-
-    public String getMessage() {
-        return mMessage;
-    }
-
-    public void setMessage(String message) {
-        mMessage = message;
-    }
-
-    public void setConversationId(String conversationId) {
-        mConversationId = conversationId;
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.kt b/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.kt
new file mode 100644
index 000000000..d49b25d6c
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/TrustRequest.kt
@@ -0,0 +1,78 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+package net.jami.model
+
+import ezvcard.Ezvcard
+import ezvcard.VCard
+import net.jami.utils.StringUtils
+
+class TrustRequest {
+    val accountId: String
+    private var mContactUsername: String? = null
+    val uri: Uri
+    var conversationId: String?
+    var vCard: VCard? = null
+    var message: String? = null
+    val timestamp: Long
+    var isNameResolved = false
+        private set
+
+    constructor(accountId: String, uri: Uri, received: Long, payload: String?, conversationId: String?) {
+        this.accountId = accountId
+        this.uri = uri
+        this.conversationId = if (StringUtils.isEmpty(conversationId)) null else conversationId
+        timestamp = received
+        vCard = if (payload == null) null else Ezvcard.parse(payload).first()
+        message = null
+    }
+
+    constructor(accountId: String, info: Map<String, String>) : this(accountId, Uri.fromId(info["from"]!!),
+        java.lang.Long.decode(info["received"]) * 1000L, info["payload"], info["conversationId"])
+
+    constructor(accountId: String, contactUri: Uri, conversationId: String?) {
+        this.accountId = accountId
+        uri = contactUri
+        this.conversationId = conversationId
+        timestamp = 0
+    }
+
+    val fullname: String
+        get() {
+            var fullname = ""
+            if (vCard != null && vCard!!.formattedName != null) {
+                fullname = vCard!!.formattedName.value
+            }
+            return fullname
+        }
+    val displayname: String
+        get() {
+            val username = mContactUsername
+            return if (username != null && username.isNotEmpty()) username else uri.toString()
+        }
+
+    fun setUsername(username: String?) {
+        mContactUsername = username
+        isNameResolved = true
+    }
+
+    companion object {
+        private val TAG = TrustRequest::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Uri.java b/ring-android/libringclient/src/main/java/net/jami/model/Uri.java
deleted file mode 100644
index 87babe7be..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/model/Uri.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.model;
-
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-
-import java.io.Serializable;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Uri implements Serializable {
-
-    private final String mScheme;
-    private final String mUsername;
-    private final String mHost;
-    private final String mPort;
-
-    private static final Pattern ANGLE_BRACKETS_PATTERN = Pattern.compile("^\\s*([^<>]+)?\\s*<([^<>]+)>\\s*$");
-    private static final Pattern HEX_ID_PATTERN = Pattern.compile("^\\p{XDigit}{40}$", Pattern.CASE_INSENSITIVE);
-    private static final Pattern RING_URI_PATTERN = Pattern.compile("^\\s*(?:ring(?:[\\s\\:]+))?(\\p{XDigit}{40})(?:@ring\\.dht)?\\s*$", Pattern.CASE_INSENSITIVE);
-    private static final Pattern URI_PATTERN = Pattern.compile("^\\s*(\\w+:)?(?:([\\w.]+)@)?(?:([\\d\\w\\.\\-]+)(?::(\\d+))?)\\s*$", Pattern.CASE_INSENSITIVE);
-    public static final String RING_URI_SCHEME = "ring:";
-    public static final String JAMI_URI_SCHEME = "jami:";
-    public static final String SWARM_SCHEME = "swarm:";
-
-    private static final String ipv4Pattern = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])";
-    private static final String ipv6Pattern = "([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}";
-    private static final Pattern VALID_IPV4_PATTERN = Pattern.compile(ipv4Pattern, Pattern.CASE_INSENSITIVE);
-    private static final Pattern VALID_IPV6_PATTERN = Pattern.compile(ipv6Pattern, Pattern.CASE_INSENSITIVE);
-
-    public Uri(String scheme, String user, String host, String port) {
-        mScheme = scheme;
-        mUsername = user;
-        mHost = host;
-        mPort = port;
-    }
-
-    public Uri(String scheme, String host) {
-        mScheme = scheme;
-        mUsername = null;
-        mHost = host;
-        mPort = null;
-    }
-
-    static public Uri fromString(String uri) {
-        Matcher m = URI_PATTERN.matcher(uri);
-        if (m.find()) {
-            return new Uri(m.group(1), m.group(2), m.group(3), m.group(4));
-        } else {
-            return new Uri(null, null, uri, null);
-        }
-    }
-
-    static public net.jami.utils.Tuple<Uri, String> fromStringWithName(String uriString) {
-        Matcher m = ANGLE_BRACKETS_PATTERN.matcher(uriString);
-        if (m.find()) {
-            return new Tuple<>(fromString(m.group(2)), m.group(1));
-        } else {
-            return new Tuple<>(fromString(uriString), null);
-        }
-    }
-
-    public static Uri fromId(String conversationId) {
-        return new Uri(null, null, conversationId, null);
-    }
-
-    public String getRawRingId() {
-        if (getUsername() != null) {
-            return getUsername();
-        } else {
-            return getHost();
-        }
-    }
-
-    public String getUri() {
-        if (isSwarm())
-            return getScheme() + getRawRingId();
-        if (isHexId())
-            return getRawRingId();
-        return toString();
-    }
-
-    public String getRawUriString() {
-        if (isSwarm())
-            return getScheme() + getRawRingId();
-        if (isHexId()) {
-            return RING_URI_SCHEME + getRawRingId();
-        }
-        return toString();
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder(64);
-        if (!net.jami.utils.StringUtils.isEmpty(mScheme)) {
-            builder.append(mScheme);
-        }
-        if (!net.jami.utils.StringUtils.isEmpty(mUsername)) {
-            builder.append(mUsername).append('@');
-        }
-        if (!net.jami.utils.StringUtils.isEmpty(mHost)) {
-            builder.append(mHost);
-        }
-        if (!net.jami.utils.StringUtils.isEmpty(mPort)) {
-            builder.append(':').append(mPort);
-        }
-        return builder.toString();
-    }
-
-    public boolean isSingleIp() {
-        return (getUsername() == null || getUsername().isEmpty()) && isIpAddress(getHost());
-    }
-
-    public boolean isHexId() {
-        return (getHost() != null && HEX_ID_PATTERN.matcher(getHost()).find())
-                || (getUsername() != null && HEX_ID_PATTERN.matcher(getUsername()).find());
-    }
-    public boolean isSwarm() {
-        return SWARM_SCHEME.equals(getScheme());
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof Uri)) {
-            return false;
-        }
-        Uri uo = (Uri) o;
-        return Objects.equals(getUsername(), uo.getUsername())
-                && Objects.equals(getHost(), uo.getHost());
-    }
-
-    public boolean isEmpty() {
-        return net.jami.utils.StringUtils.isEmpty(getUsername()) && StringUtils.isEmpty(getHost());
-    }
-
-    /**
-     * Determine if the given string is a valid IPv4 or IPv6 address.  This method
-     * uses pattern matching to see if the given string could be a valid IP address.
-     *
-     * @param ipAddress A string that is to be examined to verify whether or not
-     *                  it could be a valid IP address.
-     * @return <code>true</code> if the string is a value that is a valid IP address,
-     * <code>false</code> otherwise.
-     */
-    public static boolean isIpAddress(String ipAddress) {
-        Matcher m1 = VALID_IPV4_PATTERN.matcher(ipAddress);
-        if (m1.matches()) {
-            return true;
-        }
-        Matcher m2 = VALID_IPV6_PATTERN.matcher(ipAddress);
-        return m2.matches();
-    }
-
-    public String getScheme() {
-        return mScheme;
-    }
-
-    public String getUsername() {
-        return mUsername;
-    }
-
-    public String getHost() {
-        return mHost;
-    }
-
-    public String getPort() {
-        return mPort;
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt b/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt
new file mode 100644
index 000000000..6af5d1303
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt
@@ -0,0 +1,167 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.model
+
+import net.jami.utils.StringUtils
+import net.jami.utils.Tuple
+import java.io.Serializable
+import java.lang.StringBuilder
+import java.util.regex.Pattern
+
+class Uri : Serializable {
+    val scheme: String?
+    val username: String?
+    private val mHost: String
+    val port: String?
+
+    constructor() {
+        scheme = null
+        username = null
+        mHost = ""
+        port = null
+    }
+    constructor(scheme: String?, user: String?, host: String, port: String?) {
+        this.scheme = scheme
+        username = user
+        mHost = host
+        this.port = port
+    }
+
+    constructor(scheme: String?, host: String) {
+        this.scheme = scheme
+        username = null
+        mHost = host
+        port = null
+    }
+
+    val rawRingId: String
+        get() = username ?: host
+
+    val uri: String
+        get() {
+            if (isSwarm) return scheme + rawRingId
+            return if (isHexId) rawRingId else toString()
+        }
+    val rawUriString: String
+        get() {
+            if (isSwarm) return scheme + rawRingId
+            return if (isHexId) {
+                RING_URI_SCHEME + rawRingId
+            } else toString()
+        }
+
+    override fun toString(): String {
+        val builder = StringBuilder(64)
+        if (!StringUtils.isEmpty(scheme)) {
+            builder.append(scheme)
+        }
+        if (!StringUtils.isEmpty(username)) {
+            builder.append(username).append('@')
+        }
+        if (!StringUtils.isEmpty(mHost)) {
+            builder.append(mHost)
+        }
+        if (!StringUtils.isEmpty(port)) {
+            builder.append(':').append(port)
+        }
+        return builder.toString()
+    }
+
+    val isSingleIp: Boolean
+        get() = (username == null || username.isEmpty()) && isIpAddress(host)
+    val isHexId: Boolean
+        get() = HEX_ID_PATTERN.matcher(host).find() || username != null && HEX_ID_PATTERN.matcher(username).find()
+    val isSwarm: Boolean
+        get() = SWARM_SCHEME == scheme
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        if (other !is Uri) {
+            return false
+        }
+        return (username == other.username
+                && host == other.host)
+    }
+
+    val isEmpty: Boolean
+        get() = StringUtils.isEmpty(username) && StringUtils.isEmpty(host)
+    val host: String
+        get() = mHost
+
+    companion object {
+        private val ANGLE_BRACKETS_PATTERN = Pattern.compile("^\\s*([^<>]+)?\\s*<([^<>]+)>\\s*$")
+        private val HEX_ID_PATTERN = Pattern.compile("^\\p{XDigit}{40}$", Pattern.CASE_INSENSITIVE)
+        private val RING_URI_PATTERN = Pattern.compile("^\\s*(?:ring(?:[\\s:]+))?(\\p{XDigit}{40})(?:@ring\\.dht)?\\s*$", Pattern.CASE_INSENSITIVE)
+        private val URI_PATTERN = Pattern.compile("^\\s*(\\w+:)?(?:([\\w.]+)@)?(?:([\\d\\w.\\-]+)(?::(\\d+))?)\\s*$", Pattern.CASE_INSENSITIVE)
+        const val RING_URI_SCHEME = "ring:"
+        const val JAMI_URI_SCHEME = "jami:"
+        const val SWARM_SCHEME = "swarm:"
+        private const val ipv4Pattern = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])"
+        private const val ipv6Pattern = "([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}"
+        private val VALID_IPV4_PATTERN = Pattern.compile(ipv4Pattern, Pattern.CASE_INSENSITIVE)
+        private val VALID_IPV6_PATTERN = Pattern.compile(ipv6Pattern, Pattern.CASE_INSENSITIVE)
+
+        @JvmStatic
+        fun fromString(uri: String): Uri {
+            val m = URI_PATTERN.matcher(uri)
+            return if (m.find()) {
+                Uri(m.group(1), m.group(2), m.group(3), m.group(4))
+            } else {
+                Uri(null, null, uri, null)
+            }
+        }
+
+        @JvmStatic
+        fun fromStringWithName(uriString: String): Tuple<Uri, String?> {
+            val m = ANGLE_BRACKETS_PATTERN.matcher(uriString)
+            return if (m.find()) {
+                Tuple(fromString(m.group(2)), m.group(1))
+            } else {
+                Tuple(fromString(uriString), null)
+            }
+        }
+
+        @JvmStatic
+        fun fromId(conversationId: String): Uri {
+            return Uri(null, null, conversationId, null)
+        }
+
+        /**
+         * Determine if the given string is a valid IPv4 or IPv6 address.  This method
+         * uses pattern matching to see if the given string could be a valid IP address.
+         *
+         * @param ipAddress A string that is to be examined to verify whether or not
+         * it could be a valid IP address.
+         * @return `true` if the string is a value that is a valid IP address,
+         * `false` otherwise.
+         */
+        @JvmStatic
+        fun isIpAddress(ipAddress: String): Boolean {
+            val m1 = VALID_IPV4_PATTERN.matcher(ipAddress)
+            if (m1.matches()) {
+                return true
+            }
+            val m2 = VALID_IPV6_PATTERN.matcher(ipAddress)
+            return m2.matches()
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.java b/ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.kt
similarity index 89%
rename from ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.java
rename to ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.kt
index f7bc29bb2..d23aa4f2c 100644
--- a/ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.java
+++ b/ring-android/libringclient/src/main/java/net/jami/mvp/GenericView.kt
@@ -17,10 +17,8 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package net.jami.mvp;
+package net.jami.mvp
 
-public interface GenericView<VM> {
-
-    void showViewModel(VM viewModel);
-
-}
+interface GenericView<VM> {
+    fun showViewModel(viewModel: VM)
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java b/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java
index 58b57c7b5..8b75cbe6e 100644
--- a/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java
@@ -27,9 +27,7 @@ public abstract class RootPresenter<T> {
 
     protected CompositeDisposable mCompositeDisposable = new CompositeDisposable();
 
-    public RootPresenter() {
-
-    }
+    public RootPresenter() { }
 
     private WeakReference<T> mView;
 
@@ -40,9 +38,12 @@ public abstract class RootPresenter<T> {
     public void unbindView() {
         if (mView != null) {
             mView.clear();
+            mView = null;
         }
+        mCompositeDisposable.clear();
+    }
 
-        mView = null;
+    public void onDestroy() {
         mCompositeDisposable.dispose();
     }
 
@@ -50,7 +51,6 @@ public abstract class RootPresenter<T> {
         if (mView != null) {
             return mView.get();
         }
-
         return null;
     }
 
diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java
index 8640f713a..92d31e72d 100644
--- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java
@@ -67,12 +67,13 @@ public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> {
     public void bindView(HomeNavigationView view) {
         super.bindView(view);
         mCompositeDisposable.add(mAccountService.getCurrentProfileAccountSubject()
-                .switchMapSingle(account -> account.getAccountAlias().map(alias -> new Tuple<>(account, alias)))
+                .switchMap(account -> account.getLoadedProfileObservable()
+                        .map(alias -> new Tuple<>(account, alias)))
                 .observeOn(mUiScheduler)
                 .subscribe(alias -> {
                     HomeNavigationView v = getView();
                     if (v != null)
-                        v.showViewModel(new HomeNavigationViewModel(alias.first, alias.second));
+                        v.showViewModel(new HomeNavigationViewModel(alias.first, alias.second.first));
                 }, e ->  Log.e(TAG, "Error loading account list !", e)));
         mCompositeDisposable.add(mAccountService.getObservableAccounts()
                 .observeOn(mUiScheduler)
@@ -94,10 +95,8 @@ public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> {
         File filesDir = mDeviceRuntimeService.provideFilesDir();
 
         mCompositeDisposable.add(Single.zip(
-                VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId)
-                        .subscribeOn(Schedulers.io()),
-                photo
-                        .subscribeOn(Schedulers.io()),
+                VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()),
+                photo.subscribeOn(Schedulers.io()),
                 (vcard, pic) -> {
                     vcard.setUid(new Uid(ringId));
                     vcard.removeProperties(Photo.class);
@@ -110,6 +109,7 @@ public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> {
                 .subscribe(vcard -> {
                     account.resetProfile();
                     mAccountService.refreshAccounts();
+                    getView().setPhoto(account);
                 }, e -> Log.e(TAG, "Error saving vCard !", e)));
     }
 
@@ -129,6 +129,7 @@ public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> {
                 .subscribe(vcard -> {
                     account.resetProfile();
                     mAccountService.refreshAccounts();
+                    bindView(getView());
                 }, e -> Log.e(TAG, "Error saving vCard !", e)));
     }
 
diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java
index 1a6daa774..fc87c0309 100644
--- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java
+++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java
@@ -35,4 +35,6 @@ public interface HomeNavigationView {
 
     void askGalleryPermission();
 
+    void setPhoto(Account account);
+
 }
diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt
similarity index 67%
rename from ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.java
rename to ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt
index 49108f62b..6589fd507 100644
--- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.java
+++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt
@@ -18,25 +18,8 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
+package net.jami.navigation
 
-package net.jami.navigation;
+import net.jami.model.Account
 
-import net.jami.model.Account;
-
-public class HomeNavigationViewModel {
-    final private Account mAccount;
-    final private String mAlias;
-
-    public HomeNavigationViewModel(Account account, String alias) {
-        mAccount = account;
-        mAlias = alias;
-    }
-
-    public Account getAccount() {
-        return mAccount;
-    }
-
-    public String getAlias() {
-        return mAlias;
-    }
-}
+class HomeNavigationViewModel(val account: Account, val alias: String?)
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java
deleted file mode 100644
index a5b4d6a4e..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java
+++ /dev/null
@@ -1,1918 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.services;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-import net.jami.daemon.Blob;
-import net.jami.daemon.DataTransferInfo;
-import net.jami.daemon.JamiService;
-import net.jami.daemon.StringMap;
-import net.jami.daemon.UintVect;
-import net.jami.model.Account;
-import net.jami.model.AccountConfig;
-import net.jami.model.Call;
-import net.jami.model.Codec;
-import net.jami.model.ConfigKey;
-import net.jami.model.Contact;
-import net.jami.model.ContactEvent;
-import net.jami.model.Conversation;
-import net.jami.model.DataTransfer;
-import net.jami.model.DataTransferError;
-import net.jami.model.Interaction;
-import net.jami.model.Interaction.InteractionStatus;
-import net.jami.model.TextMessage;
-import net.jami.model.TrustRequest;
-import net.jami.model.Uri;
-import net.jami.smartlist.SmartListViewModel;
-import net.jami.utils.FileUtils;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import net.jami.utils.SwigNativeConverter;
-import net.jami.utils.VCardUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.SocketException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.SingleSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-/**
- * This service handles the accounts
- * - Load and manage the accounts stored in the daemon
- * - Keep a local cache of the accounts
- * - handle the callbacks that are send by the daemon
- */
-public class AccountService {
-
-    private static final String TAG = AccountService.class.getSimpleName();
-
-    private static final int VCARD_CHUNK_SIZE = 1000;
-    private static final long DATA_TRANSFER_REFRESH_PERIOD = 500;
-
-    private static final int PIN_GENERATION_SUCCESS = 0;
-    private static final int PIN_GENERATION_WRONG_PASSWORD = 1;
-    private static final int PIN_GENERATION_NETWORK_ERROR = 2;
-
-    @Inject
-    @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-
-    @Inject
-    HistoryService mHistoryService;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    @Inject
-    VCardService mVCardService;
-
-    private Account mCurrentAccount;
-    private List<Account> mAccountList = new ArrayList<>();
-    private boolean mHasSipAccount;
-    private boolean mHasRingAccount;
-
-    private DataTransfer mStartingTransfer = null;
-
-    private final BehaviorSubject<List<Account>> accountsSubject = BehaviorSubject.create();
-    private final Subject<Account> accountSubject = PublishSubject.create();
-    private final Observable<Account> currentAccountSubject = accountsSubject
-            .filter(l -> !l.isEmpty())
-            .map(l -> l.get(0))
-            .distinctUntilChanged();
-
-    public static class Message {
-        String accountId;
-        String messageId;
-        String callId;
-        String author;
-        Map<String, String> messages;
-    }
-    public static class Location {
-        public enum Type {
-            position,
-            stop
-        }
-        Type type;
-        String accountId;
-        String callId;
-        Uri peer;
-        long time;
-        double latitude;
-        double longitude;
-
-        public Type getType() {
-            return type;
-        }
-
-        public String getAccount() {
-            return accountId;
-        }
-
-        public Uri getPeer() {
-            return peer;
-        }
-
-        public long getDate() {
-            return time;
-        }
-
-        public double getLatitude() {
-            return latitude;
-        }
-
-        public double getLongitude() {
-            return longitude;
-        }
-    }
-
-    private final Subject<Message> incomingMessageSubject = PublishSubject.create();
-    private final Subject<Interaction> incomingSwarmMessageSubject = PublishSubject.create();
-
-    private final Observable<TextMessage> incomingTextMessageSubject = incomingMessageSubject
-            .flatMapMaybe(msg -> {
-                String message = msg.messages.get(CallService.MIME_TEXT_PLAIN);
-                if (message != null) {
-                    return mHistoryService
-                            .incomingMessage(msg.accountId, msg.messageId, msg.author, message)
-                            .toMaybe();
-                }
-                return Maybe.empty();
-            })
-            .share();
-
-    private final Observable<Location> incomingLocationSubject = incomingMessageSubject
-            .flatMapMaybe(msg -> {
-                try {
-                    String loc = msg.messages.get(CallService.MIME_GEOLOCATION);
-                    if (loc == null)
-                        return Maybe.empty();
-
-                    JsonObject obj = JsonParser.parseString(loc).getAsJsonObject();
-                    if (obj.size() < 2)
-                        return Maybe.empty();
-                    Location l = new Location();
-
-                    JsonElement type = obj.get("type");
-                    if (type == null || type.getAsString().equals(Location.Type.position.toString())) {
-                        l.type = Location.Type.position;
-                        l.latitude = obj.get("lat").getAsDouble();
-                        l.longitude = obj.get("long").getAsDouble();
-                    } else if (type.getAsString().equals(Location.Type.stop.toString())) {
-                        l.type = Location.Type.stop;
-                    }
-                    l.time = obj.get("time").getAsLong();
-                    l.accountId = msg.accountId;
-                    l.callId = msg.callId;
-                    l.peer = Uri.fromId(msg.author);
-                    return Maybe.just(l);
-                } catch (Exception e) {
-                    Log.w(TAG, "Failed to receive geolocation", e);
-                    return Maybe.empty();
-                }
-            })
-            .share();
-
-    private final Subject<Interaction> messageSubject = PublishSubject.create();
-    private final Subject<DataTransfer> dataTransferSubject = PublishSubject.create();
-    private final Subject<TrustRequest> incomingRequestsSubject = PublishSubject.create();
-
-    public void refreshAccounts() {
-        accountsSubject.onNext(mAccountList);
-    }
-
-    public static class RegisteredName {
-        public String accountId;
-        public String name;
-        public String address;
-        public int state;
-    }
-    public static class UserSearchResult {
-        private final String accountId;
-        private final String query;
-
-        public int state;
-        public List<Contact> results;
-
-        public UserSearchResult(String account, String query) {
-            accountId = account;
-            this.query = query;
-        }
-
-        public String getAccountId() {
-            return accountId;
-        }
-
-        public String getQuery() {
-            return query;
-        }
-
-        public List<Observable<SmartListViewModel>> getResultsViewModels() {
-            List<Observable<SmartListViewModel>> vms = new ArrayList<>(results.size());
-            for (Contact user : results) {
-                vms.add(Observable.just(new SmartListViewModel(accountId, user, null)));
-            }
-            return vms;
-        }
-    }
-
-    private final Subject<RegisteredName> registeredNameSubject = PublishSubject.create();
-    private final Subject<UserSearchResult> searchResultSubject = PublishSubject.create();
-
-    private static class ExportOnRingResult {
-        String accountId;
-        int code;
-        String pin;
-    }
-
-    private static class DeviceRevocationResult {
-        String accountId;
-        String deviceId;
-        int code;
-    }
-
-    private static class MigrationResult {
-        String accountId;
-        String state;
-    }
-
-    private final Subject<ExportOnRingResult> mExportSubject = PublishSubject.create();
-    private final Subject<DeviceRevocationResult> mDeviceRevocationSubject = PublishSubject.create();
-    private final Subject<MigrationResult> mMigrationSubject = PublishSubject.create();
-
-    public Observable<RegisteredName> getRegisteredNames() {
-        return registeredNameSubject;
-    }
-    public Observable<UserSearchResult> getSearchResults() {
-        return searchResultSubject;
-    }
-
-    public Observable<TextMessage> getIncomingMessages() {
-        return incomingTextMessageSubject;
-    }
-
-    public Observable<TextMessage> getIncomingSwarmMessages() {
-        return incomingSwarmMessageSubject
-                .filter(i -> i instanceof TextMessage)
-                .map(i -> (TextMessage) i);
-    }
-
-    public Observable<Location> getLocationUpdates() {
-        return incomingLocationSubject;
-    }
-
-    public Observable<Interaction> getMessageStateChanges() {
-        return messageSubject;
-    }
-
-    public Observable<TrustRequest> getIncomingRequests() {
-        return incomingRequestsSubject;
-    }
-
-    /**
-     * @return true if at least one of the loaded accounts is a SIP one
-     */
-    public boolean hasSipAccount() {
-        return mHasSipAccount;
-    }
-
-    /**
-     * @return true if at least one of the loaded accounts is a Ring one
-     */
-    public boolean hasRingAccount() {
-        return mHasRingAccount;
-    }
-
-    /**
-     * Loads the accounts from the daemon and then builds the local cache (also sends ACCOUNTS_CHANGED event)
-     *
-     * @param isConnected sets the initial connection state of the accounts
-     */
-    public void loadAccountsFromDaemon(final boolean isConnected) {
-        mExecutor.execute(() -> {
-            refreshAccountsCacheFromDaemon();
-            setAccountsActive(isConnected);
-        });
-    }
-
-    private void refreshAccountsCacheFromDaemon() {
-        Log.w(TAG, "refreshAccountsCacheFromDaemon");
-        boolean hasSip = false, hasJami = false;
-        List<Account> curList = mAccountList;
-        List<String> accountIds = new ArrayList<>(JamiService.getAccountList());
-        List<Account> newAccounts = new ArrayList<>(accountIds.size());
-        for (String id : accountIds) {
-            for (Account acc : curList)
-                if (acc.getAccountID().equals(id)) {
-                    newAccounts.add(acc);
-                    break;
-                }
-        }
-
-        // Cleanup removed accounts
-        for (Account acc : curList)
-            if (!newAccounts.contains(acc))
-                acc.cleanup();
-
-        mAccountList = newAccounts;
-
-        for (String accountId : accountIds) {
-            Account account = getAccount(accountId);
-            Map<String, String> details = JamiService.getAccountDetails(accountId).toNative();
-            List<Map<String, String>> credentials = JamiService.getCredentials(accountId).toNative();
-            Map<String, String> volatileAccountDetails = JamiService.getVolatileAccountDetails(accountId).toNative();
-            if (account == null) {
-                account = new Account(accountId, details, credentials, volatileAccountDetails);
-                newAccounts.add(account);
-            } else {
-                account.setDetails(details);
-                account.setCredentials(credentials);
-                account.setVolatileDetails(volatileAccountDetails);
-            }
-
-            if (account.isSip()) {
-                hasSip = true;
-            } else if (account.isJami()) {
-                hasJami = true;
-                boolean enabled = account.isEnabled();
-
-                account.setDevices(JamiService.getKnownRingDevices(accountId).toNative());
-                account.setContacts(JamiService.getContacts(accountId).toNative());
-                List<Map<String, String>> requests = JamiService.getTrustRequests(accountId).toNative();
-                for (Map<String, String> requestInfo : requests) {
-                    TrustRequest request = new TrustRequest(accountId, requestInfo);
-                    account.addRequest(request);
-                }
-                Log.w(TAG, accountId + " loading conversations");
-                List<String> conversations = JamiService.getConversations(account.getAccountID());
-                for (String conversationId : conversations) {
-                    Map<String, String> info = JamiService.conversationInfos(accountId, conversationId);
-                    /*for (Map.Entry<String, String> i : info.entrySet()) {
-                        Log.w(TAG, "conversation info: " + i.getKey() + " " + i.getValue());
-                    }*/
-                    Conversation.Mode mode = Conversation.Mode.values()[Integer.parseInt(info.get("mode"))];
-                    Conversation conversation = account.newSwarm(conversationId, mode);
-                    conversation.setLastMessageRead(mHistoryService.getLastMessageRead(accountId, conversation.getUri()));
-                    for (Map<String, String> member : JamiService.getConversationMembers(accountId, conversationId)) {
-                        Uri uri = Uri.fromId(member.get("uri"));
-                        Contact contact = conversation.findContact(uri);
-                        if (contact == null) {
-                            contact = account.getContactFromCache(uri);
-                            conversation.addContact(contact);
-                        }
-                    }
-                    conversation.setLastElementLoaded(Completable.defer(() -> loadMore(conversation, 2).ignoreElement()).cache());
-                    account.conversationStarted(conversation);
-                    //account.addSwarmConversation(conversationId, members);
-                }
-                for (Map<String, String> requestData : JamiService.getConversationRequests(account.getAccountID()).toNative()) {
-                    /*for (Map.Entry<String, String> e : requestData.entrySet()) {
-                        Log.e(TAG, "Request: " + e.getKey() + " " + e.getValue());
-                    }*/
-                    String conversationId = requestData.get("id");
-                    Uri from = Uri.fromString(requestData.get("from"));
-                    TrustRequest request = account.getRequest(from);
-                    if (request != null) {
-                        request.setConversationId(conversationId);
-                    } else {
-                        account.addRequest(new TrustRequest(account.getAccountID(), from, conversationId));
-                    }
-                }
-
-                if (enabled) {
-                    for (Contact contact : account.getContacts().values()) {
-                        if (!contact.isUsernameLoaded())
-                            JamiService.lookupAddress(accountId, "", contact.getUri().getRawRingId());
-                    }
-                }
-            }
-
-        }
-
-        mHasSipAccount = hasSip;
-        mHasRingAccount = hasJami;
-        if (!newAccounts.isEmpty()) {
-            Account newAccount = newAccounts.get(0);
-            if (mCurrentAccount != newAccount) {
-                mCurrentAccount = newAccount;
-            }
-        }
-
-        accountsSubject.onNext(newAccounts);
-    }
-
-    private Account getAccountByName(final String name) {
-        for (Account acc : mAccountList) {
-            if (acc.getAlias().equals(name))
-                return acc;
-        }
-        return null;
-    }
-
-    public String getNewAccountName(final String prefix) {
-        String name = String.format(prefix, "").trim();
-        if (getAccountByName(name) == null) {
-            return name;
-        }
-        int num = 1;
-        do {
-            num++;
-            name = String.format(prefix, num).trim();
-        } while (getAccountByName(name) != null);
-        return name;
-    }
-
-    /**
-     * Adds a new Account in the Daemon (also sends an ACCOUNT_ADDED event)
-     * Sets the new account as the current one
-     *
-     * @param map the account details
-     * @return the created Account
-     */
-    public Observable<Account> addAccount(final Map<String, String> map) {
-        return Observable.fromCallable(() -> {
-            String accountId = JamiService.addAccount(StringMap.toSwig(map));
-            if (StringUtils.isEmpty(accountId)) {
-                throw new RuntimeException("Can't create account.");
-            }
-            Account account = getAccount(accountId);
-            if (account == null) {
-                Map<String, String> accountDetails = JamiService.getAccountDetails(accountId).toNative();
-                List<Map<String, String>> accountCredentials = JamiService.getCredentials(accountId).toNative();
-                Map<String, String> accountVolatileDetails = JamiService.getVolatileAccountDetails(accountId).toNative();
-                Map<String, String> accountDevices = JamiService.getKnownRingDevices(accountId).toNative();
-                account = new Account(accountId, accountDetails, accountCredentials, accountVolatileDetails);
-                account.setDevices(accountDevices);
-                if (account.isSip()) {
-                    account.setRegistrationState(AccountConfig.STATE_READY, -1);
-                }
-                mAccountList.add(account);
-                accountsSubject.onNext(mAccountList);
-            }
-            return account;
-        })
-                .flatMap(account -> accountSubject
-                        .filter(acc -> acc.getAccountID().equals(account.getAccountID()))
-                        .startWithItem(account))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * @return the current Account from the local cache
-     */
-    public Account getCurrentAccount() {
-        return mCurrentAccount;
-    }
-    public int getCurrentAccountIndex() {
-        return mAccountList.indexOf(mCurrentAccount);
-    }
-
-    /**
-     * Sets the current Account in the local cache (also sends a ACCOUNTS_CHANGED event)
-     */
-    public void setCurrentAccount(Account currentAccount) {
-        if (mCurrentAccount == currentAccount)
-            return;
-        mCurrentAccount = currentAccount;
-
-        // the account order is changed
-        // the current Account is now on the top of the list
-        final List<Account> accounts = getAccounts();
-        List<String> orderedAccountIdList = new ArrayList<>(accounts.size());
-        String selectedID = mCurrentAccount.getAccountID();
-        orderedAccountIdList.add(selectedID);
-        for (Account account : accounts) {
-            if (account.getAccountID().contentEquals(selectedID)) {
-                continue;
-            }
-            orderedAccountIdList.add(account.getAccountID());
-        }
-
-        setAccountOrder(orderedAccountIdList);
-    }
-
-    /**
-     * @return the Account from the local cache that matches the accountId
-     */
-    public Account getAccount(String accountId) {
-        if (!StringUtils.isEmpty(accountId)) {
-            for (Account account : mAccountList)
-                if (accountId.equals(account.getAccountID()))
-                    return account;
-        }
-        return null;
-    }
-
-    public Single<Account> getAccountSingle(final String accountId) {
-        return accountsSubject
-                .firstOrError()
-                .map(accounts -> {
-                    for (Account account : accounts) {
-                        if (account.getAccountID().equals(accountId)) {
-                            return account;
-                        }
-                    }
-                    Log.d(TAG, "getAccountSingle() can't find account " + accountId);
-                    throw new IllegalArgumentException();
-                });
-    }
-
-    /**
-     * @return Accounts list from the local cache
-     */
-    public List<Account> getAccounts() {
-        return mAccountList;
-    }
-
-    public Observable<List<Account>> getObservableAccountList() {
-        return accountsSubject;
-    }
-
-    public Subject<Account> getObservableAccounts() {
-        return accountSubject;
-    }
-
-    public Observable<Account> getObservableAccountUpdates(String accountId) {
-        return accountSubject.filter(acc -> acc.getAccountID().equals(accountId));
-    }
-
-    public Observable<Account> getObservableAccount(String accountId) {
-        return Observable.fromCallable(() -> getAccount(accountId))
-                .concatWith(getObservableAccountUpdates(accountId));
-    }
-    public Observable<Account> getObservableAccount(Account account) {
-        return Observable.just(account)
-                .concatWith(accountSubject.filter(acc -> acc == account));
-    }
-
-    public Observable<Account> getCurrentAccountSubject() {
-        return currentAccountSubject;
-    }
-
-    public Observable<Account> getCurrentProfileAccountSubject() {
-        return currentAccountSubject.flatMapSingle(a -> mVCardService.loadProfile(a).map(p -> a));
-    }
-
-    public void subscribeBuddy(final String accountID, final String uri, final boolean flag) {
-        mExecutor.execute(() -> JamiService.subscribeBuddy(accountID, uri, flag));
-    }
-
-    /**
-     * Send profile through SIP
-     */
-    public void sendProfile(final String callId, final String accountId) {
-        mVCardService.loadSmallVCard(accountId, VCardService.MAX_SIZE_SIP)
-                .subscribeOn(Schedulers.computation())
-                .observeOn(Schedulers.from(mExecutor))
-                .subscribe(vcard -> {
-                    String stringVCard = VCardUtils.vcardToString(vcard);
-                    int nbTotal = stringVCard.length() / VCARD_CHUNK_SIZE + (stringVCard.length() % VCARD_CHUNK_SIZE != 0 ? 1 : 0);
-                    int i = 1;
-                    Random r = new Random(System.currentTimeMillis());
-                    int key = Math.abs(r.nextInt());
-
-                    Log.d(TAG, "sendProfile, vcard " + callId);
-
-                    while (i <= nbTotal) {
-                        HashMap<String, String> chunk = new HashMap<>();
-                        Log.d(TAG, "length vcard " + stringVCard.length() + " id " + key + " part " + i + " nbTotal " + nbTotal);
-                        String keyHashMap = VCardUtils.MIME_PROFILE_VCARD + "; id=" + key + ",part=" + i + ",of=" + nbTotal;
-                        String message = stringVCard.substring(0, Math.min(VCARD_CHUNK_SIZE, stringVCard.length()));
-                        chunk.put(keyHashMap, message);
-                        JamiService.sendTextMessage(callId, StringMap.toSwig(chunk), "Me", false);
-                        if (stringVCard.length() > VCARD_CHUNK_SIZE) {
-                            stringVCard = stringVCard.substring(VCARD_CHUNK_SIZE);
-                        }
-                        i++;
-                    }
-                }, e -> Log.w(TAG, "Not sending empty profile", e));
-    }
-
-    public void setMessageDisplayed(String accountId, Uri conversationUri, String messageId) {
-        mExecutor.execute(() -> JamiService.setMessageDisplayed(accountId, conversationUri.getUri(), messageId, 3));
-    }
-
-    public Single<Conversation> startConversation(String accountId, Collection<String> initialMembers) {
-        Account account = getAccount(accountId);
-        return Single.fromCallable(() -> {
-            Log.w(TAG, "startConversation");
-            String id = JamiService.startConversation(accountId);
-            Conversation conversation = account.getSwarm(id);//new Conversation(accountId, new Uri(id));
-            for (String member : initialMembers) {
-                Log.w(TAG, "addConversationMember " + member);
-                JamiService.addConversationMember(accountId, id, member);
-                conversation.addContact(account.getContactFromCache(member));
-            }
-            account.conversationStarted(conversation);
-            Log.w(TAG, "loadConversationMessages");
-            //loadMore(conversation);
-            //JamiService.loadConversationMessages(accountId, id, id, 2);
-            return conversation;
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public Completable removeConversation(String accountId, Uri conversationUri) {
-        return Completable.fromAction(() -> JamiService.removeConversation(accountId, conversationUri.getRawRingId()))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public void loadConversationHistory(String accountId, Uri conversationUri, String root, long n) {
-        JamiService.loadConversationMessages(accountId, conversationUri.getRawRingId(), root, n);
-    }
-
-    public Single<Conversation> loadMore(Conversation conversation) {
-        return loadMore(conversation, 16);
-    }
-    public Single<Conversation> loadMore(Conversation conversation, int n) {
-        synchronized (conversation) {
-            if (conversation.isLoaded()) {
-                Log.w(TAG, "loadMore: conversation already fully loaded");
-                return Single.just(conversation);
-            }
-
-            SingleSubject<Conversation> ret = conversation.getLoading();
-            if (ret != null)
-                return ret;
-            ret = SingleSubject.create();
-            Collection<String> roots = conversation.getSwarmRoot();
-            Log.w(TAG, "loadMore " + conversation.getUri() + " " + roots);
-
-            conversation.setLoading(ret);
-            if (roots.isEmpty())
-                loadConversationHistory(conversation.getAccountId(), conversation.getUri(), "", n);
-            else {
-                for (String root : roots)
-                    loadConversationHistory(conversation.getAccountId(), conversation.getUri(), root, n);
-            }
-            return ret;
-        }
-    }
-
-    public void sendConversationMessage(String accountId, Uri conversationUri, String txt) {
-        mExecutor.execute(() -> {
-            Log.w(TAG, "sendConversationMessages " + conversationUri.getRawRingId() + " : " + txt);
-            JamiService.sendMessage(accountId, conversationUri.getRawRingId(), txt, "");
-        });
-    }
-
-    /**
-     * @return Account Ids list from Daemon
-     */
-    public Single<List<String>> getAccountList() {
-        return Single.fromCallable(() -> (List<String>)new ArrayList<>(JamiService.getAccountList()))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * Sets the order of the accounts in the Daemon
-     *
-     * @param accountOrder The ordered list of account ids
-     */
-    public void setAccountOrder(final List<String> accountOrder) {
-        mExecutor.execute(() -> {
-            final StringBuilder order = new StringBuilder();
-            for (String accountId : accountOrder) {
-                order.append(accountId);
-                order.append(File.separator);
-            }
-            JamiService.setAccountsOrder(order.toString());
-        });
-    }
-
-    /**
-     * @return the account details from the Daemon
-     */
-    public Map<String, String> getAccountDetails(final String accountId) {
-        try {
-            return mExecutor.submit(() -> JamiService.getAccountDetails(accountId).toNative()).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getAccountDetails()", e);
-        }
-        return null;
-    }
-
-    /**
-     * Sets the account details in the Daemon
-     */
-    public void setAccountDetails(final String accountId, final Map<String, String> map) {
-        Log.i(TAG, "setAccountDetails() " + accountId);
-        mExecutor.execute(() -> JamiService.setAccountDetails(accountId, StringMap.toSwig(map)));
-    }
-
-    public Single<String> migrateAccount(String accountId, String password) {
-        return mMigrationSubject
-                .filter(r -> r.accountId.equals(accountId))
-                .map(r -> r.state)
-                .firstOrError()
-                .doOnSubscribe(s -> {
-                    final Account account = getAccount(accountId);
-                    HashMap<String, String> details = account.getDetails();
-                    details.put(ConfigKey.ARCHIVE_PASSWORD.key(), password);
-                    mExecutor.execute(() -> JamiService.setAccountDetails(accountId, StringMap.toSwig(details)));
-                })
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public void setAccountEnabled(final String accountId, final boolean active) {
-        mExecutor.execute(() -> JamiService.sendRegister(accountId, active));
-    }
-
-    /**
-     * Sets the activation state of the account in the Daemon
-     */
-    public void setAccountActive(final String accountId, final boolean active) {
-        mExecutor.execute(() -> JamiService.setAccountActive(accountId, active));
-    }
-
-    /**
-     * Sets the activation state of all the accounts in the Daemon
-     */
-    public void setAccountsActive(final boolean active) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "setAccountsActive() running... " + active);
-            for (Account a : mAccountList) {
-                // If the proxy is enabled we can considered the account
-                // as always active
-                if (a.isDhtProxyEnabled()) {
-                    JamiService.setAccountActive(a.getAccountID(), true);
-                } else {
-                    JamiService.setAccountActive(a.getAccountID(), active);
-                }
-            }
-        });
-    }
-
-    /**
-     * Sets the video activation state of all the accounts in the local cache
-     */
-    public void setAccountsVideoEnabled(boolean isEnabled) {
-        for (Account account : mAccountList) {
-            account.setDetail(ConfigKey.VIDEO_ENABLED, isEnabled);
-        }
-    }
-
-    /**
-     * @return the account volatile details from the Daemon
-     */
-    public Map<String, String> getVolatileAccountDetails(final String accountId) {
-        try {
-            return mExecutor.submit(() -> JamiService.getVolatileAccountDetails(accountId).toNative()).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getVolatileAccountDetails()", e);
-        }
-        return null;
-    }
-
-    /**
-     * @return the default template (account details) for a type of account
-     */
-    public Single<HashMap<String, String>> getAccountTemplate(final String accountType) {
-        Log.i(TAG, "getAccountTemplate() " + accountType);
-        return Single.fromCallable(() -> JamiService.getAccountTemplate(accountType).toNative())
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * Removes the account in the Daemon as well as local history
-     */
-    public void removeAccount(final String accountId) {
-        Log.i(TAG, "removeAccount() " + accountId);
-        mExecutor.execute(() -> JamiService.removeAccount(accountId));
-        mHistoryService.clearHistory(accountId).subscribe();
-    }
-
-    /**
-     * Exports the account on the DHT (used for multi-devices feature)
-     */
-    public Single<String> exportOnRing(final String accountId, final String password) {
-        return mExportSubject
-                .filter(r -> r.accountId.equals(accountId))
-                .firstOrError()
-                .map(result -> {
-                    switch (result.code) {
-                        case PIN_GENERATION_SUCCESS:
-                            return result.pin;
-                        case PIN_GENERATION_WRONG_PASSWORD:
-                            throw new IllegalArgumentException();
-                        case PIN_GENERATION_NETWORK_ERROR:
-                            throw new SocketException();
-                        default:
-                            throw new UnsupportedOperationException();
-                    }
-                })
-                .doOnSubscribe(l -> {
-                    Log.i(TAG, "exportOnRing() " + accountId);
-                    mExecutor.execute(() -> JamiService.exportOnRing(accountId, password));
-                })
-                .subscribeOn(Schedulers.io());
-    }
-
-    /**
-     * @return the list of the account's devices from the Daemon
-     */
-    public Map<String, String> getKnownRingDevices(final String accountId) {
-        Log.i(TAG, "getKnownRingDevices() " + accountId);
-        try {
-            return mExecutor.submit(() -> JamiService.getKnownRingDevices(accountId).toNative()).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getKnownRingDevices()", e);
-        }
-        return null;
-    }
-
-    /**
-     * @param accountId id of the account used with the device
-     * @param deviceId  id of the device to revoke
-     * @param password  password of the account
-     */
-    public Single<Integer> revokeDevice(final String accountId, final String password, final String deviceId) {
-        return mDeviceRevocationSubject
-                .filter(r -> r.accountId.equals(accountId) && r.deviceId.equals(deviceId))
-                .firstOrError()
-                .map(r -> r.code)
-                .doOnSubscribe(l -> mExecutor.execute(() -> JamiService.revokeDevice(accountId, password, deviceId)))
-                .subscribeOn(Schedulers.io());
-    }
-
-    /**
-     * @param accountId id of the account used with the device
-     * @param newName   new device name
-     */
-    public void renameDevice(final String accountId, final String newName) {
-        final Account account = getAccount(accountId);
-        mExecutor.execute(() -> {
-            Log.i(TAG, "renameDevice() thread running... " + newName);
-            StringMap details = JamiService.getAccountDetails(accountId);
-            details.put(ConfigKey.ACCOUNT_DEVICE_NAME.key(), newName);
-            JamiService.setAccountDetails(accountId, details);
-            account.setDetail(ConfigKey.ACCOUNT_DEVICE_NAME, newName);
-            account.setDevices(JamiService.getKnownRingDevices(accountId).toNative());
-        });
-    }
-
-    public Completable exportToFile(String accountId, String absolutePath, String password) {
-        return Completable.fromAction(() -> {
-            if (!JamiService.exportToFile(accountId, absolutePath, password))
-                throw new IllegalArgumentException("Can't export archive");
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * @param accountId   id of the account
-     * @param oldPassword old account password
-     */
-    public Completable setAccountPassword(final String accountId, final String oldPassword, final String newPassword) {
-        return Completable.fromAction(() -> {
-            if (!JamiService.changeAccountPassword(accountId, oldPassword, newPassword))
-                throw new IllegalArgumentException("Can't change password");
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * Sets the active codecs list of the account in the Daemon
-     */
-    public void setActiveCodecList(final String accountId, final List<Long> codecs) {
-        mExecutor.execute(() -> {
-            UintVect list = new UintVect();
-            list.reserve(codecs.size());
-            list.addAll(codecs);
-            JamiService.setActiveCodecList(accountId, list);
-            accountSubject.onNext(getAccount(accountId));
-        });
-    }
-
-    /**
-     * @return The account's codecs list from the Daemon
-     */
-    public Single<List<Codec>> getCodecList(final String accountId) {
-        return Single.fromCallable(() -> {
-            List<Codec> results = new ArrayList<>();
-            UintVect payloads = JamiService.getCodecList();
-            UintVect activePayloads = JamiService.getActiveCodecList(accountId);
-            for (int i = 0; i < payloads.size(); ++i) {
-                StringMap details = JamiService.getCodecDetails(accountId, payloads.get(i));
-                if (details.size() > 1) {
-                    results.add(new Codec(payloads.get(i), details.toNative(), activePayloads.contains(payloads.get(i))));
-                } else {
-                    Log.i(TAG, "Error loading codec " + i);
-                }
-            }
-            return results;
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public Map<String, String> validateCertificatePath(final String accountID, final String certificatePath, final String privateKeyPath, final String privateKeyPass) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "validateCertificatePath() running...");
-                return JamiService.validateCertificatePath(accountID, certificatePath, privateKeyPath, privateKeyPass, "").toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running validateCertificatePath()", e);
-        }
-        return null;
-    }
-
-    public Map<String, String> validateCertificate(final String accountId, final String certificate) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "validateCertificate() running...");
-                return JamiService.validateCertificate(accountId, certificate).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running validateCertificate()", e);
-        }
-        return null;
-    }
-
-    public Map<String, String> getCertificateDetailsPath(final String certificatePath) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCertificateDetailsPath() running...");
-                return JamiService.getCertificateDetails(certificatePath).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getCertificateDetailsPath()", e);
-        }
-        return null;
-    }
-
-    public Map<String, String> getCertificateDetails(final String certificateRaw) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCertificateDetails() running...");
-                return JamiService.getCertificateDetails(certificateRaw).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getCertificateDetails()", e);
-        }
-        return null;
-    }
-
-    /**
-     * @return the supported TLS methods from the Daemon
-     */
-    public List<String> getTlsSupportedMethods() {
-        Log.i(TAG, "getTlsSupportedMethods()");
-        return SwigNativeConverter.toJava(JamiService.getSupportedTlsMethod());
-    }
-
-    /**
-     * @return the account's credentials from the Daemon
-     */
-    public List<Map<String, String>> getCredentials(final String accountId) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCredentials() running...");
-                return JamiService.getCredentials(accountId).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getCredentials()", e);
-        }
-        return null;
-    }
-
-    /**
-     * Sets the account's credentials in the Daemon
-     */
-    public void setCredentials(final String accountId, final List<Map<String, String>> credentials) {
-        Log.i(TAG, "setCredentials() " + accountId);
-        mExecutor.execute(() -> JamiService.setCredentials(accountId, SwigNativeConverter.toSwig(credentials)));
-    }
-
-    /**
-     * Sets the registration state to true for all the accounts in the Daemon
-     */
-    public void registerAllAccounts() {
-        Log.i(TAG, "registerAllAccounts()");
-        mExecutor.execute(this::registerAllAccounts);
-    }
-
-    /**
-     * Registers a new name on the blockchain for the account
-     */
-    public void registerName(final Account account, final String password, final String name) {
-
-        if (account.registeringUsername) {
-            Log.w(TAG, "Already trying to register username");
-            return;
-        }
-
-        account.registeringUsername = true;
-        registerName(account.getAccountID(), password, name);
-    }
-
-    /**
-     * Register a new name on the blockchain for the account Id
-     */
-    public void registerName(final String account, final String password, final String name) {
-        Log.i(TAG, "registerName()");
-        mExecutor.execute(() -> JamiService.registerName(account, password, name));
-    }
-
-    /* contact requests */
-
-    /**
-     * @return all trust requests from the daemon for the account Id
-     */
-    public List<Map<String, String>> getTrustRequests(final String accountId) {
-        try {
-            return mExecutor.submit(() -> JamiService.getTrustRequests(accountId).toNative()).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getTrustRequests()", e);
-        }
-        return null;
-    }
-
-    /**
-     * Accepts a pending trust request
-     */
-    public void acceptTrustRequest(final String accountId, final Uri from) {
-        Log.i(TAG, "acceptRequest() " + accountId + " " + from);
-        Account account = getAccount(accountId);
-        if (account != null) {
-            TrustRequest request = account.getRequest(from);
-            if (request != null) {
-                VCard vCard = request.getVCard();
-                if (vCard != null) {
-                    VCardUtils.savePeerProfileToDisk(vCard, accountId, from.getRawRingId() + ".vcf", mDeviceRuntimeService.provideFilesDir());
-                }
-            }
-            account.removeRequest(from);
-            //handleTrustRequest(accountId, from, null, ContactType.INVITATION_ACCEPTED);
-        }
-        mExecutor.execute(() -> JamiService.acceptTrustRequest(accountId, from.getRawRingId()));
-    }
-
-
-    /**
-     * Handles adding contacts and is the initial point of conversation creation
-     *
-     * @param conversation    the user's account
-     * @param contactUri the contacts raw string uri
-     */
-    private void handleTrustRequest(Conversation conversation, Uri contactUri, TrustRequest request, ContactType type) {
-        ContactEvent event = new ContactEvent();
-        switch (type) {
-            case ADDED:
-                break;
-            case INVITATION_RECEIVED:
-                event.setStatus(Interaction.InteractionStatus.UNKNOWN);
-                event.setAuthor(contactUri.getRawRingId());
-                event.setTimestamp(request.getTimestamp());
-                break;
-            case INVITATION_ACCEPTED:
-                event.setStatus(Interaction.InteractionStatus.SUCCESS);
-                event.setAuthor(contactUri.getRawRingId());
-                break;
-            case INVITATION_DISCARDED:
-                mHistoryService.clearHistory(contactUri.getRawRingId(), conversation.getAccountId(), true).subscribe();
-                return;
-            default:
-                return;
-        }
-        mHistoryService.insertInteraction(conversation.getAccountId(), conversation, event).subscribe();
-    }
-
-    private enum ContactType {
-        ADDED, INVITATION_RECEIVED, INVITATION_ACCEPTED, INVITATION_DISCARDED
-    }
-
-    /**
-     * Refuses and blocks a pending trust request
-     */
-    public boolean discardTrustRequest(final String accountId, final Uri contactUri) {
-        Account account = getAccount(accountId);
-        boolean removed = false;
-        if (account != null) {
-            removed = account.removeRequest(contactUri);
-            mHistoryService.clearHistory(contactUri.getRawRingId(), accountId, true).subscribe();
-        }
-        mExecutor.execute(() -> JamiService.discardTrustRequest(accountId, contactUri.getRawRingId()));
-        return removed;
-    }
-
-    /**
-     * Sends a new trust request
-     */
-    public void sendTrustRequest(Conversation conversation, final Uri to, final Blob message) {
-        Log.i(TAG, "sendTrustRequest() " + conversation.getAccountId() + " " + to);
-        handleTrustRequest(conversation, to, null, ContactType.ADDED);
-        mExecutor.execute(() -> JamiService.sendTrustRequest(conversation.getAccountId(), to.getRawRingId(), message == null ? new Blob() : message));
-    }
-
-    /**
-     * Add a new contact for the account Id on the Daemon
-     */
-    public void addContact(final String accountId, final String uri) {
-        Log.i(TAG, "addContact() " + accountId + " " + uri);
-        //handleTrustRequest(accountId, Uri.fromString(uri), null, ContactType.ADDED);
-        mExecutor.execute(() -> JamiService.addContact(accountId, uri));
-    }
-
-    /**
-     * Remove an existing contact for the account Id on the Daemon
-     */
-    public void removeContact(final String accountId, final String uri, final boolean ban) {
-        Log.i(TAG, "removeContact() " + accountId + " " + uri + " ban:" + ban);
-        mExecutor.execute(() -> JamiService.removeContact(accountId, uri, ban));
-    }
-
-    /**
-     * @return the contacts list from the daemon
-     */
-    public List<Map<String, String>> getContacts(final String accountId) {
-        try {
-            return mExecutor.submit(() -> JamiService.getContacts(accountId).toNative()).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getContacts()", e);
-        }
-        return null;
-    }
-
-    /**
-     * Looks up for the availability of the name on the blockchain
-     */
-    public void lookupName(final String account, final String nameserver, final String name) {
-        Log.i(TAG, "lookupName() " + account + " " + nameserver + " " + name);
-        mExecutor.execute(() -> JamiService.lookupName(account, nameserver, name));
-    }
-
-    public Single<RegisteredName> findRegistrationByName(final String account, final String nameserver, final String name) {
-        if (StringUtils.isEmpty(name)) {
-            return Single.just(new RegisteredName());
-        }
-        return getRegisteredNames()
-                .filter(r -> account.equals(r.accountId) && name.equals(r.name))
-                .firstOrError()
-                .doOnSubscribe(s -> mExecutor.execute(() -> JamiService.lookupName(account, nameserver, name)))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public Single<UserSearchResult> searchUser(final String account, final String query) {
-        if (StringUtils.isEmpty(query)) {
-            return Single.just(new UserSearchResult(account, query));
-        }
-        String encodedUrl;
-        try {
-            encodedUrl = URLEncoder.encode(query, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            return Single.error(e);
-        }
-        return getSearchResults()
-                .filter(r -> account.equals(r.accountId) && encodedUrl.equals(r.query))
-                .firstOrError()
-                .doOnSubscribe(s -> mExecutor.execute(() -> JamiService.searchUser(account, encodedUrl)))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    /**
-     * Reverse looks up the address in the blockchain to find the name
-     */
-    public void lookupAddress(final String account, final String nameserver, final String address) {
-        mExecutor.execute(() -> JamiService.lookupAddress(account, nameserver, address));
-    }
-
-    public void pushNotificationReceived(final String from, final Map<String, String> data) {
-        // Log.i(TAG, "pushNotificationReceived()");
-        mExecutor.execute(() -> JamiService.pushNotificationReceived(from, StringMap.toSwig(data)));
-    }
-
-    public void setPushNotificationToken(final String pushNotificationToken) {
-        //Log.i(TAG, "setPushNotificationToken()");
-        mExecutor.execute(() -> JamiService.setPushNotificationToken(pushNotificationToken));
-    }
-
-    void volumeChanged(String device, int value) {
-        Log.w(TAG, "volumeChanged " + device + " " + value);
-    }
-
-    void accountsChanged() {
-        // Accounts have changed in Daemon, we have to update our local cache
-        refreshAccountsCacheFromDaemon();
-    }
-
-    void stunStatusFailure(String accountId) {
-        Log.d(TAG, "stun status failure: " + accountId);
-    }
-
-    void registrationStateChanged(String accountId, String newState, int code, String detailString) {
-        //Log.d(TAG, "registrationStateChanged: " + accountId + ", " + newState + ", " + code + ", " + detailString);
-
-        Account account = getAccount(accountId);
-        if (account == null) {
-            return;
-        }
-        String oldState = account.getRegistrationState();
-        if (oldState.contentEquals(AccountConfig.STATE_INITIALIZING) &&
-                !newState.contentEquals(AccountConfig.STATE_INITIALIZING)) {
-            account.setDetails(JamiService.getAccountDetails(account.getAccountID()).toNative());
-            account.setCredentials(JamiService.getCredentials(account.getAccountID()).toNative());
-            account.setDevices(JamiService.getKnownRingDevices(account.getAccountID()).toNative());
-            account.setVolatileDetails(JamiService.getVolatileAccountDetails(account.getAccountID()).toNative());
-        } else {
-            account.setRegistrationState(newState, code);
-        }
-
-        if (!oldState.equals(newState)) {
-            accountSubject.onNext(account);
-        }
-    }
-
-    void accountDetailsChanged(String accountId, Map<String, String> details) {
-        Account account = getAccount(accountId);
-        if (account == null) {
-            return;
-        }
-        Log.d(TAG, "accountDetailsChanged: " + accountId + " " + details.size());
-        account.setDetails(details);
-        accountSubject.onNext(account);
-    }
-
-    void volatileAccountDetailsChanged(String accountId, Map<String, String> details) {
-        Account account = getAccount(accountId);
-        if (account == null) {
-            return;
-        }
-        //Log.d(TAG, "volatileAccountDetailsChanged: " + accountId + " " + details.size());
-        account.setVolatileDetails(details);
-        accountSubject.onNext(account);
-    }
-
-    public void accountProfileReceived(String accountId, String name, String photo) {
-        Account account = getAccount(accountId);
-        if (account == null)
-            return;
-        mVCardService.saveVCardProfile(accountId, account.getUri(), name, photo)
-                .subscribeOn(Schedulers.io())
-                .subscribe(vcard -> account.resetProfile(), e -> Log.e(TAG, "Error saving profile", e));
-    }
-
-    void profileReceived(String accountId, String peerId, String vcardPath) {
-        Account account = getAccount(accountId);
-        if (account == null)
-            return;
-        Log.w(TAG, "profileReceived: " + accountId + ", " + peerId + ", " + vcardPath);
-        Contact contact = account.getContactFromCache(peerId);
-        mVCardService.peerProfileReceived(accountId, peerId, new File(vcardPath))
-                .subscribe(profile -> contact.setProfile(profile.first, profile.second), e -> Log.e(TAG, "Error saving contact profile", e));
-    }
-
-    void incomingAccountMessage(String accountId, String messageId, String callId, String from, Map<String, String> messages) {
-        Log.d(TAG, "incomingAccountMessage: " + accountId + " " + messages.size());
-        Message message = new Message();
-        message.accountId = accountId;
-        message.messageId = messageId;
-        message.callId = callId;
-        message.author = from;
-        message.messages = messages;
-        incomingMessageSubject.onNext(message);
-    }
-
-    void accountMessageStatusChanged(String accountId, String conversationId, String messageId, String peer, int status) {
-        InteractionStatus newStatus = InteractionStatus.fromIntTextMessage(status);
-        Log.d(TAG, "accountMessageStatusChanged: " + accountId + ", " + conversationId + ", " + messageId + ", " + peer + ", " + newStatus);
-        if (StringUtils.isEmpty(conversationId)) {
-            mHistoryService
-                    .accountMessageStatusChanged(accountId, messageId, peer, newStatus)
-                    .subscribe(messageSubject::onNext, e -> Log.e(TAG, "Error updating message: " + e.getLocalizedMessage()));
-        } else {
-            Interaction msg = new Interaction(accountId);
-            msg.setStatus(newStatus);
-            msg.setSwarmInfo(conversationId, messageId, null);
-            messageSubject.onNext(msg);
-        }
-    }
-
-    public void composingStatusChanged(String accountId, String conversationId, String contactUri, int status) {
-        Log.d(TAG, "composingStatusChanged: " + accountId + ", " + contactUri + ", " + conversationId + ", " + status);
-        getAccountSingle(accountId)
-                .subscribe(account -> account.composingStatusChanged(conversationId, Uri.fromId(contactUri), Account.ComposingStatus.fromInt(status)));
-    }
-
-    void errorAlert(int alert) {
-        Log.d(TAG, "errorAlert : " + alert);
-    }
-
-    void knownDevicesChanged(String accountId, Map<String, String> devices) {
-        Account accountChanged = getAccount(accountId);
-        if (accountChanged != null) {
-            accountChanged.setDevices(devices);
-            accountSubject.onNext(accountChanged);
-        }
-    }
-
-    void exportOnRingEnded(String accountId, int code, String pin) {
-        Log.d(TAG, "exportOnRingEnded: " + accountId + ", " + code + ", " + pin);
-        ExportOnRingResult result = new ExportOnRingResult();
-        result.accountId = accountId;
-        result.code = code;
-        result.pin = pin;
-        mExportSubject.onNext(result);
-    }
-
-    void nameRegistrationEnded(String accountId, int state, String name) {
-        Log.d(TAG, "nameRegistrationEnded: " + accountId + ", " + state + ", " + name);
-
-        Account acc = getAccount(accountId);
-        if (acc == null) {
-            Log.w(TAG, "Can't find account for name registration callback");
-            return;
-        }
-
-        acc.registeringUsername = false;
-        acc.setVolatileDetails(JamiService.getVolatileAccountDetails(acc.getAccountID()).toNative());
-        if (state == 0) {
-            acc.setDetail(ConfigKey.ACCOUNT_REGISTERED_NAME, name);
-        }
-
-        accountSubject.onNext(acc);
-    }
-
-    void migrationEnded(String accountId, String state) {
-        Log.d(TAG, "migrationEnded: " + accountId + ", " + state);
-        MigrationResult result = new MigrationResult();
-        result.accountId = accountId;
-        result.state = state;
-        mMigrationSubject.onNext(result);
-    }
-
-    void deviceRevocationEnded(String accountId, String device, int state) {
-        Log.d(TAG, "deviceRevocationEnded: " + accountId + ", " + device + ", " + state);
-        DeviceRevocationResult result = new DeviceRevocationResult();
-        result.accountId = accountId;
-        result.deviceId = device;
-        result.code = state;
-        if (state == 0) {
-            Account account = getAccount(accountId);
-            if (account != null) {
-                Map<String, String> devices = account.getDevices();
-                devices.remove(device);
-                account.setDevices(devices);
-                accountSubject.onNext(account);
-            }
-        }
-        mDeviceRevocationSubject.onNext(result);
-    }
-
-    void incomingTrustRequest(String accountId, String conversationId, String from, String message, long received) {
-        Log.d(TAG, "incomingTrustRequest: " + accountId + ", " + conversationId + ", " + from + ", " + received);
-
-        Account account = getAccount(accountId);
-        if (account != null) {
-            Uri fromUri = Uri.fromString(from);
-            TrustRequest request = account.getRequest(fromUri);
-            if (request == null)
-                request = new TrustRequest(accountId, fromUri, received * 1000L, message, conversationId);
-            else
-                request.setVCard(Ezvcard.parse(message).first());
-
-            VCard vcard = request.getVCard();
-            if (vcard != null) {
-                Contact contact = account.getContactFromCache(fromUri);
-                if (!contact.detailsLoaded) {
-                    // VCardUtils.savePeerProfileToDisk(vcard, accountId, from + ".vcf", mDeviceRuntimeService.provideFilesDir());
-                    mVCardService.loadVCardProfile(vcard)
-                            .subscribeOn(Schedulers.computation())
-                            .subscribe(profile -> contact.setProfile(profile.first, profile.second));
-                }
-            }
-            account.addRequest(request);
-            // handleTrustRequest(account, Uri.fromString(from), request, ContactType.INVITATION_RECEIVED);
-            if (account.isEnabled())
-                lookupAddress(accountId, "", from);
-            incomingRequestsSubject.onNext(request);
-        }
-    }
-
-    void contactAdded(String accountId, String uri, boolean confirmed) {
-        Account account = getAccount(accountId);
-        if (account != null) {
-            account.addContact(uri, confirmed);
-            if (account.isEnabled())
-                lookupAddress(accountId, "", uri);
-        }
-    }
-
-    void contactRemoved(String accountId, String uri, boolean banned) {
-        Account account = getAccount(accountId);
-        Log.d(TAG, "Contact removed: " + uri + " User is banned: " + banned);
-        if (account != null) {
-            mHistoryService.clearHistory(uri, accountId, true).subscribe();
-            account.removeContact(uri, banned);
-        }
-    }
-
-    void registeredNameFound(String accountId, int state, String address, String name) {
-        try {
-            //Log.d(TAG, "registeredNameFound: " + accountId + ", " + state + ", " + name + ", " + address);
-            if (!StringUtils.isEmpty(address)) {
-                Account account = getAccount(accountId);
-                if (account != null) {
-                    account.registeredNameFound(state, address, name);
-                }
-            }
-
-            RegisteredName r = new RegisteredName();
-            r.accountId = accountId;
-            r.address = address;
-            r.name = name;
-            r.state = state;
-            registeredNameSubject.onNext(r);
-        } catch (Exception e) {
-            Log.w(TAG, "registeredNameFound exception", e);
-        }
-    }
-
-    public void userSearchEnded(String accountId, int state, String query, ArrayList<Map<String, String>> results) {
-        Account account = getAccount(accountId);
-        UserSearchResult r = new UserSearchResult(accountId, query);
-        r.state = state;
-        r.results = new ArrayList<>(results.size());
-        for (Map<String, String> m : results) {
-            String uri = m.get("id");
-            String username = m.get("username");
-            String firstName = m.get("firstName");
-            String lastName = m.get("lastName");
-            String picture_b64 = m.get("profilePicture");
-            Contact contact = account.getContactFromCache(uri);
-            if (contact != null) {
-                contact.setUsername(username);
-                contact.setProfile(firstName + " " + lastName, mVCardService.base64ToBitmap(picture_b64));
-                r.results.add(contact);
-            }
-        }
-        searchResultSubject.onNext(r);
-    }
-
-    private Interaction addMessage(Account account, Conversation conversation, Map<String, String> message)  {
-        /* for (Map.Entry<String, String> e : message.entrySet()) {
-            Log.w(TAG, e.getKey() + " -> " + e.getValue());
-        } */
-        String id = message.get("id");
-        String type = message.get("type");
-        String author = message.get("author");
-        String parent = message.get("linearizedParent");
-        List<String> parents = StringUtils.isEmpty(parent) ? Collections.emptyList() : Collections.singletonList(parent);
-        Uri authorUri = Uri.fromId(author);
-
-        long timestamp = Long.parseLong(message.get("timestamp")) * 1000;
-        Contact contact = conversation.findContact(authorUri);
-        if (contact == null) {
-            contact = account.getContactFromCache(authorUri);
-        }
-        Interaction interaction;
-        switch (type) {
-            case "member":
-                contact.setAddedDate(new Date(timestamp));
-                interaction = new ContactEvent(contact);
-                break;
-            case "text/plain":
-                interaction = new TextMessage(author, account.getAccountID(), timestamp, conversation, message.get("body"), !contact.isUser());
-                break;
-            case "application/data-transfer+json": {
-                try {
-                    String fileName = message.get("displayName");
-                    String fileId = message.get("fileId");
-                    //interaction = account.getDataTransfer(fileId);
-                    //if (interaction == null) {
-                        String[] paths = new String[1];
-                        long[] progressA = new long[1];
-                        long[] totalA = new long[1];
-                        JamiService.fileTransferInfo(account.getAccountID(), conversation.getUri().getRawRingId(), fileId, paths, totalA, progressA);
-                        if (totalA[0] == 0) {
-                            totalA[0] = Long.parseLong(message.get("totalSize"));
-                        }
-                        File path = new File(paths[0]);
-                        interaction = new DataTransfer(fileId, account.getAccountID(), author, fileName, contact.isUser(), timestamp, totalA[0], progressA[0]);
-                        ((DataTransfer)interaction).setDaemonPath(path);
-                        boolean isComplete = path.exists() && progressA[0] == totalA[0];
-                        Log.w(TAG, "add DataTransfer at " + paths[0] + " with progress " + progressA[0] + "/" + totalA[0]);
-                        interaction.setStatus(isComplete ? InteractionStatus.TRANSFER_FINISHED : InteractionStatus.FILE_AVAILABLE);
-                    //}
-                } catch (Exception e) {
-                    interaction = new Interaction(conversation, Interaction.InteractionType.INVALID);
-                }
-                break;
-            }
-            case "application/call-history+json":
-                interaction = new Call(null, account.getAccountID(), authorUri.getRawUriString(), contact.isUser() ? Call.Direction.OUTGOING : Call.Direction.INCOMING, timestamp);
-                ((Call) interaction).setDuration(Long.parseLong(message.get("duration")));
-                break;
-            case "merge":
-            default:
-                interaction = new Interaction(conversation, Interaction.InteractionType.INVALID);
-                break;
-        }
-        interaction.setContact(contact);
-        interaction.setSwarmInfo(conversation.getUri().getRawRingId(), id, parents);
-        interaction.setConversation(conversation);
-        if (conversation.addSwarmElement(interaction)) {
-            if (conversation.isVisible())
-                mHistoryService.setMessageRead(account.getAccountID(), conversation.getUri(), interaction.getMessageId());
-        }
-        return interaction;
-    }
-
-    public void conversationLoaded(String accountId, String conversationId, List<Map<String, String>> messages) {
-        try {
-            // Log.w(TAG, "ConversationCallback: conversationLoaded " + accountId + "/" + conversationId + " " + messages.size());
-            Account account = getAccount(accountId);
-            if (account == null) {
-                Log.w(TAG, "conversationLoaded: can't find account");
-                return;
-            }
-            Conversation conversation = account.getSwarm(conversationId);
-            synchronized (conversation) {
-                for (Map<String, String> message : messages) {
-                    addMessage(account, conversation, message);
-                }
-                conversation.stopLoading();
-            }
-            account.conversationChanged();
-        } catch (Exception e) {
-            Log.e(TAG, "Exception loading message", e);
-        }
-    }
-
-    private enum ConversationMemberEvent {
-        Add, Join, Remove, Ban
-    }
-
-    public void conversationMemberEvent(String accountId, String conversationId, String peerUri, int event) {
-        Log.w(TAG, "ConversationCallback: conversationMemberEvent " + accountId + "/" + conversationId);
-        Account account = getAccount(accountId);
-        if (account == null) {
-            Log.w(TAG, "conversationMemberEvent: can't find account");
-            return;
-        }
-        Conversation conversation = account.getSwarm(conversationId);
-        Uri uri = Uri.fromId(peerUri);
-        switch (ConversationMemberEvent.values()[event])  {
-            case Add:
-            case Join: {
-                Contact contact = conversation.findContact(uri);
-                if (contact == null) {
-                    conversation.addContact(account.getContactFromCache(uri));
-                }
-                break;
-            }
-            case Remove:
-            case Ban: {
-                Contact contact = conversation.findContact(uri);
-                if (contact != null) {
-                    conversation.removeContact(contact);
-                }
-                break;
-            }
-        }
-    }
-
-    public void conversationReady(String accountId, String conversationId) {
-        Log.w(TAG, "ConversationCallback: conversationReady " + accountId + "/" + conversationId);
-        Account account = getAccount(accountId);
-        if (account == null) {
-            Log.w(TAG, "conversationReady: can't find account");
-            return;
-        }
-        StringMap info = JamiService.conversationInfos(accountId, conversationId);
-        /*for (Map.Entry<String, String> i : info.entrySet()) {
-            Log.w(TAG, "conversation info: " + i.getKey() + " " + i.getValue());
-        }*/
-        int modeInt = Integer.parseInt(info.get("mode"));
-        Conversation.Mode mode = Conversation.Mode.values()[modeInt];
-        Conversation conversation = account.newSwarm(conversationId, mode);
-
-        for (Map<String, String> member : JamiService.getConversationMembers(accountId, conversationId)) {
-            Uri uri = Uri.fromId(member.get("uri"));
-            Contact contact = conversation.findContact(uri);
-            if (contact == null) {
-                contact = account.getContactFromCache(uri);
-                conversation.addContact(contact);
-            }
-        }
-        account.conversationStarted(conversation);
-        loadMore(conversation, 2);
-    }
-
-    public void conversationRemoved(String accountId, String conversationId) {
-        Account account = getAccount(accountId);
-        if (account == null) {
-            Log.w(TAG, "conversationRemoved: can't find account");
-            return;
-        }
-        account.removeSwarm(conversationId);
-    }
-
-    public void conversationRequestReceived(String accountId, String conversationId, Map<String, String> metadata) {
-        Log.w(TAG, "ConversationCallback: conversationRequestReceived " + accountId + "/" + conversationId + " " + metadata.size());
-        Account account = getAccount(accountId);
-        if (account == null) {
-            Log.w(TAG, "conversationRequestReceived: can't find account");
-            return;
-        }
-        Uri contactUri = Uri.fromId(metadata.get("from"));
-        account.addRequest(new TrustRequest(account.getAccountID(), contactUri, conversationId));
-    }
-
-    public void messageReceived(String accountId, String conversationId, Map<String, String> message) {
-        Log.w(TAG, "ConversationCallback: messageReceived " + accountId + "/" + conversationId + " " + message.size());
-        Account account = getAccount(accountId);
-        Conversation conversation = account.getSwarm(conversationId);
-        synchronized (conversation) {
-            Interaction interaction = addMessage(account, conversation, message);
-            account.conversationUpdated(conversation);
-            boolean isIncoming = !interaction.getContact().isUser();
-            if (isIncoming) {
-                incomingSwarmMessageSubject.onNext(interaction);
-                if (interaction instanceof DataTransfer)
-                    dataTransferSubject.onNext((DataTransfer)interaction);
-            }
-        }
-    }
-
-    public Single<DataTransfer> sendFile(final File file, final DataTransfer dataTransfer) {
-        return Single.fromCallable(() -> {
-            mStartingTransfer = dataTransfer;
-
-            DataTransferInfo dataTransferInfo = new DataTransferInfo();
-            dataTransferInfo.setAccountId(dataTransfer.getAccount());
-
-            String conversationId = dataTransfer.getConversationId();
-            if (!StringUtils.isEmpty(conversationId))
-                dataTransferInfo.setConversationId(conversationId);
-            else
-                dataTransferInfo.setPeer(dataTransfer.getConversation().getParticipant());
-
-            dataTransferInfo.setPath(file.getAbsolutePath());
-            dataTransferInfo.setDisplayName(dataTransfer.getDisplayName());
-
-            Log.i(TAG, "sendFile() id=" + dataTransfer.getId() + " accountId=" + dataTransferInfo.getAccountId() + ", peer=" + dataTransferInfo.getPeer() + ", filePath=" + dataTransferInfo.getPath());
-            long[] id = new long[1];
-            DataTransferError err = getDataTransferError(JamiService.sendFileLegacy(dataTransferInfo, id));
-            if (err != DataTransferError.SUCCESS) {
-                throw new IOException(err.name());
-            } else {
-                Log.e(TAG, "sendFile: got ID " + id[0]);
-                dataTransfer.setDaemonId(id[0]);
-            }
-            return dataTransfer;
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public void sendFile(Conversation conversation, final File file) {
-        mExecutor.execute(() -> JamiService.sendFile(conversation.getAccountId(), conversation.getUri().getRawRingId(), file.getAbsolutePath(), file.getName(), ""));
-    }
-
-    public List<net.jami.daemon.Message> getLastMessages(String accountId, long baseTime) {
-        try {
-            return mExecutor.submit(() -> SwigNativeConverter.toJava(JamiService.getLastMessages(accountId, baseTime))).get();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return new ArrayList<>();
-    }
-
-    public void acceptFileTransfer(final String accountId, final Uri conversationUri, String messageId, String fileId) {
-        Account account = getAccount(accountId);
-        if (account != null) {
-            Conversation conversation = account.getByUri(conversationUri);
-            acceptFileTransfer(conversation, fileId, conversation.isSwarm() ? (DataTransfer)conversation.getMessage(messageId) : account.getDataTransfer(fileId));
-        }
-    }
-
-    public void acceptFileTransfer(Conversation conversation, String fileId, DataTransfer transfer) {
-        if (conversation.isSwarm()) {
-            String conversationId = conversation.getUri().getRawRingId();
-            File newPath = mDeviceRuntimeService.getNewConversationPath(conversation.getAccountId(), conversationId, transfer.getDisplayName());
-            Log.i(TAG, "downloadFile() id=" + conversation.getAccountId() + ", path=" + conversationId + " " + fileId + " to -> " + newPath.getAbsolutePath());
-            JamiService.downloadFile(conversation.getAccountId(), conversationId, transfer.getMessageId(), fileId, newPath.getAbsolutePath());
-        } else {
-            if (transfer == null) {
-                return;
-            }
-            File path = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath());
-            Log.i(TAG, "acceptFileTransfer() id=" + fileId + ", path=" + path.getAbsolutePath());
-            JamiService.acceptFileTransfer(conversation.getAccountId(), fileId, path.getAbsolutePath());
-        }
-    }
-
-    public void cancelDataTransfer(final String accountId, final String conversationId, final String messageId, final String fileId) {
-        Log.i(TAG, "cancelDataTransfer() id=" + fileId);
-        mExecutor.execute(() -> JamiService.cancelDataTransfer(accountId, conversationId, fileId));
-    }
-
-    private class DataTransferRefreshTask implements Runnable {
-        private final Account mAccount;
-        private final Conversation mConversation;
-        private final DataTransfer mToUpdate;
-        public ScheduledFuture<?> scheduledTask;
-
-        DataTransferRefreshTask(Account account, Conversation conversation, DataTransfer t) {
-            mAccount = account;
-            mConversation = conversation;
-            mToUpdate = t;
-        }
-
-        @Override
-        public void run() {
-            synchronized (mToUpdate) {
-                if (mToUpdate.getStatus() == Interaction.InteractionStatus.TRANSFER_ONGOING) {
-                    dataTransferEvent(mAccount, mConversation, mToUpdate.getMessageId(), mToUpdate.getFileId(), 5);
-                } else {
-                    scheduledTask.cancel(false);
-                    scheduledTask = null;
-                }
-            }
-        }
-    }
-
-    void dataTransferEvent(String accountId, String conversationId, String interactionId, final String fileId, int eventCode) {
-        Account account = getAccount(accountId);
-        if (account != null) {
-            Conversation conversation = StringUtils.isEmpty(conversationId) ? null : account.getSwarm(conversationId);
-            dataTransferEvent(account, conversation, interactionId, fileId, eventCode);
-        }
-    }
-    void dataTransferEvent(Account account, Conversation conversation, final String interactionId, final String fileId, int eventCode) {
-        Interaction.InteractionStatus transferStatus = getDataTransferEventCode(eventCode);
-        Log.d(TAG, "Data Transfer " + interactionId + " " + fileId + " " + transferStatus);
-
-        String from;
-        long total, progress;
-        String displayName;
-        DataTransfer transfer = account.getDataTransfer(fileId);
-        boolean outgoing = false;
-        if (conversation == null) {
-            DataTransferInfo info = new DataTransferInfo();
-            DataTransferError err = getDataTransferError(JamiService.dataTransferInfo(account.getAccountID(), fileId, info));
-            if (err != DataTransferError.SUCCESS) {
-                Log.d(TAG, "Data Transfer error getting details " + err);
-                return;
-            }
-            from = info.getPeer();
-            total = info.getTotalSize();
-            progress = info.getBytesProgress();
-            conversation = account.getByUri(from);
-            outgoing = info.getFlags() == 0;
-            displayName = info.getDisplayName();
-        } else {
-            String[] paths = new String[1];
-            long[] progressA = new long[1];
-            long[] totalA = new long[1];
-            JamiService.fileTransferInfo(account.getAccountID(), conversation.getUri().getRawRingId(), fileId, paths, totalA, progressA);
-            progress = progressA[0];
-            total = totalA[0];
-            if (transfer == null && !StringUtils.isEmpty(interactionId)) {
-                transfer = (DataTransfer) conversation.getMessage(interactionId);
-            }
-            if (transfer == null)
-                return;
-            transfer.setConversation(conversation);
-            transfer.setDaemonPath(new File(paths[0]));
-            from = transfer.getAuthor();
-            displayName = transfer.getDisplayName();
-        }
-
-        if (transfer == null) {
-            if (outgoing && mStartingTransfer != null) {
-                Log.d(TAG, "Data Transfer mStartingTransfer");
-                transfer = mStartingTransfer;
-                mStartingTransfer = null;
-            } else {
-                transfer = new DataTransfer(conversation, from, account.getAccountID(), displayName,
-                        outgoing, total,
-                        progress, fileId);
-                if (conversation.isSwarm()) {
-                    transfer.setSwarmInfo(conversation.getUri().getRawRingId(), interactionId, null);
-                } else {
-                    mHistoryService.insertInteraction(account.getAccountID(), conversation, transfer).blockingAwait();
-                }
-            }
-            account.putDataTransfer(fileId, transfer);
-        } else synchronized (transfer) {
-            InteractionStatus oldState = transfer.getStatus();
-            if (oldState != transferStatus) {
-                if (transferStatus == Interaction.InteractionStatus.TRANSFER_ONGOING) {
-                    DataTransferRefreshTask task = new DataTransferRefreshTask(account, conversation, transfer);
-                    task.scheduledTask = mExecutor.scheduleAtFixedRate(task,
-                            DATA_TRANSFER_REFRESH_PERIOD,
-                            DATA_TRANSFER_REFRESH_PERIOD, TimeUnit.MILLISECONDS);
-                } else if (transferStatus.isError()) {
-                    if (!transfer.isOutgoing()) {
-                        File tmpPath = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath());
-                        tmpPath.delete();
-                    }
-                } else if (transferStatus == (Interaction.InteractionStatus.TRANSFER_FINISHED)) {
-                    if (!conversation.isSwarm() && !transfer.isOutgoing()) {
-                        File tmpPath = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath());
-                        File path = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), transfer.getStoragePath());
-                        FileUtils.moveFile(tmpPath, path);
-                    }
-                }
-            }
-            transfer.setStatus(transferStatus);
-            transfer.setBytesProgress(progress);
-            if (!conversation.isSwarm()) {
-                mHistoryService.updateInteraction(transfer, account.getAccountID()).subscribe();
-            }
-        }
-
-        Log.d(TAG, "Data Transfer dataTransferSubject.onNext");
-        dataTransferSubject.onNext(transfer);
-    }
-
-    private static Interaction.InteractionStatus getDataTransferEventCode(int eventCode) {
-        Interaction.InteractionStatus dataTransferEventCode = Interaction.InteractionStatus.INVALID;
-        try {
-            dataTransferEventCode = InteractionStatus.fromIntFile(eventCode);
-        } catch (ArrayIndexOutOfBoundsException ignored) {
-            Log.e(TAG, "getEventCode: invalid data transfer status from daemon");
-        }
-        return dataTransferEventCode;
-    }
-
-    private static DataTransferError getDataTransferError(Long errorCode) {
-        if (errorCode == null) {
-            Log.e(TAG, "getDataTransferError: invalid error code");
-        } else {
-            try {
-                return DataTransferError.values()[errorCode.intValue()];
-            } catch (ArrayIndexOutOfBoundsException ignored) {
-                Log.e(TAG, "getDataTransferError: invalid data transfer error from daemon");
-            }
-        }
-        return DataTransferError.UNKNOWN;
-    }
-
-    public Subject<DataTransfer> getDataTransfers() {
-        return dataTransferSubject;
-    }
-
-    public Observable<DataTransfer> observeDataTransfer(DataTransfer transfer) {
-        return dataTransferSubject
-                .filter(t -> t == transfer)
-                .startWithItem(transfer);
-    }
-
-    public void setProxyEnabled(boolean enabled) {
-        mExecutor.execute(() -> {
-            for (Account acc : mAccountList) {
-                if (acc.isJami() && (acc.isDhtProxyEnabled() != enabled)) {
-                    Log.d(TAG, (enabled ? "Enabling" : "Disabling") + " proxy for account " + acc.getAccountID());
-                    acc.setDhtProxyEnabled(enabled);
-                    StringMap details = JamiService.getAccountDetails(acc.getAccountID());
-                    details.put(ConfigKey.PROXY_ENABLED.key(), enabled ? "true" : "false");
-                    JamiService.setAccountDetails(acc.getAccountID(), details);
-                }
-            }
-        });
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt
new file mode 100644
index 000000000..f6363963d
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt
@@ -0,0 +1,1785 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.services
+
+import com.google.gson.JsonParser
+import ezvcard.Ezvcard
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Maybe
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.SingleSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.daemon.*
+import net.jami.model.*
+import net.jami.model.Interaction.InteractionStatus
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.*
+import java.io.File
+import java.io.IOException
+import java.io.UnsupportedEncodingException
+import java.net.SocketException
+import java.net.URLEncoder
+import java.util.*
+import java.util.concurrent.Callable
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+import kotlin.collections.HashMap
+import kotlin.math.min
+
+/**
+ * This service handles the accounts
+ * - Load and manage the accounts stored in the daemon
+ * - Keep a local cache of the accounts
+ * - handle the callbacks that are send by the daemon
+ */
+class AccountService(
+    private val mExecutor: ScheduledExecutorService,
+    private val mHistoryService: HistoryService,
+    private val mDeviceRuntimeService: DeviceRuntimeService,
+    private val mVCardService: VCardService
+) {
+    /**
+     * @return the current Account from the local cache
+     */
+    var currentAccount: Account? = null
+        set(account) {
+            if (field === account) return
+            field = account!!
+
+            // the account order is changed
+            // the current Account is now on the top of the list
+            val accounts: List<Account> = mAccountList
+            val orderedAccountIdList: MutableList<String> = ArrayList(accounts.size)
+            val selectedID = account.accountID
+            orderedAccountIdList.add(selectedID)
+            for (a in accounts) {
+                if (a.accountID.contentEquals(selectedID)) {
+                    continue
+                }
+                orderedAccountIdList.add(a.accountID)
+            }
+            setAccountOrder(orderedAccountIdList)
+        }
+
+    private var mAccountList: MutableList<Account> = ArrayList()
+    private var mHasSipAccount = false
+    private var mHasRingAccount = false
+    private var mStartingTransfer: DataTransfer? = null
+    private val accountsSubject = BehaviorSubject.create<List<Account>>()
+    val observableAccounts: Subject<Account> = PublishSubject.create()
+    val currentAccountSubject: Observable<Account> = accountsSubject
+        .filter { l -> l.isNotEmpty() }
+        .map { l -> l[0] }
+        .distinctUntilChanged()
+
+    class Message constructor (
+        val accountId: String,
+        val messageId: String?,
+        val callId: String?,
+        val author: String,
+        val messages: Map<String, String>
+    )
+
+    class Location(
+        val account: String,
+        val callId: String?,
+        val peer: Uri,
+        var date: Long) {
+        enum class Type {
+            Position, Stop
+        }
+
+        lateinit var type: Type
+        var latitude = 0.0
+        var longitude = 0.0
+    }
+
+    private val incomingMessageSubject: Subject<Message> = PublishSubject.create()
+    private val incomingSwarmMessageSubject: Subject<Interaction> = PublishSubject.create()
+    val incomingMessages: Observable<TextMessage> = incomingMessageSubject
+        .flatMapMaybe { msg: Message ->
+            val message = msg.messages[CallService.MIME_TEXT_PLAIN]
+            if (message != null) {
+                return@flatMapMaybe mHistoryService
+                    .incomingMessage(msg.accountId, msg.messageId, msg.author, message)
+                    .toMaybe()
+            }
+            Maybe.empty()
+        }
+        .share()
+    val locationUpdates: Observable<Location> = incomingMessageSubject
+        .flatMapMaybe { msg: Message ->
+            try {
+                val loc = msg.messages[CallService.MIME_GEOLOCATION] ?: return@flatMapMaybe Maybe.empty<Location>()
+                val obj = JsonParser.parseString(loc).asJsonObject
+                if (obj.size() < 2) return@flatMapMaybe Maybe.empty<Location>()
+                return@flatMapMaybe Maybe.just(Location(msg.accountId, msg.callId, Uri.fromId(msg.author), obj["time"].asLong).apply {
+                    val t = obj["type"]
+                    if (t == null || t.asString == Location.Type.Position.toString()) {
+                        type = Location.Type.Position
+                        latitude = obj["lat"].asDouble
+                        longitude = obj["long"].asDouble
+                    } else if (t.asString == Location.Type.Stop.toString()) {
+                        type = Location.Type.Stop
+                    }
+                })
+            } catch (e: Exception) {
+                Log.w(TAG, "Failed to receive geolocation", e)
+                return@flatMapMaybe Maybe.empty<Location>()
+            }
+        }
+        .share()
+    private val messageSubject: Subject<Interaction> = PublishSubject.create()
+    val dataTransfers: Subject<DataTransfer> = PublishSubject.create()
+    private val incomingRequestsSubject: Subject<TrustRequest> = PublishSubject.create()
+    fun refreshAccounts() {
+        accountsSubject.onNext(mAccountList)
+    }
+
+    class RegisteredName(
+        val accountId: String,
+        val name: String,
+        val address: String? = null,
+        val state: Int = 0
+    )
+
+    class UserSearchResult(val accountId: String, val query: String, var state: Int = 0) {
+        var results: MutableList<Contact>? = null
+        val resultsViewModels: List<Observable<SmartListViewModel>>
+            get() {
+                val vms: MutableList<Observable<SmartListViewModel>> = ArrayList(results!!.size)
+                for (user in results!!) {
+                    vms.add(Observable.just(SmartListViewModel(accountId, user, null)))
+                }
+                return vms
+            }
+    }
+
+    private val registeredNameSubject: Subject<RegisteredName> = PublishSubject.create()
+    private val searchResultSubject: Subject<UserSearchResult> = PublishSubject.create()
+
+    private class ExportOnRingResult (
+        var accountId: String,
+        var code: Int,
+        var pin: String?
+    )
+
+    private class DeviceRevocationResult (
+        var accountId: String,
+        var deviceId: String,
+        var code: Int
+    )
+
+    private class MigrationResult (
+        var accountId: String,
+        var state: String
+    )
+
+    private val mExportSubject: Subject<ExportOnRingResult> = PublishSubject.create()
+    private val mDeviceRevocationSubject: Subject<DeviceRevocationResult> = PublishSubject.create()
+    private val mMigrationSubject: Subject<MigrationResult> = PublishSubject.create()
+    val registeredNames: Observable<RegisteredName>
+        get() = registeredNameSubject
+    val searchResults: Observable<UserSearchResult>
+        get() = searchResultSubject
+    val incomingSwarmMessages: Observable<TextMessage>
+        get() = incomingSwarmMessageSubject
+            .filter { i: Interaction -> i is TextMessage }
+            .map { i: Interaction -> i as TextMessage }
+    val messageStateChanges: Observable<Interaction>
+        get() = messageSubject
+    val incomingRequests: Observable<TrustRequest>
+        get() = incomingRequestsSubject
+
+    /**
+     * @return true if at least one of the loaded accounts is a SIP one
+     */
+    fun hasSipAccount(): Boolean {
+        return mHasSipAccount
+    }
+
+    /**
+     * @return true if at least one of the loaded accounts is a Ring one
+     */
+    fun hasRingAccount(): Boolean {
+        return mHasRingAccount
+    }
+
+    /**
+     * Loads the accounts from the daemon and then builds the local cache (also sends ACCOUNTS_CHANGED event)
+     *
+     * @param isConnected sets the initial connection state of the accounts
+     */
+    fun loadAccountsFromDaemon(isConnected: Boolean) {
+        mExecutor.execute {
+            refreshAccountsCacheFromDaemon()
+            setAccountsActive(isConnected)
+        }
+    }
+
+    private fun refreshAccountsCacheFromDaemon() {
+        Log.w(TAG, "refreshAccountsCacheFromDaemon")
+        var hasSip = false
+        var hasJami = false
+        val curList: List<Account> = mAccountList
+        val accountIds: List<String> = ArrayList(JamiService.getAccountList())
+        val newAccounts: MutableList<Account> = ArrayList(accountIds.size)
+        for (id in accountIds) {
+            for (acc in curList) if (acc.accountID == id) {
+                newAccounts.add(acc)
+                break
+            }
+        }
+
+        // Cleanup removed accounts
+        for (acc in curList) if (!newAccounts.contains(acc)) acc.cleanup()
+        for (accountId in accountIds) {
+            var account = findAccount(newAccounts, accountId)
+            val details: Map<String, String> = JamiService.getAccountDetails(accountId).toNative()
+            val credentials: List<Map<String, String>> = JamiService.getCredentials(accountId).toNative()
+            val volatileAccountDetails: Map<String, String> = JamiService.getVolatileAccountDetails(accountId).toNative()
+            if (account == null) {
+                account = Account(accountId, details, credentials, volatileAccountDetails)
+                newAccounts.add(account)
+            } else {
+                account.setDetails(details)
+                account.setCredentials(credentials)
+                account.setVolatileDetails(volatileAccountDetails)
+            }
+        }
+        mAccountList = newAccounts
+        synchronized(newAccounts) {
+            for (account in newAccounts) {
+                val accountId = account.accountID
+                if (account.isSip) {
+                    hasSip = true
+                } else if (account.isJami) {
+                    hasJami = true
+                    val enabled = account.isEnabled
+                    account.devices = JamiService.getKnownRingDevices(accountId).toNative()
+                    account.setContacts(JamiService.getContacts(accountId).toNative())
+                    val requests: List<Map<String, String>> = JamiService.getTrustRequests(accountId).toNative()
+                    for (requestInfo in requests) {
+                        val request = TrustRequest(accountId, requestInfo)
+                        account.addRequest(request)
+                    }
+                    val conversations: List<String> = JamiService.getConversations(account.accountID)
+                    Log.w(TAG, accountId + " loading conversations: " + conversations.size)
+                    for (conversationId in conversations) {
+                        try {
+                            val info: Map<String, String> = JamiService.conversationInfos(accountId, conversationId).toNative()
+                            /*for (Map.Entry<String, String> i : info.entrySet()) {
+                                Log.w(TAG, "conversation info: " + i.getKey() + " " + i.getValue());
+                            }*/
+                            val mode = if ("true" == info["syncing"]) Conversation.Mode.Syncing else Conversation.Mode.values()[info["mode"]!!.toInt()]
+                            val conversation = account.newSwarm(conversationId, mode)
+                            if (mode != Conversation.Mode.Syncing) {
+                                for (member in JamiService.getConversationMembers(accountId, conversationId)) {
+                                    /*for (Map.Entry<String, String> i : member.entrySet()) {
+                                        Log.w(TAG, "conversation member: " + i.getKey() + " " + i.getValue());
+                                    }*/
+                                    val uri = Uri.fromId(member["uri"]!!)
+                                    //String role = member.get("role");
+                                    val lastDisplayed = member["lastDisplayed"]
+                                    var contact = conversation.findContact(uri)
+                                    if (contact == null) {
+                                        contact = account.getContactFromCache(uri)
+                                        conversation.addContact(contact)
+                                    }
+                                    if (!StringUtils.isEmpty(lastDisplayed) && contact.isUser) {
+                                        conversation.setLastMessageRead(lastDisplayed)
+                                    }
+                                }
+                            }
+                            conversation.lastElementLoaded = Completable.defer { loadMore(conversation, 2).ignoreElement() }.cache()
+                            account.conversationStarted(conversation)
+                        } catch (e: Exception) {
+                            Log.w(TAG, "Error loading conversation", e)
+                        }
+                    }
+                    for (requestData in JamiService.getConversationRequests(account.accountID).toNative()) {
+                        /*for (Map.Entry<String, String> e : requestData.entrySet()) {
+                            Log.e(TAG, "Request: " + e.getKey() + " " + e.getValue());
+                        }*/
+                        val conversationId = requestData["id"]
+                        val from = Uri.fromString(requestData["from"]!!)
+                        val request = account.getRequest(from)
+                        if (request == null || conversationId != request.conversationId) {
+                            val received = requestData["received"]
+                            account.addRequest(TrustRequest(account.accountID, from, java.lang.Long.decode(received) * 1000L, null, conversationId))
+                        }
+                    }
+                    if (enabled) {
+                        for (contact in account.contacts.values) {
+                            if (!contact.isUsernameLoaded) JamiService.lookupAddress(
+                                accountId,
+                                "",
+                                contact.uri.rawRingId
+                            )
+                        }
+                    }
+                }
+            }
+            mHasSipAccount = hasSip
+            mHasRingAccount = hasJami
+            if (!newAccounts.isEmpty()) {
+                val newAccount = newAccounts[0]
+                if (currentAccount !== newAccount) {
+                    currentAccount = newAccount
+                }
+            }
+        }
+        accountsSubject.onNext(newAccounts)
+    }
+
+    private fun getAccountByName(name: String): Account? {
+        synchronized(mAccountList) {
+            for (acc in mAccountList) {
+                if (acc.alias == name) return acc
+            }
+        }
+        return null
+    }
+
+    fun getNewAccountName(prefix: String?): String {
+        var name = String.format(prefix!!, "").trim { it <= ' ' }
+        if (getAccountByName(name) == null) {
+            return name
+        }
+        var num = 1
+        do {
+            num++
+            name = String.format(prefix, num).trim { it <= ' ' }
+        } while (getAccountByName(name) != null)
+        return name
+    }
+
+    /**
+     * Adds a new Account in the Daemon (also sends an ACCOUNT_ADDED event)
+     * Sets the new account as the current one
+     *
+     * @param map the account details
+     * @return the created Account
+     */
+    fun addAccount(map: Map<String?, String?>?): Observable<Account?> {
+        return Observable.fromCallable {
+            val accountId = JamiService.addAccount(StringMap.toSwig(map))
+            if (StringUtils.isEmpty(accountId)) {
+                throw RuntimeException("Can't create account.")
+            }
+            var account = getAccount(accountId)
+            if (account == null) {
+                val accountDetails: Map<String, String> = JamiService.getAccountDetails(accountId).toNative()
+                val accountCredentials: List<Map<String, String>> = JamiService.getCredentials(accountId).toNative()
+                val accountVolatileDetails: Map<String, String> = JamiService.getVolatileAccountDetails(accountId).toNative()
+                val accountDevices: Map<String, String> = JamiService.getKnownRingDevices(accountId).toNative()
+                account = Account(accountId, accountDetails, accountCredentials, accountVolatileDetails)
+                account.devices = accountDevices
+                if (account.isSip) {
+                    account.setRegistrationState(AccountConfig.STATE_READY, -1)
+                }
+                mAccountList.add(account)
+                accountsSubject.onNext(mAccountList)
+            }
+            account
+        }
+            .flatMap { account: Account ->
+                observableAccounts
+                    .filter { acc: Account? -> acc!!.accountID == account.accountID }
+                    .startWithItem(account)
+            }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    val currentAccountIndex: Int
+        get() = mAccountList.indexOf(currentAccount)
+
+    /**
+     * @return the Account from the local cache that matches the accountId
+     */
+    fun getAccount(accountId: String): Account? {
+        if (!StringUtils.isEmpty(accountId)) {
+            synchronized(mAccountList) { for (account in mAccountList) if (accountId == account.accountID) return account }
+        }
+        return null
+    }
+
+    fun getAccountSingle(accountId: String): Single<Account> {
+        return accountsSubject
+            .firstOrError()
+            .map { accounts: List<Account> ->
+                for (account in accounts) {
+                    if (account.accountID == accountId) {
+                        return@map account
+                    }
+                }
+                Log.d(TAG, "getAccountSingle() can't find account $accountId")
+                throw IllegalArgumentException()
+            }
+    }
+
+    val observableAccountList: Observable<List<Account>>
+        get() = accountsSubject
+
+    fun getObservableAccountUpdates(accountId: String): Observable<Account> {
+        return observableAccounts.filter { acc -> acc.accountID == accountId }
+    }
+
+    fun getObservableAccount(accountId: String): Observable<Account> {
+        return Observable.fromCallable<Account> { getAccount(accountId) }
+            .concatWith(getObservableAccountUpdates(accountId))
+    }
+
+    fun getObservableAccount(account: Account): Observable<Account> {
+        return Observable.just(account)
+            .concatWith(observableAccounts.filter { acc -> acc === account })
+    }
+
+    val currentProfileAccountSubject: Observable<Account>
+        get() = currentAccountSubject.flatMapSingle { a: Account ->
+            mVCardService.loadProfile(a).firstOrError().map { p: Tuple<String?, Any?>? -> a }
+        }
+
+    fun subscribeBuddy(accountID: String?, uri: String?, flag: Boolean) {
+        mExecutor.execute { JamiService.subscribeBuddy(accountID, uri, flag) }
+    }
+
+    /**
+     * Send profile through SIP
+     */
+    fun sendProfile(callId: String, accountId: String) {
+        mVCardService.loadSmallVCard(accountId, VCardService.MAX_SIZE_SIP)
+            .subscribeOn(Schedulers.computation())
+            .observeOn(Schedulers.from(mExecutor))
+            .subscribe({ vcard: VCard ->
+                var stringVCard = VCardUtils.vcardToString(vcard)!!
+                val nbTotal = stringVCard.length / VCARD_CHUNK_SIZE + if (stringVCard.length % VCARD_CHUNK_SIZE != 0) 1 else 0
+                var i = 1
+                val r = Random(System.currentTimeMillis())
+                val key = Math.abs(r.nextInt())
+                Log.d(TAG, "sendProfile, vcard $callId")
+                while (i <= nbTotal) {
+                    val chunk = HashMap<String, String>()
+                    Log.d(TAG, "length vcard " + stringVCard.length + " id " + key + " part " + i + " nbTotal " + nbTotal)
+                    val keyHashMap = VCardUtils.MIME_PROFILE_VCARD + "; id=" + key + ",part=" + i + ",of=" + nbTotal
+                    val message = stringVCard.substring(0, min(VCARD_CHUNK_SIZE, stringVCard.length))
+                    chunk[keyHashMap] = message
+                    JamiService.sendTextMessage(callId, StringMap.toSwig(chunk), "Me", false)
+                    if (stringVCard.length > VCARD_CHUNK_SIZE) {
+                        stringVCard = stringVCard.substring(VCARD_CHUNK_SIZE)
+                    }
+                    i++
+                }
+            }) { e: Throwable -> Log.w(TAG, "Not sending empty profile", e) }
+    }
+
+    fun setMessageDisplayed(accountId: String?, conversationUri: Uri, messageId: String?) {
+        mExecutor.execute { JamiService.setMessageDisplayed(accountId, conversationUri.uri, messageId, 3) }
+    }
+
+    fun startConversation(accountId: String, initialMembers: Collection<String>): Single<Conversation> {
+        return getAccountSingle(accountId).map { account ->
+            Log.w(TAG, "startConversation")
+            val id = JamiService.startConversation(accountId)
+            val conversation = account.getSwarm(id)!! //new Conversation(accountId, new Uri(id));
+            for (member in initialMembers) {
+                Log.w(TAG, "addConversationMember $member")
+                JamiService.addConversationMember(accountId, id, member)
+                conversation.addContact(account.getContactFromCache(member))
+            }
+            account.conversationStarted(conversation)
+            Log.w(TAG, "loadConversationMessages")
+            conversation
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun removeConversation(accountId: String, conversationUri: Uri): Completable {
+        return Completable.fromAction { JamiService.removeConversation(accountId, conversationUri.rawRingId) }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun loadConversationHistory(accountId: String, conversationUri: Uri, root: String, n: Long) {
+        JamiService.loadConversationMessages(accountId, conversationUri.rawRingId, root, n)
+    }
+
+    @JvmOverloads
+    fun loadMore(conversation: Conversation, n: Int = 16): Single<Conversation> {
+        synchronized(conversation) {
+            if (conversation.isLoaded()) {
+                Log.w(TAG, "loadMore: conversation already fully loaded")
+                return Single.just(conversation)
+            }
+            if (conversation.mode.blockingFirst() == Conversation.Mode.Syncing) {
+                Log.w(TAG, "loadMore: conversation is syncing")
+                return Single.just(conversation)
+            }
+            conversation.loading?.let { return it }
+            val ret = SingleSubject.create<Conversation>()
+            val roots = conversation.swarmRoot
+            Log.w(TAG, "loadMore " + conversation.uri + " " + roots)
+            conversation.loading = ret
+            if (roots.isEmpty())
+                loadConversationHistory(conversation.accountId, conversation.uri, "", n.toLong()
+            ) else {
+                for (root in roots)
+                    loadConversationHistory(conversation.accountId, conversation.uri, root, n.toLong())
+            }
+            return ret
+        }
+    }
+
+    fun sendConversationMessage(accountId: String, conversationUri: Uri, txt: String) {
+        mExecutor.execute {
+            Log.w(TAG, "sendConversationMessages " + conversationUri.rawRingId + " : " + txt)
+            JamiService.sendMessage(accountId, conversationUri.rawRingId, txt, "")
+        }
+    }
+    /**
+     * @return Account Ids list from Daemon
+     */
+    /*public Single<List<String>> getAccountList() {
+        return Single.fromCallable(() -> (List<String>)new ArrayList<>(JamiService.getAccountList()))
+                .subscribeOn(Schedulers.from(mExecutor));
+    }*/
+    /**
+     * Sets the order of the accounts in the Daemon
+     *
+     * @param accountOrder The ordered list of account ids
+     */
+    fun setAccountOrder(accountOrder: List<String>) {
+        mExecutor.execute {
+            val order = StringBuilder()
+            for (accountId in accountOrder) {
+                order.append(accountId)
+                order.append(File.separator)
+            }
+            JamiService.setAccountsOrder(order.toString())
+        }
+    }
+
+    /**
+     * Sets the account details in the Daemon
+     */
+    fun setAccountDetails(accountId: String, map: Map<String, String>) {
+        Log.i(TAG, "setAccountDetails() $accountId")
+        mExecutor.execute { JamiService.setAccountDetails(accountId, StringMap.toSwig(map)) }
+    }
+
+    fun migrateAccount(accountId: String, password: String): Single<String?> {
+        return mMigrationSubject
+            .filter { r: MigrationResult -> r.accountId == accountId }
+            .map { r: MigrationResult -> r.state }
+            .firstOrError()
+            .doOnSubscribe {
+                val details = getAccount(accountId)!!.details
+                details[ConfigKey.ARCHIVE_PASSWORD.key()] = password
+                mExecutor.execute { JamiService.setAccountDetails(accountId, StringMap.toSwig(details)) }
+            }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun setAccountEnabled(accountId: String?, active: Boolean) {
+        mExecutor.execute { JamiService.sendRegister(accountId, active) }
+    }
+
+    /**
+     * Sets the activation state of the account in the Daemon
+     */
+    fun setAccountActive(accountId: String?, active: Boolean) {
+        mExecutor.execute { JamiService.setAccountActive(accountId, active) }
+    }
+
+    /**
+     * Sets the activation state of all the accounts in the Daemon
+     */
+    fun setAccountsActive(active: Boolean) {
+        mExecutor.execute {
+            Log.i(TAG, "setAccountsActive() running... $active")
+            synchronized(mAccountList) {
+                for (a in mAccountList) {
+                    // If the proxy is enabled we can considered the account
+                    // as always active
+                    if (a.isDhtProxyEnabled) {
+                        JamiService.setAccountActive(a.accountID, true)
+                    } else {
+                        JamiService.setAccountActive(a.accountID, active)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the video activation state of all the accounts in the local cache
+     */
+    fun setAccountsVideoEnabled(isEnabled: Boolean) {
+        synchronized(mAccountList) {
+            for (account in mAccountList) {
+                account.setDetail(ConfigKey.VIDEO_ENABLED, isEnabled)
+            }
+        }
+    }
+
+    /**
+     * @return the default template (account details) for a type of account
+     */
+    fun getAccountTemplate(accountType: String): Single<HashMap<String, String>> {
+        Log.i(TAG, "getAccountTemplate() $accountType")
+        return Single.fromCallable { JamiService.getAccountTemplate(accountType).toNative() }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    /**
+     * Removes the account in the Daemon as well as local history
+     */
+    fun removeAccount(accountId: String) {
+        Log.i(TAG, "removeAccount() $accountId")
+        mExecutor.execute { JamiService.removeAccount(accountId) }
+        mHistoryService.clearHistory(accountId).subscribe()
+    }
+
+    /**
+     * Exports the account on the DHT (used for multi-devices feature)
+     */
+    fun exportOnRing(accountId: String, password: String): Single<String> {
+        return mExportSubject
+            .filter { r: ExportOnRingResult -> r.accountId == accountId }
+            .firstOrError()
+            .map { result: ExportOnRingResult ->
+                when (result.code) {
+                    PIN_GENERATION_SUCCESS -> return@map result.pin!!
+                    PIN_GENERATION_WRONG_PASSWORD -> throw IllegalArgumentException()
+                    PIN_GENERATION_NETWORK_ERROR -> throw SocketException()
+                    else -> throw UnsupportedOperationException()
+                }
+            }
+            .doOnSubscribe {
+                Log.i(TAG, "exportOnRing() $accountId")
+                mExecutor.execute { JamiService.exportOnRing(accountId, password) }
+            }
+            .subscribeOn(Schedulers.io())
+    }
+
+    /**
+     * @return the list of the account's devices from the Daemon
+     */
+    fun getKnownRingDevices(accountId: String): Map<String, String> {
+        Log.i(TAG, "getKnownRingDevices() $accountId")
+        return try {
+             mExecutor.submit<HashMap<String, String>> {
+                JamiService.getKnownRingDevices(accountId).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getKnownRingDevices()", e)
+            return HashMap()
+        }
+    }
+
+    /**
+     * @param accountId id of the account used with the device
+     * @param deviceId  id of the device to revoke
+     * @param password  password of the account
+     */
+    fun revokeDevice(accountId: String, password: String, deviceId: String): Single<Int> {
+        return mDeviceRevocationSubject
+            .filter { r: DeviceRevocationResult -> r.accountId == accountId && r.deviceId == deviceId }
+            .firstOrError()
+            .map { r: DeviceRevocationResult -> r.code }
+            .doOnSubscribe { mExecutor.execute {
+                JamiService.revokeDevice(accountId, password, deviceId)
+            }}
+            .subscribeOn(Schedulers.io())
+    }
+
+    /**
+     * @param accountId id of the account used with the device
+     * @param newName   new device name
+     */
+    fun renameDevice(accountId: String, newName: String) {
+        val account = getAccount(accountId)
+        mExecutor.execute {
+            Log.i(TAG, "renameDevice() thread running... $newName")
+            val details = JamiService.getAccountDetails(accountId)
+            details[ConfigKey.ACCOUNT_DEVICE_NAME.key()] = newName
+            JamiService.setAccountDetails(accountId, details)
+            account!!.setDetail(ConfigKey.ACCOUNT_DEVICE_NAME, newName)
+            account.devices = JamiService.getKnownRingDevices(accountId).toNative()
+        }
+    }
+
+    fun exportToFile(accountId: String, absolutePath: String, password: String): Completable {
+        return Completable.fromAction {
+            require(JamiService.exportToFile(accountId, absolutePath, password)) { "Can't export archive" }
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    /**
+     * @param accountId   id of the account
+     * @param oldPassword old account password
+     */
+    fun setAccountPassword(accountId: String, oldPassword: String, newPassword: String): Completable {
+        return Completable.fromAction {
+            require(JamiService.changeAccountPassword(accountId, oldPassword, newPassword)) { "Can't change password" }
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    /**
+     * Sets the active codecs list of the account in the Daemon
+     */
+    fun setActiveCodecList(accountId: String, codecs: List<Long>) {
+        mExecutor.execute {
+            val list = UintVect()
+            list.reserve(codecs.size.toLong())
+            list.addAll(codecs)
+            JamiService.setActiveCodecList(accountId, list)
+            observableAccounts.onNext(getAccount(accountId))
+        }
+    }
+
+    /**
+     * @return The account's codecs list from the Daemon
+     */
+    fun getCodecList(accountId: String): Single<List<Codec>> {
+        return Single.fromCallable<List<Codec>> {
+            val results: MutableList<Codec> = ArrayList()
+            val payloads = JamiService.getCodecList()
+            val activePayloads = JamiService.getActiveCodecList(accountId)
+            for (i in payloads.indices) {
+                val details = JamiService.getCodecDetails(accountId, payloads[i])
+                if (details.size > 1) {
+                    results.add(Codec(payloads[i], details.toNative(), activePayloads.contains(payloads[i])))
+                } else {
+                    Log.i(TAG, "Error loading codec $i")
+                }
+            }
+            results
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun validateCertificatePath(
+        accountID: String?,
+        certificatePath: String?,
+        privateKeyPath: String?,
+        privateKeyPass: String?
+    ): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "validateCertificatePath() running...")
+                JamiService.validateCertificatePath(
+                    accountID,
+                    certificatePath,
+                    privateKeyPath,
+                    privateKeyPass,
+                    ""
+                ).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running validateCertificatePath()", e)
+        }
+        return null
+    }
+
+    fun validateCertificate(accountId: String?, certificate: String?): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "validateCertificate() running...")
+                JamiService.validateCertificate(accountId, certificate).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running validateCertificate()", e)
+        }
+        return null
+    }
+
+    fun getCertificateDetailsPath(certificatePath: String?): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "getCertificateDetailsPath() running...")
+                JamiService.getCertificateDetails(certificatePath).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getCertificateDetailsPath()", e)
+        }
+        return null
+    }
+
+    fun getCertificateDetails(certificateRaw: String?): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "getCertificateDetails() running...")
+                JamiService.getCertificateDetails(certificateRaw).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getCertificateDetails()", e)
+        }
+        return null
+    }
+
+    /**
+     * @return the supported TLS methods from the Daemon
+     */
+    val tlsSupportedMethods: List<String>
+        get() {
+            Log.i(TAG, "getTlsSupportedMethods()")
+            return SwigNativeConverter.toJava(JamiService.getSupportedTlsMethod())
+        }
+
+    /**
+     * @return the account's credentials from the Daemon
+     */
+    fun getCredentials(accountId: String?): List<Map<String, String>>? {
+        try {
+            return mExecutor.submit<ArrayList<Map<String, String>>> {
+                Log.i(TAG, "getCredentials() running...")
+                JamiService.getCredentials(accountId).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getCredentials()", e)
+        }
+        return null
+    }
+
+    /**
+     * Sets the account's credentials in the Daemon
+     */
+    fun setCredentials(accountId: String, credentials: List<Map<String, String>>) {
+        Log.i(TAG, "setCredentials() $accountId")
+        mExecutor.execute { JamiService.setCredentials(accountId, SwigNativeConverter.toSwig(credentials)) }
+    }
+
+    /**
+     * Sets the registration state to true for all the accounts in the Daemon
+     */
+    fun registerAllAccounts() {
+        Log.i(TAG, "registerAllAccounts()")
+        mExecutor.execute { registerAllAccounts() }
+    }
+
+    /**
+     * Registers a new name on the blockchain for the account
+     */
+    fun registerName(account: Account, password: String?, name: String?) {
+        if (account.registeringUsername) {
+            Log.w(TAG, "Already trying to register username")
+            return
+        }
+        account.registeringUsername = true
+        registerName(account.accountID, password ?: "", name)
+    }
+
+    /**
+     * Register a new name on the blockchain for the account Id
+     */
+    fun registerName(account: String?, password: String?, name: String?) {
+        Log.i(TAG, "registerName()")
+        mExecutor.execute { JamiService.registerName(account, password, name) }
+    }
+    /* contact requests */
+    /**
+     * @return all trust requests from the daemon for the account Id
+     */
+    fun getTrustRequests(accountId: String?): List<Map<String, String>>? {
+        try {
+            return mExecutor.submit<ArrayList<Map<String, String>>> {
+                JamiService.getTrustRequests(
+                    accountId
+                ).toNative()
+            }
+                .get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getTrustRequests()", e)
+        }
+        return null
+    }
+
+    /**
+     * Accepts a pending trust request
+     */
+    fun acceptTrustRequest(accountId: String, from: Uri) {
+        Log.i(TAG, "acceptRequest() $accountId $from")
+        getAccount(accountId)?.let { account -> account.removeRequest(from)?.vCard?.let{ vcard ->
+            VCardUtils.savePeerProfileToDisk(vcard, accountId, from.rawRingId + ".vcf", mDeviceRuntimeService.provideFilesDir())
+        }}
+        mExecutor.execute { JamiService.acceptTrustRequest(accountId, from.rawRingId) }
+    }
+
+    /**
+     * Handles adding contacts and is the initial point of conversation creation
+     *
+     * @param conversation    the user's account
+     * @param contactUri the contacts raw string uri
+     */
+    private fun handleTrustRequest(conversation: Conversation, contactUri: Uri, request: TrustRequest?, type: ContactType) {
+        val event = ContactEvent()
+        when (type) {
+            ContactType.ADDED -> {
+            }
+            ContactType.INVITATION_RECEIVED -> {
+                event.status = InteractionStatus.UNKNOWN
+                event.author = contactUri.rawRingId
+                event.timestamp = request!!.timestamp
+            }
+            ContactType.INVITATION_ACCEPTED -> {
+                event.status = InteractionStatus.SUCCESS
+                event.author = contactUri.rawRingId
+            }
+            ContactType.INVITATION_DISCARDED -> {
+                mHistoryService.clearHistory(contactUri.rawRingId, conversation.accountId, true)
+                    .subscribe()
+                return
+            }
+            else -> return
+        }
+        mHistoryService.insertInteraction(conversation.accountId, conversation, event).subscribe()
+    }
+
+    private enum class ContactType {
+        ADDED, INVITATION_RECEIVED, INVITATION_ACCEPTED, INVITATION_DISCARDED
+    }
+
+    /**
+     * Refuses and blocks a pending trust request
+     */
+    fun discardTrustRequest(accountId: String, contactUri: Uri): Boolean {
+        val account = getAccount(accountId)
+        var removed = false
+        if (account != null) {
+            removed = account.removeRequest(contactUri) != null
+            mHistoryService.clearHistory(contactUri.rawRingId, accountId, true).subscribe()
+        }
+        mExecutor.execute { JamiService.discardTrustRequest(accountId, contactUri.rawRingId) }
+        return removed
+    }
+
+    /**
+     * Sends a new trust request
+     */
+    fun sendTrustRequest(conversation: Conversation, to: Uri, message: Blob?) {
+        Log.i(TAG, "sendTrustRequest() " + conversation.accountId + " " + to)
+        handleTrustRequest(conversation, to, null, ContactType.ADDED)
+        mExecutor.execute { JamiService.sendTrustRequest(conversation.accountId, to.rawRingId, message ?: Blob()) }
+    }
+
+    /**
+     * Add a new contact for the account Id on the Daemon
+     */
+    fun addContact(accountId: String, uri: String) {
+        Log.i(TAG, "addContact() $accountId $uri")
+        //handleTrustRequest(accountId, Uri.fromString(uri), null, ContactType.ADDED);
+        mExecutor.execute { JamiService.addContact(accountId, uri) }
+    }
+
+    /**
+     * Remove an existing contact for the account Id on the Daemon
+     */
+    fun removeContact(accountId: String, uri: String, ban: Boolean) {
+        Log.i(TAG, "removeContact() $accountId $uri ban:$ban")
+        mExecutor.execute { JamiService.removeContact(accountId, uri, ban) }
+    }
+
+    /**
+     * @return the contacts list from the daemon
+     */
+    fun getContacts(accountId: String?): List<Map<String, String>>? {
+        try {
+            return mExecutor.submit<ArrayList<Map<String, String>>> {
+                JamiService.getContacts(
+                    accountId
+                ).toNative()
+            }
+                .get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getContacts()", e)
+        }
+        return null
+    }
+
+    /**
+     * Looks up for the availability of the name on the blockchain
+     */
+    fun lookupName(account: String, nameserver: String, name: String) {
+        Log.i(TAG, "lookupName() $account $nameserver $name")
+        mExecutor.execute { JamiService.lookupName(account, nameserver, name) }
+    }
+
+    fun findRegistrationByName(account: String, nameserver: String, name: String): Single<RegisteredName> {
+        return if (name.isEmpty()) {
+            Single.just(RegisteredName(account, name))
+        } else registeredNames
+            .filter { r: RegisteredName -> account == r.accountId && name == r.name }
+            .firstOrError()
+            .doOnSubscribe {
+                mExecutor.execute { JamiService.lookupName(account, nameserver, name) }
+            }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun searchUser(account: String, query: String): Single<UserSearchResult> {
+        if (StringUtils.isEmpty(query)) {
+            return Single.just(UserSearchResult(account, query))
+        }
+        val encodedUrl: String = try {
+            URLEncoder.encode(query, "UTF-8")
+        } catch (e: UnsupportedEncodingException) {
+            return Single.error(e)
+        }
+        return searchResults
+            .filter { r: UserSearchResult -> account == r.accountId && encodedUrl == r.query }
+            .firstOrError()
+            .doOnSubscribe {
+                mExecutor.execute { JamiService.searchUser(account, encodedUrl) }
+            }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    /**
+     * Reverse looks up the address in the blockchain to find the name
+     */
+    fun lookupAddress(account: String?, nameserver: String?, address: String?) {
+        mExecutor.execute { JamiService.lookupAddress(account, nameserver, address) }
+    }
+
+    fun pushNotificationReceived(from: String?, data: Map<String?, String?>?) {
+        // Log.i(TAG, "pushNotificationReceived()");
+        mExecutor.execute { JamiService.pushNotificationReceived(from, StringMap.toSwig(data)) }
+    }
+
+    fun setPushNotificationToken(pushNotificationToken: String?) {
+        //Log.i(TAG, "setPushNotificationToken()");
+        mExecutor.execute { JamiService.setPushNotificationToken(pushNotificationToken) }
+    }
+
+    fun volumeChanged(device: String, value: Int) {
+        Log.w(TAG, "volumeChanged $device $value")
+    }
+
+    fun accountsChanged() {
+        // Accounts have changed in Daemon, we have to update our local cache
+        refreshAccountsCacheFromDaemon()
+    }
+
+    fun stunStatusFailure(accountId: String) {
+        Log.d(TAG, "stun status failure: $accountId")
+    }
+
+    fun registrationStateChanged(accountId: String, newState: String, code: Int, detailString: String?) {
+        //Log.d(TAG, "registrationStateChanged: " + accountId + ", " + newState + ", " + code + ", " + detailString);
+        val account = getAccount(accountId) ?: return
+        val oldState = account.registrationState
+        if (oldState.contentEquals(AccountConfig.STATE_INITIALIZING) && !newState.contentEquals(AccountConfig.STATE_INITIALIZING)) {
+            account.setDetails(JamiService.getAccountDetails(account.accountID).toNative())
+            account.setCredentials(JamiService.getCredentials(account.accountID).toNative())
+            account.devices = JamiService.getKnownRingDevices(account.accountID).toNative()
+            account.setVolatileDetails(JamiService.getVolatileAccountDetails(account.accountID).toNative())
+        } else {
+            account.setRegistrationState(newState, code)
+        }
+        if (oldState != newState) {
+            observableAccounts.onNext(account)
+        }
+    }
+
+    fun accountDetailsChanged(accountId: String, details: Map<String, String>) {
+        val account = getAccount(accountId) ?: return
+        Log.d(TAG, "accountDetailsChanged: " + accountId + " " + details.size)
+        account.setDetails(details)
+        observableAccounts.onNext(account)
+    }
+
+    fun volatileAccountDetailsChanged(accountId: String, details: Map<String, String>) {
+        val account = getAccount(accountId) ?: return
+        //Log.d(TAG, "volatileAccountDetailsChanged: " + accountId + " " + details.size());
+        account.setVolatileDetails(details)
+        observableAccounts.onNext(account)
+    }
+
+    fun accountProfileReceived(accountId: String, name: String?, photo: String?) {
+        val account = getAccount(accountId) ?: return
+        mVCardService.saveVCardProfile(accountId, account.uri, name, photo)
+            .subscribeOn(Schedulers.io())
+            .subscribe({ account.resetProfile() }) { e -> Log.e(TAG, "Error saving profile", e) }
+    }
+
+    fun profileReceived(accountId: String, peerId: String, vcardPath: String) {
+        val account = getAccount(accountId) ?: return
+        Log.w(TAG, "profileReceived: $accountId, $peerId, $vcardPath")
+        val contact = account.getContactFromCache(peerId)
+        mVCardService.peerProfileReceived(accountId, peerId, File(vcardPath))
+            .subscribe({ profile: Tuple<String?, Any?> ->
+                contact.setProfile(profile.first, profile.second)
+            }) { e -> Log.e(TAG, "Error saving contact profile", e) }
+    }
+
+    fun incomingAccountMessage(accountId: String, messageId: String?, callId: String?, from: String, messages: Map<String, String>) {
+        Log.d(TAG, "incomingAccountMessage: " + accountId + " " + messages.size)
+        incomingMessageSubject.onNext(Message(accountId, messageId, callId, from, messages))
+    }
+
+    fun accountMessageStatusChanged(
+        accountId: String,
+        conversationId: String,
+        messageId: String,
+        peer: String,
+        status: Int
+    ) {
+        val newStatus = InteractionStatus.fromIntTextMessage(status)
+        Log.d(TAG, "accountMessageStatusChanged: $accountId, $conversationId, $messageId, $peer, $newStatus")
+        if (StringUtils.isEmpty(conversationId)) {
+            mHistoryService
+                .accountMessageStatusChanged(accountId, messageId, peer, newStatus)
+                .subscribe({ t: TextMessage -> messageSubject.onNext(t) }) { e: Throwable ->
+                    Log.e(TAG, "Error updating message: " + e.localizedMessage) }
+        } else {
+            val msg = Interaction(accountId)
+            msg.status = newStatus
+            msg.setSwarmInfo(conversationId, messageId, null)
+            messageSubject.onNext(msg)
+        }
+    }
+
+    fun composingStatusChanged(
+        accountId: String,
+        conversationId: String,
+        contactUri: String,
+        status: Int
+    ) {
+        Log.d(TAG, "composingStatusChanged: $accountId, $contactUri, $conversationId, $status")
+        getAccountSingle(accountId)
+            .subscribe { account: Account ->
+                account.composingStatusChanged(
+                    conversationId,
+                    Uri.fromId(contactUri),
+                    Account.ComposingStatus.fromInt(status)
+                )
+            }
+    }
+
+    fun errorAlert(alert: Int) {
+        Log.d(TAG, "errorAlert : $alert")
+    }
+
+    fun knownDevicesChanged(accountId: String, devices: Map<String, String>) {
+        getAccount(accountId)?.let { account ->
+            account.devices = devices
+            observableAccounts.onNext(account)
+        }
+    }
+
+    fun exportOnRingEnded(accountId: String, code: Int, pin: String) {
+        Log.d(TAG, "exportOnRingEnded: $accountId, $code, $pin")
+        mExportSubject.onNext(ExportOnRingResult(accountId, code, pin))
+    }
+
+    fun nameRegistrationEnded(accountId: String, state: Int, name: String) {
+        Log.d(TAG, "nameRegistrationEnded: $accountId, $state, $name")
+        val acc = getAccount(accountId)
+        if (acc == null) {
+            Log.w(TAG, "Can't find account for name registration callback")
+            return
+        }
+        acc.registeringUsername = false
+        acc.setVolatileDetails(JamiService.getVolatileAccountDetails(acc.accountID).toNative())
+        if (state == 0) {
+            acc.setDetail(ConfigKey.ACCOUNT_REGISTERED_NAME, name)
+        }
+        observableAccounts.onNext(acc)
+    }
+
+    fun migrationEnded(accountId: String, state: String) {
+        Log.d(TAG, "migrationEnded: $accountId, $state")
+        mMigrationSubject.onNext(MigrationResult(accountId, state))
+    }
+
+    fun deviceRevocationEnded(accountId: String, device: String, state: Int) {
+        Log.d(TAG, "deviceRevocationEnded: $accountId, $device, $state")
+        if (state == 0) {
+            getAccount(accountId)?.let { account ->
+                val devices = HashMap(account.devices)
+                devices.remove(device)
+                account.devices = devices
+                observableAccounts.onNext(account)
+            }
+        }
+        mDeviceRevocationSubject.onNext(DeviceRevocationResult(accountId, device, state))
+    }
+
+    fun incomingTrustRequest(accountId: String, conversationId: String,from: String, message: String?, received: Long) {
+        Log.d(TAG, "incomingTrustRequest: $accountId, $conversationId, $from, $received")
+        val account = getAccount(accountId)
+        if (account != null) {
+            val fromUri = Uri.fromString(from)
+            var request = account.getRequest(fromUri)
+            if (request == null) request = TrustRequest(
+                accountId,
+                fromUri,
+                received * 1000L,
+                message,
+                conversationId
+            ) else request.vCard = Ezvcard.parse(message).first()
+            val vcard = request.vCard
+            if (vcard != null) {
+                val contact = account.getContactFromCache(fromUri)
+                if (!contact.detailsLoaded) {
+                    // VCardUtils.savePeerProfileToDisk(vcard, accountId, from + ".vcf", mDeviceRuntimeService.provideFilesDir());
+                    mVCardService.loadVCardProfile(vcard)
+                        .subscribeOn(Schedulers.computation())
+                        .subscribe { profile: Tuple<String?, Any?> ->
+                            contact.setProfile(
+                                profile.first,
+                                profile.second
+                            )
+                        }
+                }
+            }
+            account.addRequest(request)
+            // handleTrustRequest(account, Uri.fromString(from), request, ContactType.INVITATION_RECEIVED);
+            if (account.isEnabled) lookupAddress(accountId, "", from)
+            incomingRequestsSubject.onNext(request)
+        }
+    }
+
+    fun contactAdded(accountId: String, uri: String, confirmed: Boolean) {
+        val account = getAccount(accountId)
+        if (account != null) {
+            val details: Map<String, String> = JamiService.getContactDetails(accountId, uri)
+            val contact = account.addContact(details)
+            val conversationUri = contact.conversationUri.blockingFirst()
+            if (conversationUri.isSwarm) {
+                var conversation = account.getSwarm(conversationUri.rawRingId)
+                if (conversation == null) {
+                    conversation = account.newSwarm(conversationUri.rawRingId, Conversation.Mode.Syncing)
+                    conversation.addContact(contact)
+                }
+            }
+            //account.addContact(uri, confirmed);
+            if (account.isEnabled) lookupAddress(accountId, "", uri)
+        }
+    }
+
+    fun contactRemoved(accountId: String, uri: String, banned: Boolean) {
+        Log.d(TAG, "Contact removed: $uri User is banned: $banned")
+        getAccount(accountId)?.let { account ->
+            mHistoryService.clearHistory(uri, accountId, true).subscribe()
+            account.removeContact(uri, banned)
+        }
+    }
+
+    fun registeredNameFound(accountId: String, state: Int, address: String, name: String) {
+        try {
+            //Log.d(TAG, "registeredNameFound: " + accountId + ", " + state + ", " + name + ", " + address);
+            if (address.isNotEmpty()) {
+                getAccount(accountId)?.registeredNameFound(state, address, name)
+            }
+            registeredNameSubject.onNext(RegisteredName(accountId, name, address, state))
+        } catch (e: Exception) {
+            Log.w(TAG, "registeredNameFound exception", e)
+        }
+    }
+
+    fun userSearchEnded(accountId: String, state: Int, query: String, results: ArrayList<Map<String, String>>) {
+        val account = getAccount(accountId)!!
+        val r = UserSearchResult(accountId, query, state)
+        val contacts = ArrayList<Contact>(results.size)
+        for (m in results) {
+            val uri = m["id"]!!
+            val username = m["username"]
+            val firstName = m["firstName"]
+            val lastName = m["lastName"]
+            val picture_b64 = m["profilePicture"]
+            val contact = account.getContactFromCache(uri)
+            if (username != null)
+                contact.setUsername(username)
+            contact.setProfile("$firstName $lastName", mVCardService.base64ToBitmap(picture_b64))
+            contacts.add(contact)
+        }
+        r.results = contacts
+        searchResultSubject.onNext(r)
+    }
+
+    private fun addMessage(
+        account: Account,
+        conversation: Conversation,
+        message: Map<String, String>
+    ): Interaction {
+        for ((key, value) in message) {
+            Log.w(TAG, "$key -> $value")
+        }
+        val id = message["id"]!!
+        val type = message["type"]!!
+        val author = message["author"]!!
+        val parent = message["linearizedParent"]
+        val authorUri = Uri.fromId(author)
+        val timestamp = message["timestamp"]!!.toLong() * 1000
+        val contact = conversation.findContact(authorUri) ?: account.getContactFromCache(authorUri)
+        var interaction: Interaction
+        when (type) {
+            "initial" -> {
+                if (conversation.mode.blockingFirst() == Conversation.Mode.OneToOne) {
+                    val invited = message["invited"]!!
+                    var invitedContact = conversation.findContact(Uri.fromId(invited))
+                    if (invitedContact == null) {
+                        invitedContact = account.getContactFromCache(invited)
+                    }
+                    invitedContact.addedDate = Date(timestamp)
+                    interaction = ContactEvent(invitedContact).setEvent(ContactEvent.Event.fromConversationAction("add"))
+                } else {
+                    interaction = Interaction(conversation, Interaction.InteractionType.INVALID)
+                }
+            }
+            "member" -> {
+                val action = message["action"]!!
+                val uri = message["uri"]!!
+                var member = conversation.findContact(Uri.fromId(uri))
+                if (member == null) {
+                    member = account.getContactFromCache(uri)
+                }
+                member.addedDate = Date(timestamp)
+                interaction = ContactEvent(member).setEvent(ContactEvent.Event.fromConversationAction(action))
+            }
+            "text/plain" -> interaction = TextMessage(author, account.accountID, timestamp, conversation, message["body"]!!, !contact.isUser)
+            "application/data-transfer+json" -> {
+                try {
+                    val fileName = message["displayName"]!!
+                    val fileId = message["fileId"]
+                    //interaction = account.getDataTransfer(fileId);
+                    //if (interaction == null) {
+                    val paths = arrayOfNulls<String>(1)
+                    val progressA = LongArray(1)
+                    val totalA = LongArray(1)
+                    JamiService.fileTransferInfo(account.accountID, conversation.uri.rawRingId, fileId, paths, totalA, progressA)
+                    if (totalA[0] == 0L) {
+                        totalA[0] = message["totalSize"]!!.toLong()
+                    }
+                    val path = File(paths[0]!!)
+                    interaction = DataTransfer(fileId, account.accountID, author, fileName, contact.isUser, timestamp, totalA[0], progressA[0])
+                    interaction.daemonPath = path
+                    val isComplete = path.exists() && progressA[0] == totalA[0]
+                    Log.w(TAG, "add DataTransfer at " + paths[0] + " with progress " + progressA[0] + "/" + totalA[0])
+                    interaction.status = if (isComplete) InteractionStatus.TRANSFER_FINISHED else InteractionStatus.FILE_AVAILABLE
+                    //}
+                } catch (e: Exception) {
+                    interaction = Interaction(conversation, Interaction.InteractionType.INVALID)
+                }
+            }
+            "application/call-history+json" -> {
+                interaction = Call(null, account.accountID, authorUri.rawUriString, if (contact.isUser) Call.Direction.OUTGOING else Call.Direction.INCOMING,timestamp)
+                interaction.duration = message["duration"]!!.toLong()
+            }
+            "merge" -> interaction = Interaction(conversation, Interaction.InteractionType.INVALID)
+            else -> interaction = Interaction(conversation, Interaction.InteractionType.INVALID)
+        }
+        interaction.contact = contact
+        interaction.setSwarmInfo(conversation.uri.rawRingId, id, if (StringUtils.isEmpty(parent)) null else parent)
+        interaction.conversation = conversation
+        if (conversation.addSwarmElement(interaction)) {
+            if (conversation.isVisible)
+                mHistoryService.setMessageRead(account.accountID, conversation.uri, interaction.messageId)
+        }
+        return interaction
+    }
+
+    fun conversationLoaded(accountId: String, conversationId: String, messages: List<Map<String, String>>) {
+        try {
+            // Log.w(TAG, "ConversationCallback: conversationLoaded " + accountId + "/" + conversationId + " " + messages.size());
+            getAccount(accountId)?.let { account -> account.getSwarm(conversationId)?.let { conversation ->
+                synchronized(conversation) {
+                    for (message in messages) {
+                        addMessage(account, conversation, message)
+                    }
+                    conversation.stopLoading()
+                }
+                account.conversationChanged()
+            }}
+        } catch (e: Exception) {
+            Log.e(TAG, "Exception loading message", e)
+        }
+    }
+
+    private enum class ConversationMemberEvent {
+        Add, Join, Remove, Ban
+    }
+
+    fun conversationMemberEvent(accountId: String, conversationId: String, peerUri: String, event: Int) {
+        Log.w(TAG, "ConversationCallback: conversationMemberEvent $accountId/$conversationId")
+        getAccount(accountId)?.let { account -> account.getSwarm(conversationId)?.let { conversation ->
+            val uri = Uri.fromId(peerUri)
+            when (ConversationMemberEvent.values()[event]) {
+                ConversationMemberEvent.Add, ConversationMemberEvent.Join -> {
+                    val contact = conversation.findContact(uri)
+                    if (contact == null) {
+                        conversation.addContact(account.getContactFromCache(uri))
+                    }
+                }
+                ConversationMemberEvent.Remove, ConversationMemberEvent.Ban -> {
+                    if (conversation.mode.blockingFirst() != Conversation.Mode.OneToOne) {
+                        conversation.findContact(uri)?.let { contact -> conversation.removeContact(contact) }
+                    }
+                }
+            }
+        }}
+    }
+
+    fun conversationReady(accountId: String, conversationId: String) {
+        Log.w(TAG, "ConversationCallback: conversationReady $accountId/$conversationId")
+        val account = getAccount(accountId)
+        if (account == null) {
+            Log.w(TAG, "conversationReady: can't find account")
+            return
+        }
+        val info = JamiService.conversationInfos(accountId, conversationId)
+        /*for (Map.Entry<String, String> i : info.entrySet()) {
+            Log.w(TAG, "conversation info: " + i.getKey() + " " + i.getValue());
+        }*/
+        val modeInt = info["mode"]!!.toInt()
+        val mode = Conversation.Mode.values()[modeInt]
+        var c = account.getSwarm(conversationId)
+        var setMode = false
+        if (c == null) {
+            c = account.newSwarm(conversationId, mode)
+        } else {
+            setMode = mode != c.mode.blockingFirst()
+        }
+        val conversation = c
+        synchronized(conversation) {
+            // Making sure to add contacts before changing the mode
+            for (member in JamiService.getConversationMembers(accountId, conversationId)) {
+                val uri = Uri.fromId(member["uri"]!!)
+                var contact = conversation.findContact(uri)
+                if (contact == null) {
+                    contact = account.getContactFromCache(uri)
+                    conversation.addContact(contact)
+                }
+            }
+            if (conversation.lastElementLoaded == null) conversation.lastElementLoaded =
+                Completable.defer { loadMore(conversation, 2).ignoreElement() }
+                    .cache()
+            if (setMode) conversation.setMode(mode)
+        }
+        account.conversationStarted(conversation)
+        loadMore(conversation, 2)
+    }
+
+    fun conversationRemoved(accountId: String, conversationId: String) {
+        val account = getAccount(accountId)
+        if (account == null) {
+            Log.w(TAG, "conversationRemoved: can't find account")
+            return
+        }
+        account.removeSwarm(conversationId)
+    }
+
+    fun conversationRequestDeclined(accountId: String, conversationId: String) {
+        Log.d(TAG, "conversation's request for $conversationId is declined")
+        val account = getAccount(accountId)
+        if (account == null) {
+            Log.w(TAG, "conversationRequestDeclined: can't find account")
+            return
+        }
+        account.removeRequestPerConvId(conversationId)
+    }
+
+    fun conversationRequestReceived(accountId: String, conversationId: String, metadata: Map<String, String>) {
+        Log.w(TAG, "ConversationCallback: conversationRequestReceived " + accountId + "/" + conversationId + " " + metadata.size)
+        val account = getAccount(accountId)
+        if (account == null) {
+            Log.w(TAG, "conversationRequestReceived: can't find account")
+            return
+        }
+        val contactUri = Uri.fromId(metadata["from"]!!)
+        val request = account.getRequest(contactUri)
+        if (request == null || conversationId != request.conversationId) {
+            val received = metadata["received"]
+            account.addRequest(TrustRequest(account.accountID, contactUri, java.lang.Long.decode(received) * 1000L, null, conversationId))
+        }
+    }
+
+    fun messageReceived(accountId: String, conversationId: String, message: Map<String, String>) {
+        Log.w(TAG, "ConversationCallback: messageReceived " + accountId + "/" + conversationId + " " + message.size)
+        getAccount(accountId)?.let { account -> account.getSwarm(conversationId)?.let { conversation ->
+            synchronized(conversation) {
+                val interaction = addMessage(account, conversation, message)
+                account.conversationUpdated(conversation)
+                val isIncoming = !interaction.contact!!.isUser
+                if (isIncoming)
+                    incomingSwarmMessageSubject.onNext(interaction)
+                if (interaction is DataTransfer)
+                    dataTransfers.onNext(interaction)
+            }
+        }}
+    }
+
+    fun sendFile(file: File, dataTransfer: DataTransfer): Single<DataTransfer> {
+        return Single.fromCallable {
+            mStartingTransfer = dataTransfer
+            val dataTransferInfo = DataTransferInfo()
+            dataTransferInfo.accountId = dataTransfer.account
+            val conversationId = dataTransfer.conversationId
+            if (!StringUtils.isEmpty(conversationId))
+                dataTransferInfo.conversationId = conversationId
+            else
+                dataTransferInfo.peer = dataTransfer.conversation?.participant
+            dataTransferInfo.path = file.absolutePath
+            dataTransferInfo.displayName = dataTransfer.displayName
+            Log.i(TAG, "sendFile() id=" + dataTransfer.id + " accountId=" + dataTransferInfo.accountId + ", peer=" + dataTransferInfo.peer + ", filePath=" + dataTransferInfo.path)
+            val id = LongArray(1)
+            val err = getDataTransferError(JamiService.sendFileLegacy(dataTransferInfo, id))
+            if (err != DataTransferError.SUCCESS) {
+                throw IOException(err.name)
+            } else {
+                Log.e(TAG, "sendFile: got ID " + id[0])
+                dataTransfer.daemonId = id[0]
+            }
+            dataTransfer
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun sendFile(conversation: Conversation, file: File) {
+        mExecutor.execute { JamiService.sendFile(conversation.accountId, conversation.uri.rawRingId,file.absolutePath, file.name, "") }
+    }
+
+    fun getLastMessages(accountId: String?, baseTime: Long): List<net.jami.daemon.Message> {
+        try {
+            return mExecutor.submit(Callable {
+                SwigNativeConverter.toJava(JamiService.getLastMessages(accountId, baseTime))
+            }).get()
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        return ArrayList()
+    }
+
+    fun acceptFileTransfer(accountId: String, conversationUri: Uri, messageId: String?, fileId: String) {
+        getAccount(accountId)?.let { account -> account.getByUri(conversationUri)?.let { conversation ->
+            val transfer = if (conversation.isSwarm)
+                conversation.getMessage(messageId!!) as DataTransfer?
+            else
+                account.getDataTransfer(fileId)
+            acceptFileTransfer(conversation, fileId, transfer!!)
+        }}
+    }
+
+    fun acceptFileTransfer(conversation: Conversation, fileId: String, transfer: DataTransfer) {
+        if (conversation.isSwarm) {
+            val conversationId = conversation.uri.rawRingId
+            val newPath = mDeviceRuntimeService.getNewConversationPath(conversation.accountId, conversationId, transfer.displayName)
+            Log.i(TAG, "downloadFile() id=" + conversation.accountId + ", path=" + conversationId + " " + fileId + " to -> " + newPath.absolutePath)
+            JamiService.downloadFile(conversation.accountId, conversationId, transfer.messageId, fileId, newPath.absolutePath)
+        } else {
+            val path = mDeviceRuntimeService.getTemporaryPath(conversation.uri.rawRingId, transfer.storagePath)
+            Log.i(TAG, "acceptFileTransfer() id=" + fileId + ", path=" + path.absolutePath)
+            JamiService.acceptFileTransfer(conversation.accountId, fileId, path.absolutePath)
+        }
+    }
+
+    fun cancelDataTransfer(accountId: String, conversationId: String, messageId: String?, fileId: String) {
+        Log.i(TAG, "cancelDataTransfer() id=$fileId")
+        mExecutor.execute { JamiService.cancelDataTransfer(accountId, conversationId, fileId) }
+    }
+
+    private inner class DataTransferRefreshTask constructor(
+        private val mAccount: Account,
+        private val mConversation: Conversation?,
+        private val mToUpdate: DataTransfer
+    ) : Runnable {
+        var scheduledTask: ScheduledFuture<*>? = null
+        override fun run() {
+            synchronized(mToUpdate) {
+                if (mToUpdate.status == InteractionStatus.TRANSFER_ONGOING) {
+                    dataTransferEvent(mAccount, mConversation, mToUpdate.messageId, mToUpdate.fileId!!, 5)
+                } else {
+                    scheduledTask!!.cancel(false)
+                    scheduledTask = null
+                }
+            }
+        }
+    }
+
+    fun dataTransferEvent(accountId: String, conversationId: String, interactionId: String, fileId: String, eventCode: Int) {
+        val account = getAccount(accountId)
+        if (account != null) {
+            val conversation = if (conversationId.isEmpty()) null else account.getSwarm(conversationId)
+            dataTransferEvent(account, conversation, interactionId, fileId, eventCode)
+        }
+    }
+
+    fun dataTransferEvent(account: Account, conversation: Conversation?, interactionId: String?, fileId: String, eventCode: Int) {
+        var conversation = conversation
+        val transferStatus = getDataTransferEventCode(eventCode)
+        Log.d(TAG, "Data Transfer $interactionId $fileId $transferStatus")
+        val from: String
+        val total: Long
+        val progress: Long
+        val displayName: String
+        var transfer = account.getDataTransfer(fileId)
+        var outgoing = false
+        if (conversation == null) {
+            val info = DataTransferInfo()
+            val err =
+                getDataTransferError(JamiService.dataTransferInfo(account.accountID, fileId, info))
+            if (err != DataTransferError.SUCCESS) {
+                Log.d(TAG, "Data Transfer error getting details $err")
+                return
+            }
+            from = info.peer
+            total = info.totalSize
+            progress = info.bytesProgress
+            conversation = account.getByUri(from)
+            outgoing = info.flags == 0L
+            displayName = info.displayName
+        } else {
+            val paths = arrayOfNulls<String>(1)
+            val progressA = LongArray(1)
+            val totalA = LongArray(1)
+            JamiService.fileTransferInfo(account.accountID, conversation.uri.rawRingId, fileId, paths, totalA, progressA)
+            progress = progressA[0]
+            total = totalA[0]
+            if (transfer == null && interactionId != null && interactionId.isNotEmpty()) {
+                transfer = conversation.getMessage(interactionId) as DataTransfer
+            }
+            if (transfer == null) return
+            transfer.conversation = conversation
+            transfer.daemonPath = File(paths[0]!!)
+            from = transfer.author!!
+            displayName = transfer.displayName
+        }
+        if (transfer == null) {
+            val startingTransfer = mStartingTransfer
+            if (outgoing && startingTransfer != null) {
+                Log.d(TAG, "Data Transfer mStartingTransfer")
+                transfer = startingTransfer
+                mStartingTransfer = null
+            } else {
+                transfer = DataTransfer(conversation, from, account.accountID, displayName,
+                    outgoing, total,
+                    progress, fileId
+                )
+                if (conversation!!.isSwarm) {
+                    transfer.setSwarmInfo(conversation.uri.rawRingId, interactionId!!, null)
+                } else {
+                    mHistoryService.insertInteraction(account.accountID, conversation, transfer)
+                        .blockingAwait()
+                }
+            }
+            account.putDataTransfer(fileId, transfer)
+        } else synchronized(transfer) {
+            val oldState = transfer.status
+            if (oldState != transferStatus) {
+                if (transferStatus == InteractionStatus.TRANSFER_ONGOING) {
+                    val task = DataTransferRefreshTask(account, conversation, transfer)
+                    task.scheduledTask = mExecutor.scheduleAtFixedRate(
+                        task,
+                        DATA_TRANSFER_REFRESH_PERIOD,
+                        DATA_TRANSFER_REFRESH_PERIOD, TimeUnit.MILLISECONDS
+                    )
+                } else if (transferStatus.isError) {
+                    if (!transfer.isOutgoing) {
+                        val tmpPath = mDeviceRuntimeService.getTemporaryPath(
+                            conversation!!.uri.rawRingId, transfer.storagePath
+                        )
+                        tmpPath.delete()
+                    }
+                } else if (transferStatus == InteractionStatus.TRANSFER_FINISHED) {
+                    if (!conversation!!.isSwarm && !transfer.isOutgoing) {
+                        val tmpPath = mDeviceRuntimeService.getTemporaryPath(
+                            conversation.uri.rawRingId, transfer.storagePath
+                        )
+                        val path = mDeviceRuntimeService.getConversationPath(
+                            conversation.uri.rawRingId, transfer.storagePath
+                        )
+                        FileUtils.moveFile(tmpPath, path)
+                    }
+                }
+            }
+            transfer.status = transferStatus
+            transfer.bytesProgress = progress
+            if (!conversation!!.isSwarm) {
+                mHistoryService.updateInteraction(transfer, account.accountID).subscribe()
+            }
+        }
+        Log.d(TAG, "Data Transfer dataTransferSubject.onNext")
+        dataTransfers.onNext(transfer)
+    }
+
+    fun observeDataTransfer(transfer: DataTransfer): Observable<DataTransfer?> {
+        return dataTransfers
+            .filter { t: DataTransfer? -> t === transfer }
+            .startWithItem(transfer)
+    }
+
+    fun setProxyEnabled(enabled: Boolean) {
+        mExecutor.execute {
+            synchronized(mAccountList) {
+                for (acc in mAccountList) {
+                    if (acc.isJami && acc.isDhtProxyEnabled != enabled) {
+                        Log.d(TAG, (if (enabled) "Enabling" else "Disabling") + " proxy for account " + acc.accountID)
+                        acc.isDhtProxyEnabled = enabled
+                        val details = JamiService.getAccountDetails(acc.accountID)
+                        details[ConfigKey.PROXY_ENABLED.key()] = if (enabled) "true" else "false"
+                        JamiService.setAccountDetails(acc.accountID, details)
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val TAG = AccountService::class.java.simpleName
+        private const val VCARD_CHUNK_SIZE = 1000
+        private const val DATA_TRANSFER_REFRESH_PERIOD: Long = 500
+        private const val PIN_GENERATION_SUCCESS = 0
+        private const val PIN_GENERATION_WRONG_PASSWORD = 1
+        private const val PIN_GENERATION_NETWORK_ERROR = 2
+        private fun findAccount(accounts: List<Account?>, accountId: String): Account? {
+            for (account in accounts) if (accountId == account!!.accountID) return account
+            return null
+        }
+
+        private fun getDataTransferEventCode(eventCode: Int): InteractionStatus {
+            var dataTransferEventCode = InteractionStatus.INVALID
+            try {
+                dataTransferEventCode = InteractionStatus.fromIntFile(eventCode)
+            } catch (ignored: ArrayIndexOutOfBoundsException) {
+                Log.e(TAG, "getEventCode: invalid data transfer status from daemon")
+            }
+            return dataTransferEventCode
+        }
+
+        private fun getDataTransferError(errorCode: Long?): DataTransferError {
+            if (errorCode == null) {
+                Log.e(TAG, "getDataTransferError: invalid error code")
+            } else {
+                try {
+                    return DataTransferError.values()[errorCode.toInt()]
+                } catch (ignored: ArrayIndexOutOfBoundsException) {
+                    Log.e(TAG, "getDataTransferError: invalid data transfer error from daemon")
+                }
+            }
+            return DataTransferError.UNKNOWN
+        }
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/CallService.java b/ring-android/libringclient/src/main/java/net/jami/services/CallService.java
deleted file mode 100644
index 875d0efb0..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/services/CallService.java
+++ /dev/null
@@ -1,837 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.services;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import net.jami.daemon.Blob;
-import net.jami.daemon.JamiService;
-import net.jami.daemon.StringMap;
-import net.jami.daemon.StringVect;
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Conference;
-import net.jami.model.Conversation;
-import net.jami.model.Call;
-import net.jami.model.Uri;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-
-public class CallService {
-
-    private final static String TAG = CallService.class.getSimpleName();
-    public final static String MIME_TEXT_PLAIN = "text/plain";
-    public static final String MIME_GEOLOCATION = "application/geo";
-    public static final String MEDIA_TYPE_AUDIO = "MEDIA_TYPE_AUDIO";
-    public static final String MEDIA_TYPE_VIDEO = "MEDIA_TYPE_VIDEO";
-
-    @Inject
-    @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-
-    @Inject
-    ContactService mContactService;
-
-    @Inject
-    HistoryService mHistoryService;
-
-    @Inject
-    AccountService mAccountService;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    private final Map<String, Call> currentCalls = new HashMap<>();
-    private final Map<String, Conference> currentConferences = new HashMap<>();
-
-    private final PublishSubject<Call> callSubject = PublishSubject.create();
-    private final PublishSubject<Conference> conferenceSubject = PublishSubject.create();
-
-    // private final Set<String> currentConnections = new HashSet<>();
-    // private final BehaviorSubject<Integer> connectionSubject = BehaviorSubject.createDefault(0);
-
-    public Observable<Conference> getConfsUpdates() {
-        return conferenceSubject;
-    }
-
-    private Observable<Conference> getConfCallUpdates(final Conference conf) {
-        Log.w(TAG, "getConfCallUpdates " + conf.getConfId());
-
-        return conferenceSubject
-                .filter(c -> c == conf)
-                .startWithItem(conf)
-                .map(Conference::getParticipants)
-                .switchMap(list -> Observable.fromIterable(list)
-                        .flatMap(call -> callSubject.filter(c -> c == call)))
-                .map(call -> conf)
-                .startWithItem(conf);
-    }
-
-    public Observable<Conference> getConfUpdates(final String confId) {
-        Call call = getCurrentCallForId(confId);
-        return call == null ? Observable.error(new IllegalArgumentException()) : getConfUpdates(call);
-        /*Conference call = currentConferences.get(confId);
-        return call == null ? Observable.error(new IllegalArgumentException()) : conferenceSubject
-                .filter(c -> c.getId().equals(confId));//getConfUpdates(call);*/
-    }
-
-    /*public Observable<Boolean> getConnectionUpdates() {
-        return connectionSubject
-                .map(i -> i > 0)
-                .distinctUntilChanged();
-    }*/
-
-    private void updateConnectionCount() {
-        //connectionSubject.onNext(currentConnections.size() - 2*currentCalls.size());
-    }
-
-    public void setIsComposing(String accountId, String uri, boolean isComposing) {
-        mExecutor.execute(() -> JamiService.setIsComposing(accountId, uri, isComposing));
-    }
-
-    public void onConferenceInfoUpdated(String confId, List<Map<String, String>> info) {
-        Log.w(TAG, "onConferenceInfoUpdated " + confId + " " + info);
-        Conference conference = getConference(confId);
-        boolean isModerator = false;
-        if (conference != null) {
-            List<Conference.ParticipantInfo> newInfo = new ArrayList<>(info.size());
-            if (conference.isConference()) {
-                for (Map<String, String> i : info) {
-                    Call call = conference.findCallByContact(Uri.fromString(i.get("uri")));
-                    if (call != null) {
-                        Conference.ParticipantInfo confInfo = new Conference.ParticipantInfo(call, call.getContact(), i);
-                        if (confInfo.isEmpty()) {
-                            Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry " + i);
-                            continue;
-                        }
-                        if (confInfo.contact.isUser() && confInfo.isModerator) {
-                            isModerator = true;
-                        }
-                        newInfo.add(confInfo);
-                    } else {
-                        Log.w(TAG, "onConferenceInfoUpdated " + confId + " can't find call for " + i);
-                        // TODO
-                    }
-                }
-            } else {
-                Account account = mAccountService.getAccount(conference.getCall().getAccount());
-                for (Map<String, String> i : info) {
-                    Conference.ParticipantInfo confInfo = new Conference.ParticipantInfo(null, account.getContactFromCache(Uri.fromString(i.get("uri"))), i);
-                    if (confInfo.isEmpty()) {
-                        Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry " + i);
-                        continue;
-                    }
-                    if (confInfo.contact.isUser() && confInfo.isModerator) {
-                        isModerator = true;
-                    }
-                    newInfo.add(confInfo);
-                }
-            }
-            conference.setIsModerator(isModerator);
-            conference.setInfo(newInfo);
-        } else {
-            Log.w(TAG, "onConferenceInfoUpdated can't find conference" + confId);
-        }
-    }
-
-    public void setConfMaximizedParticipant(String confId, Uri uri) {
-        mExecutor.execute(() -> {
-            JamiService.setActiveParticipant(confId, uri == null ? "" : uri.getRawRingId());
-            JamiService.setConferenceLayout(confId, 1);
-        });
-    }
-
-    public void setConfGridLayout(String confId) {
-        mExecutor.execute(() -> JamiService.setConferenceLayout(confId, 0));
-    }
-
-    public void remoteRecordingChanged(String callId, Uri peerNumber, boolean state) {
-        Log.w(TAG, "remoteRecordingChanged " + callId + " " + peerNumber + " " + state);
-        Conference conference = getConference(callId);
-        Call call;
-        if (conference == null) {
-            call = getCurrentCallForId(callId);
-            if (call != null) {
-                conference = getConference(call);
-            }
-        } else {
-            call = conference.getFirstCall();
-        }
-        Account account = call == null ? null : mAccountService.getAccount(call.getAccount());
-        Contact contact = account == null ? null : account.getContactFromCache(peerNumber);
-        if (conference != null && contact != null) {
-            conference.setParticipantRecording(contact, state);
-        }
-    }
-
-    private static class ConferenceEntity {
-        Conference conference;
-        ConferenceEntity(Conference conf) {
-            conference = conf;
-        }
-    }
-
-    public Observable<Conference> getConfUpdates(final Call call) {
-        return getConfUpdates(getConference(call));
-    }
-    private Observable<Conference> getConfUpdates(final Conference conference) {
-        Log.w(TAG, "getConfUpdates " + conference.getId());
-
-        ConferenceEntity conferenceEntity = new ConferenceEntity(conference);
-        return conferenceSubject
-                .startWithItem(conference)
-                .filter(conf -> {
-                    Log.w(TAG, "getConfUpdates filter " + conf.getConfId() + " " + conf.getParticipants().size() + " (tracked " + conferenceEntity.conference.getConfId() + " " + conferenceEntity.conference.getParticipants().size() + ")");
-                    if (conf == conferenceEntity.conference) {
-                        return true;
-                    }
-                    if (conf.contains(conferenceEntity.conference.getId())) {
-                        Log.w(TAG, "Switching tracked conference (up) to " + conf.getId());
-                        conferenceEntity.conference = conf;
-                        return true;
-                    }
-                    if (conferenceEntity.conference.getParticipants().size() == 1
-                            && conf.getParticipants().size() == 1
-                            && conferenceEntity.conference.getCall() == conf.getCall()
-                            && conf.getCall().getDaemonIdString().equals(conf.getConfId())) {
-                        Log.w(TAG, "Switching tracked conference (down) to " + conf.getId());
-                        conferenceEntity.conference = conf;
-                        return true;
-                    }
-                    return false;
-                })
-                .switchMap(this::getConfCallUpdates);
-    }
-
-    public Observable<Call> getCallsUpdates() {
-        return callSubject;
-    }
-    private Observable<Call> getCallUpdates(final Call call) {
-        return callSubject.filter(c -> c == call)
-                .startWithItem(call)
-                .takeWhile(c -> c.getCallStatus() != Call.CallStatus.OVER);
-    }
-    /*public Observable<SipCall> getCallUpdates(final String callId) {
-        SipCall call = getCurrentCallForId(callId);
-        return call == null ? Observable.error(new IllegalArgumentException()) : getCallUpdates(call);
-    }*/
-
-    public Observable<Call> placeCallObservable(final String accountId, final Uri conversationUri, final Uri number, final boolean audioOnly) {
-        return placeCall(accountId, conversationUri, number, audioOnly)
-                .flatMapObservable(this::getCallUpdates);
-    }
-
-    public Single<Call> placeCall(final String account, final Uri conversationUri, final Uri number, final boolean audioOnly) {
-        return Single.fromCallable(() -> {
-            Log.i(TAG, "placeCall() thread running... " + number + " audioOnly: " + audioOnly);
-
-            HashMap<String, String> volatileDetails = new HashMap<>();
-            volatileDetails.put(Call.KEY_AUDIO_ONLY, String.valueOf(audioOnly));
-
-            String callId = JamiService.placeCall(account, number.getUri(), StringMap.toSwig(volatileDetails));
-            if (callId == null || callId.isEmpty())
-                return null;
-            if (audioOnly) {
-                JamiService.muteLocalMedia(callId, "MEDIA_TYPE_VIDEO", true);
-            }
-            Call call = addCall(account, callId, number, Call.Direction.OUTGOING);
-            if (conversationUri != null && conversationUri.isSwarm())
-                call.setSwarmInfo(conversationUri.getRawRingId());
-            call.muteVideo(audioOnly);
-            updateConnectionCount();
-            return call;
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public void refuse(final String callId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "refuse() running... " + callId);
-            JamiService.refuse(callId);
-            JamiService.hangUp(callId);
-        });
-    }
-
-    public void accept(final String callId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "accept() running... " + callId);
-            JamiService.muteCapture(false);
-            JamiService.accept(callId);
-        });
-    }
-
-    public void hangUp(final String callId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "hangUp() running... " + callId);
-            JamiService.hangUp(callId);
-        });
-    }
-
-    public void muteParticipant(String confId, String peerId, boolean mute) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "mute participant... " + peerId);
-            JamiService.muteParticipant(confId, peerId, mute);
-        });
-    }
-
-    public void hangupParticipant(String confId, String peerId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "hangup participant... " + peerId);
-            JamiService.hangupParticipant(confId, peerId);
-        });
-    }
-
-    public void hold(final String callId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "hold() running... " + callId);
-            JamiService.hold(callId);
-        });
-    }
-
-    public void unhold(final String callId) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "unhold() running... " + callId);
-            JamiService.unhold(callId);
-        });
-    }
-
-    public Map<String, String> getCallDetails(final String callId) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCallDetails() running... " + callId);
-                return JamiService.getCallDetails(callId).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getCallDetails()", e);
-        }
-        return null;
-    }
-
-    public void muteRingTone(boolean mute) {
-        Log.d(TAG, (mute ? "Muting." : "Unmuting.") + " ringtone.");
-        JamiService.muteRingtone(mute);
-    }
-
-    public void restartAudioLayer() {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "restartAudioLayer() running...");
-            JamiService.setAudioPlugin(JamiService.getCurrentAudioOutputPlugin());
-        });
-    }
-
-    public void setAudioPlugin(final String audioPlugin) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "setAudioPlugin() running...");
-            JamiService.setAudioPlugin(audioPlugin);
-        });
-    }
-
-    public String getCurrentAudioOutputPlugin() {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCurrentAudioOutputPlugin() running...");
-                return JamiService.getCurrentAudioOutputPlugin();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getCallDetails()", e);
-        }
-        return null;
-    }
-
-    public void playDtmf(final String key) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "playDTMF() running...");
-            JamiService.playDTMF(key);
-        });
-    }
-
-    public void setMuted(final boolean mute) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "muteCapture() running...");
-            JamiService.muteCapture(mute);
-        });
-    }
-
-    public void setLocalMediaMuted(final String callId, String mediaType, final boolean mute) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "muteCapture() running...");
-            JamiService.muteLocalMedia(callId, mediaType, mute);
-        });
-    }
-
-    public boolean isCaptureMuted() {
-        return JamiService.isCaptureMuted();
-    }
-
-    public void transfer(final String callId, final String to) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "transfer() thread running...");
-            if (JamiService.transfer(callId, to)) {
-                Log.i(TAG, "OK");
-            } else {
-                Log.i(TAG, "NOT OK");
-            }
-        });
-    }
-
-    public void attendedTransfer(final String transferId, final String targetID) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "attendedTransfer() thread running...");
-            if (JamiService.attendedTransfer(transferId, targetID)) {
-                Log.i(TAG, "OK");
-            } else {
-                Log.i(TAG, "NOT OK");
-            }
-        });
-    }
-
-    public String getRecordPath() {
-        try {
-            return mExecutor.submit(JamiService::getRecordPath).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running isCaptureMuted()", e);
-        }
-        return null;
-    }
-
-    public boolean toggleRecordingCall(final String id) {
-        mExecutor.execute(() -> JamiService.toggleRecording(id));
-        return false;
-    }
-
-    public boolean startRecordedFilePlayback(final String filepath) {
-        mExecutor.execute(() -> JamiService.startRecordedFilePlayback(filepath));
-        return false;
-    }
-
-    public void stopRecordedFilePlayback() {
-        mExecutor.execute(JamiService::stopRecordedFilePlayback);
-    }
-
-    public void setRecordPath(final String path) {
-        mExecutor.execute(() -> JamiService.setRecordPath(path));
-    }
-
-    public void sendTextMessage(final String callId, final String msg) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "sendTextMessage() thread running...");
-            StringMap messages = new StringMap();
-            messages.setRaw("text/plain", Blob.fromString(msg));
-            JamiService.sendTextMessage(callId, messages, "", false);
-        });
-    }
-
-    public Single<Long> sendAccountTextMessage(final String accountId, final String to, final String msg) {
-        return Single.fromCallable(() -> {
-            Log.i(TAG, "sendAccountTextMessage() running... " + accountId + " " + to + " " + msg);
-            StringMap msgs = new StringMap();
-            msgs.setRaw("text/plain", Blob.fromString(msg));
-            return JamiService.sendAccountTextMessage(accountId, to, msgs);
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public Completable cancelMessage(final String accountId, final long messageID) {
-        return Completable.fromAction(() -> {
-            Log.i(TAG, "CancelMessage() running...   Account ID:  " + accountId + " " + "Message ID " + " " + messageID);
-            JamiService.cancelMessage(accountId, messageID);
-        }).subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    private Call getCurrentCallForId(String callId) {
-        return currentCalls.get(callId);
-    }
-
-    /*public Call getCurrentCallForContactId(String contactId) {
-        for (Call call : currentCalls.values()) {
-            if (contactId.contains(call.getContact().getPrimaryNumber())) {
-                return call;
-            }
-        }
-        return null;
-    }*/
-
-    public void removeCallForId(String callId) {
-        synchronized (currentCalls) {
-            currentCalls.remove(callId);
-            currentConferences.remove(callId);
-        }
-    }
-
-    private Call addCall(String accountId, String callId, Uri from, Call.Direction direction) {
-        synchronized (currentCalls) {
-            Call call = currentCalls.get(callId);
-            if (call == null) {
-                Account account = mAccountService.getAccount(accountId);
-                Contact contact = mContactService.findContact(account, from);
-                Uri conversationUri = contact.getConversationUri().blockingFirst();
-                Conversation conversation = conversationUri.equals(from) ? account.getByUri(from) : account.getSwarm(conversationUri.getRawRingId());
-                call = new Call(callId, from.getUri(), accountId, conversation, contact, direction);
-                currentCalls.put(callId, call);
-            } else {
-                Log.w(TAG, "Call already existed ! " + callId + " " + from);
-            }
-            return call;
-        }
-    }
-
-    private Conference addConference(Call call) {
-        String confId = call.getConfId();
-        if (confId == null) {
-            confId = call.getDaemonIdString();
-        }
-        Conference conference = currentConferences.get(confId);
-        if (conference == null) {
-            conference = new Conference(call);
-            currentConferences.put(confId, conference);
-            conferenceSubject.onNext(conference);
-        }
-        return conference;
-    }
-
-    private Call parseCallState(String callId, String newState) {
-        Call.CallStatus callState = Call.CallStatus.fromString(newState);
-        Call call = currentCalls.get(callId);
-        if (call != null) {
-            call.setCallState(callState);
-            call.setDetails(JamiService.getCallDetails(callId).toNative());
-        } else if (callState !=  Call.CallStatus.OVER && callState !=  Call.CallStatus.FAILURE) {
-            Map<String, String> callDetails = JamiService.getCallDetails(callId);
-            call = new Call(callId, callDetails);
-            if (StringUtils.isEmpty(call.getContactNumber())) {
-                Log.w(TAG, "No number");
-                return null;
-            }
-
-            call.setCallState(callState);
-            Account account = mAccountService.getAccount(call.getAccount());
-
-            Contact contact = mContactService.findContact(account, Uri.fromString(call.getContactNumber()));
-            String registeredName = callDetails.get(Call.KEY_REGISTERED_NAME);
-            if (registeredName != null && !registeredName.isEmpty()) {
-                contact.setUsername(registeredName);
-            }
-
-            Conversation conversation = account.getByUri(contact.getConversationUri().blockingFirst());
-            call.setContact(contact);
-            call.setConversation(conversation);
-            Log.w(TAG, "parseCallState " + contact + " " + contact.getConversationUri().blockingFirst() + " " + conversation + " " + conversation.getParticipant());
-
-            currentCalls.put(callId, call);
-            updateConnectionCount();
-        }
-        return call;
-    }
-
-    public void connectionUpdate(String id, int state) {
-        // Log.d(TAG, "connectionUpdate: " + id + " " + state);
-        /*switch(state) {
-            case 0:
-                currentConnections.add(id);
-                break;
-            case 1:
-            case 2:
-                currentConnections.remove(id);
-                break;
-        }
-        updateConnectionCount();*/
-    }
-
-    void callStateChanged(String callId, String newState, int detailCode) {
-        Log.d(TAG, "call state changed: " + callId + ", " + newState + ", " + detailCode);
-        try {
-            synchronized (currentCalls) {
-                Call call = parseCallState(callId, newState);
-                if (call != null) {
-                    callSubject.onNext(call);
-                    if (call.getCallStatus() == Call.CallStatus.OVER) {
-                        currentCalls.remove(call.getDaemonIdString());
-                        currentConferences.remove(call.getDaemonIdString());
-                        updateConnectionCount();
-                    }
-                }
-            }
-        } catch (Exception e) {
-            Log.w(TAG, "Exception during state change: ", e);
-        }
-    }
-
-    void incomingCall(String accountId, String callId, String from) {
-        Log.d(TAG, "incoming call: " + accountId + ", " + callId + ", " + from);
-
-        Call call = addCall(accountId, callId, Uri.fromStringWithName(from).first, Call.Direction.INCOMING);
-        callSubject.onNext(call);
-        updateConnectionCount();
-    }
-
-    public void incomingMessage(String callId, String from, Map<String, String> messages) {
-        Call call = currentCalls.get(callId);
-        if (call == null || messages == null) {
-            Log.w(TAG, "incomingMessage: unknown call or no message: " + callId + " " + from);
-            return;
-        }
-        VCard vcard = call.appendToVCard(messages);
-        if (vcard != null) {
-            mContactService.saveVCardContactData(call.getContact(), call.getAccount(), vcard);
-        }
-        if (messages.containsKey(MIME_TEXT_PLAIN)) {
-            mAccountService.incomingAccountMessage(call.getAccount(), null, callId, from, messages);
-        }
-    }
-
-    void recordPlaybackFilepath(String id, String filename) {
-        Log.d(TAG, "record playback filepath: " + id + ", " + filename);
-        // todo needs more explainations on that
-    }
-
-    void onRtcpReportReceived(String callId) {
-        Log.i(TAG, "on RTCP report received: " + callId);
-    }
-
-    public void removeConference(final String confId) {
-        mExecutor.execute(() -> JamiService.removeConference(confId));
-    }
-
-    public Single<Boolean> joinParticipant(final String selCallId, final String dragCallId) {
-        return Single.fromCallable(() -> JamiService.joinParticipant(selCallId, dragCallId))
-                .subscribeOn(Schedulers.from(mExecutor));
-    }
-
-    public void addParticipant(final String callId, final String confId) {
-        mExecutor.execute(() -> JamiService.addParticipant(callId, confId));
-    }
-
-    public void addMainParticipant(final String confId) {
-        mExecutor.execute(() -> JamiService.addMainParticipant(confId));
-    }
-
-    public void detachParticipant(final String callId) {
-        mExecutor.execute(() -> JamiService.detachParticipant(callId));
-    }
-
-    public void joinConference(final String selConfId, final String dragConfId) {
-        mExecutor.execute(() -> JamiService.joinConference(selConfId, dragConfId));
-    }
-
-    public void hangUpConference(final String confId) {
-        mExecutor.execute(() -> JamiService.hangUpConference(confId));
-    }
-
-    public void holdConference(final String confId) {
-        mExecutor.execute(() -> JamiService.holdConference(confId));
-    }
-
-    public void unholdConference(final String confId) {
-        mExecutor.execute(() -> JamiService.unholdConference(confId));
-    }
-
-    public boolean isConferenceParticipant(final String callId) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "isConferenceParticipant() running...");
-                return JamiService.isConferenceParticipant(callId);
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running isConferenceParticipant()", e);
-        }
-        return false;
-    }
-
-    public Map<String, ArrayList<String>> getConferenceList() {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getConferenceList() running...");
-                StringVect callIds = JamiService.getCallList();
-                HashMap<String, ArrayList<String>> confs = new HashMap<>(callIds.size());
-                for (int i = 0; i < callIds.size(); i++) {
-                    String callId = callIds.get(i);
-                    String confId = JamiService.getConferenceId(callId);
-                    Map<String, String> callDetails = JamiService.getCallDetails(callId).toNative();
-
-                    //todo remove condition when callDetails does not contains sips ids anymore
-                    if (!callDetails.get("PEER_NUMBER").contains("sips")) {
-                        if (confId == null || confId.isEmpty()) {
-                            confId = callId;
-                        }
-                        ArrayList<String> calls = confs.get(confId);
-                        if (calls == null) {
-                            calls = new ArrayList<>();
-                            confs.put(confId, calls);
-                        }
-                        calls.add(callId);
-                    }
-                }
-                return confs;
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running isConferenceParticipant()", e);
-        }
-        return null;
-    }
-
-    public List<String> getParticipantList(final String confId) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getParticipantList() running...");
-                return new ArrayList<>(JamiService.getParticipantList(confId));
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getParticipantList()", e);
-        }
-        return null;
-    }
-
-    public Conference getConference(Call call) {
-        return addConference(call);
-    }
-
-    public String getConferenceId(String callId) {
-        return JamiService.getConferenceId(callId);
-    }
-
-    public String getConferenceState(final String callId) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getConferenceDetails() thread running...");
-                return JamiService.getConferenceDetails(callId).get("CONF_STATE");
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getParticipantList()", e);
-        }
-        return null;
-    }
-
-    public Conference getConference(final String id) {
-        return currentConferences.get(id);
-    }
-
-    public Map<String, String> getConferenceDetails(final String id) {
-        try {
-            return mExecutor.submit(() -> {
-                Log.i(TAG, "getCredentials() thread running...");
-                return JamiService.getConferenceDetails(id).toNative();
-            }).get();
-        } catch (Exception e) {
-            Log.e(TAG, "Error running getParticipantList()", e);
-        }
-        return null;
-    }
-
-    void conferenceCreated(final String confId) {
-        Log.d(TAG, "conference created: " + confId);
-
-        Conference conf = currentConferences.get(confId);
-        if (conf == null) {
-            conf = new Conference(confId);
-            currentConferences.put(confId, conf);
-        }
-        StringVect participants = JamiService.getParticipantList(confId);
-        StringMap map = JamiService.getConferenceDetails(confId);
-        conf.setState(map.get("STATE"));
-        for (String callId : participants) {
-            Call call = getCurrentCallForId(callId);
-            if (call != null) {
-                Log.d(TAG, "conference created: adding participant " + callId + " " + call.getContact().getDisplayName());
-                call.setConfId(confId);
-                conf.addParticipant(call);
-            }
-            Conference rconf = currentConferences.remove(callId);
-            Log.d(TAG, "conference created: removing conference " + callId + " " + rconf + " now " + currentConferences.size());
-        }
-        conferenceSubject.onNext(conf);
-    }
-
-    void conferenceRemoved(String confId) {
-        Log.d(TAG, "conference removed: " + confId);
-
-        Conference conf = currentConferences.remove(confId);
-        if (conf != null) {
-            for (Call call : conf.getParticipants()) {
-                call.setConfId(null);
-            }
-            conf.removeParticipants();
-            conferenceSubject.onNext(conf);
-        }
-    }
-
-    void conferenceChanged(String confId, String state) {
-        Log.d(TAG, "conference changed: " + confId + ", " + state);
-        try {
-            Conference conf = currentConferences.get(confId);
-            if (conf == null) {
-                conf = new Conference(confId);
-                currentConferences.put(confId, conf);
-            }
-            conf.setState(state);
-            Set<String> participants = new HashSet<>(JamiService.getParticipantList(confId));
-            // Add new participants
-            for (String callId : participants) {
-                if (!conf.contains(callId)) {
-                    Call call = getCurrentCallForId(callId);
-                    if (call != null) {
-                        Log.d(TAG, "conference changed: adding participant " + callId + " " + call.getContact().getDisplayName());
-                        call.setConfId(confId);
-                        conf.addParticipant(call);
-                    }
-                    currentConferences.remove(callId);
-                }
-            }
-
-            // Remove participants
-            List<Call> calls = conf.getParticipants();
-            Iterator<Call> i = calls.iterator();
-            boolean removed = false;
-            while (i.hasNext()) {
-                Call call = i.next();
-                if (!participants.contains(call.getDaemonIdString())) {
-                    Log.d(TAG, "conference changed: removing participant " + call.getDaemonIdString() + " " + call.getContact().getDisplayName());
-                    call.setConfId(null);
-                    i.remove();
-                    removed = true;
-                }
-            }
-
-            conferenceSubject.onNext(conf);
-
-            if (removed && conf.getParticipants().size() == 1 && conf.getConfId() != null) {
-                Call call = conf.getCall();
-                call.setConfId(null);
-                addConference(call);
-            }
-        } catch (Exception e) {
-            Log.w(TAG, "exception in conferenceChanged", e);
-        }
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt b/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt
new file mode 100644
index 000000000..485098353
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt
@@ -0,0 +1,787 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.services
+
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.PublishSubject
+import net.jami.daemon.Blob
+import net.jami.daemon.JamiService
+import net.jami.daemon.StringMap
+import net.jami.model.Call
+import net.jami.model.Call.CallStatus
+import net.jami.model.Conference
+import net.jami.model.Conference.ParticipantInfo
+import net.jami.model.Uri
+import net.jami.model.Uri.Companion.fromString
+import net.jami.model.Uri.Companion.fromStringWithName
+import net.jami.utils.Log
+import net.jami.utils.StringUtils.isEmpty
+import java.util.*
+import java.util.concurrent.Callable
+import java.util.concurrent.ScheduledExecutorService
+
+class CallService(
+    private val mExecutor: ScheduledExecutorService,
+    private val mContactService: ContactService,
+    private val mAccountService: AccountService
+) {
+    private val currentCalls: MutableMap<String, Call> = HashMap()
+    private val currentConferences: MutableMap<String, Conference> = HashMap()
+    private val callSubject = PublishSubject.create<Call>()
+    private val conferenceSubject = PublishSubject.create<Conference>()
+
+    // private final Set<String> currentConnections = new HashSet<>();
+    // private final BehaviorSubject<Integer> connectionSubject = BehaviorSubject.createDefault(0);
+    val confsUpdates: Observable<Conference>
+        get() = conferenceSubject
+
+    private fun getConfCallUpdates(conf: Conference): Observable<Conference> {
+        Log.w(TAG, "getConfCallUpdates " + conf.id)
+        return conferenceSubject
+            .filter { c -> c == conf }
+            .startWithItem(conf)
+            .map(Conference::participants)
+            .switchMap { list: List<Call> -> Observable.fromIterable(list)
+                    .flatMap { call: Call -> callSubject.filter { c -> c == call } } }
+            .map { conf }
+            .startWithItem(conf)
+    }
+
+    fun getConfUpdates(confId: String): Observable<Conference> {
+        return getCurrentCallForId(confId)?.let { getConfUpdates(it) }
+            ?: Observable.error(IllegalArgumentException())
+        /*Conference call = currentConferences.get(confId);
+        return call == null ? Observable.error(new IllegalArgumentException()) : conferenceSubject
+                .filter(c -> c.getId().equals(confId));//getConfUpdates(call);*/
+    }
+
+    /*public Observable<Boolean> getConnectionUpdates() {
+        return connectionSubject
+                .map(i -> i > 0)
+                .distinctUntilChanged();
+    }*/
+    private fun updateConnectionCount() {
+        //connectionSubject.onNext(currentConnections.size() - 2*currentCalls.size());
+    }
+
+    fun setIsComposing(accountId: String?, uri: String?, isComposing: Boolean) {
+        mExecutor.execute { JamiService.setIsComposing(accountId, uri, isComposing) }
+    }
+
+    fun onConferenceInfoUpdated(confId: String, info: List<Map<String, String>>) {
+        Log.w(TAG, "onConferenceInfoUpdated $confId $info")
+        val conference = getConference(confId)
+        var isModerator = false
+        if (conference != null) {
+            val newInfo: MutableList<ParticipantInfo> = ArrayList(info.size)
+            if (conference.isConference) {
+                for (i in info) {
+                    val call = conference.findCallByContact(fromString(i["uri"]!!))
+                    if (call != null) {
+                        val confInfo = ParticipantInfo(call, call.contact!!, i)
+                        if (confInfo.isEmpty) {
+                            Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry $i")
+                            continue
+                        }
+                        if (confInfo.contact.isUser && confInfo.isModerator) {
+                            isModerator = true
+                        }
+                        newInfo.add(confInfo)
+                    } else {
+                        Log.w(TAG, "onConferenceInfoUpdated $confId can't find call for $i")
+                        // TODO
+                    }
+                }
+            } else {
+                val account = mAccountService.getAccount(conference.call!!.account!!)!!
+                for (i in info) {
+                    val confInfo = ParticipantInfo(null, account.getContactFromCache(fromString(i["uri"]!!)), i)
+                    if (confInfo.isEmpty) {
+                        Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry $i")
+                        continue
+                    }
+                    if (confInfo.contact.isUser && confInfo.isModerator) {
+                        isModerator = true
+                    }
+                    newInfo.add(confInfo)
+                }
+            }
+            conference.isModerator = isModerator
+            conference.setInfo(newInfo)
+        } else {
+            Log.w(TAG, "onConferenceInfoUpdated can't find conference$confId")
+        }
+    }
+
+    fun setConfMaximizedParticipant(confId: String, uri: Uri) {
+        mExecutor.execute {
+            JamiService.setActiveParticipant(confId, uri?.rawRingId ?: "")
+            JamiService.setConferenceLayout(confId, 1)
+        }
+    }
+
+    fun setConfGridLayout(confId: String?) {
+        mExecutor.execute { JamiService.setConferenceLayout(confId, 0) }
+    }
+
+    fun remoteRecordingChanged(callId: String, peerNumber: Uri, state: Boolean) {
+        Log.w(TAG, "remoteRecordingChanged $callId $peerNumber $state")
+        var conference = getConference(callId)
+        val call: Call?
+        if (conference == null) {
+            call = getCurrentCallForId(callId)
+            if (call != null) {
+                conference = getConference(call)
+            }
+        } else {
+            call = conference.firstCall
+        }
+        val account = if (call == null) null else mAccountService.getAccount(call.account!!)
+        val contact = account?.getContactFromCache(peerNumber)
+        if (conference != null && contact != null) {
+            conference.setParticipantRecording(contact, state)
+        }
+    }
+
+    private class ConferenceEntity internal constructor(var conference: Conference)
+
+    fun getConfUpdates(call: Call): Observable<Conference> {
+        return getConfUpdates(getConference(call))
+    }
+
+    private fun getConfUpdates(conference: Conference): Observable<Conference> {
+        Log.w(TAG, "getConfUpdates " + conference.id)
+        val conferenceEntity = ConferenceEntity(conference)
+        return conferenceSubject
+            .startWithItem(conference)
+            .filter { conf: Conference ->
+                Log.w(TAG, "getConfUpdates filter " + conf.id + " " + conf.participants.size + " (tracked " + conferenceEntity.conference.id + " " + conferenceEntity.conference.participants.size + ")")
+                if (conf == conferenceEntity.conference) {
+                    return@filter true
+                }
+                if (conf.contains(conferenceEntity.conference.id)) {
+                    Log.w(TAG, "Switching tracked conference (up) to " + conf.id)
+                    conferenceEntity.conference = conf
+                    return@filter true
+                }
+                if (conferenceEntity.conference.participants.size == 1 && conf.participants.size == 1 && conferenceEntity.conference.call == conf.call && conf.call!!.daemonIdString == conf.id) {
+                    Log.w(TAG, "Switching tracked conference (down) to " + conf.id)
+                    conferenceEntity.conference = conf
+                    return@filter true
+                }
+                false
+            }
+            .switchMap { conf: Conference -> getConfCallUpdates(conf) }
+    }
+
+    val callsUpdates: Observable<Call>
+        get() = callSubject
+
+    private fun getCallUpdates(call: Call): Observable<Call> {
+        return callSubject.filter { c: Call -> c == call }
+            .startWithItem(call)
+            .takeWhile { c: Call -> c.callStatus !== CallStatus.OVER }
+    }
+
+    /*public Observable<SipCall> getCallUpdates(final String callId) {
+        SipCall call = getCurrentCallForId(callId);
+        return call == null ? Observable.error(new IllegalArgumentException()) : getCallUpdates(call);
+    }*/
+    fun placeCallObservable(accountId: String, conversationUri: Uri?, number: Uri, audioOnly: Boolean): Observable<Call> {
+        return placeCall(accountId, conversationUri, number, audioOnly)
+            .flatMapObservable { call: Call -> getCallUpdates(call) }
+    }
+
+    fun placeCall(account: String, conversationUri: Uri?, number: Uri, audioOnly: Boolean): Single<Call> {
+        return Single.fromCallable<Call> {
+            Log.i(TAG, "placeCall() thread running... $number audioOnly: $audioOnly")
+            val volatileDetails = HashMap<String, String>()
+            volatileDetails[Call.KEY_AUDIO_ONLY] = audioOnly.toString()
+            val callId = JamiService.placeCall(account, number.uri, StringMap.toSwig(volatileDetails))
+            if (callId == null || callId.isEmpty()) return@fromCallable null
+            if (audioOnly) {
+                JamiService.muteLocalMedia(callId, "MEDIA_TYPE_VIDEO", true)
+            }
+            val call = addCall(account, callId, number, Call.Direction.OUTGOING)
+            if (conversationUri != null && conversationUri.isSwarm) call.setSwarmInfo(conversationUri.rawRingId)
+            call.muteVideo(audioOnly)
+            updateConnectionCount()
+            call
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun refuse(callId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "refuse() running... $callId")
+            JamiService.refuse(callId)
+            JamiService.hangUp(callId)
+        }
+    }
+
+    fun accept(callId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "accept() running... $callId")
+            JamiService.muteCapture(false)
+            JamiService.accept(callId)
+        }
+    }
+
+    fun hangUp(callId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "hangUp() running... $callId")
+            JamiService.hangUp(callId)
+        }
+    }
+
+    fun muteParticipant(confId: String, peerId: String, mute: Boolean) {
+        mExecutor.execute {
+            Log.i(TAG, "mute participant... $peerId")
+            JamiService.muteParticipant(confId, peerId, mute)
+        }
+    }
+
+    fun hangupParticipant(confId: String?, peerId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "hangup participant... $peerId")
+            JamiService.hangupParticipant(confId, peerId)
+        }
+    }
+
+    fun hold(callId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "hold() running... $callId")
+            JamiService.hold(callId)
+        }
+    }
+
+    fun unhold(callId: String) {
+        mExecutor.execute {
+            Log.i(TAG, "unhold() running... $callId")
+            JamiService.unhold(callId)
+        }
+    }
+
+    fun getCallDetails(callId: String): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "getCallDetails() running... $callId")
+                JamiService.getCallDetails(callId).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getCallDetails()", e)
+        }
+        return null
+    }
+
+    fun muteRingTone(mute: Boolean) {
+        Log.d(TAG, (if (mute) "Muting." else "Unmuting.") + " ringtone.")
+        JamiService.muteRingtone(mute)
+    }
+
+    fun restartAudioLayer() {
+        mExecutor.execute {
+            Log.i(TAG, "restartAudioLayer() running...")
+            JamiService.setAudioPlugin(JamiService.getCurrentAudioOutputPlugin())
+        }
+    }
+
+    fun setAudioPlugin(audioPlugin: String) {
+        mExecutor.execute {
+            Log.i(TAG, "setAudioPlugin() running...")
+            JamiService.setAudioPlugin(audioPlugin)
+        }
+    }
+
+    val currentAudioOutputPlugin: String?
+        get() {
+            try {
+                return mExecutor.submit<String> {
+                    Log.i(TAG, "getCurrentAudioOutputPlugin() running...")
+                    JamiService.getCurrentAudioOutputPlugin()
+                }.get()
+            } catch (e: Exception) {
+                Log.e(TAG, "Error running getCallDetails()", e)
+            }
+            return null
+        }
+
+    fun playDtmf(key: String) {
+        mExecutor.execute {
+            Log.i(TAG, "playDTMF() running...")
+            JamiService.playDTMF(key)
+        }
+    }
+
+    fun setMuted(mute: Boolean) {
+        mExecutor.execute {
+            Log.i(TAG, "muteCapture() running...")
+            JamiService.muteCapture(mute)
+        }
+    }
+
+    fun setLocalMediaMuted(callId: String, mediaType: String, mute: Boolean) {
+        mExecutor.execute {
+            Log.i(TAG, "muteCapture() running...")
+            JamiService.muteLocalMedia(callId, mediaType, mute)
+        }
+    }
+
+    val isCaptureMuted: Boolean
+        get() = JamiService.isCaptureMuted()
+
+    fun transfer(callId: String, to: String) {
+        mExecutor.execute {
+            Log.i(TAG, "transfer() thread running...")
+            if (JamiService.transfer(callId, to)) {
+                Log.i(TAG, "OK")
+            } else {
+                Log.i(TAG, "NOT OK")
+            }
+        }
+    }
+
+    fun attendedTransfer(transferId: String, targetID: String) {
+        mExecutor.execute {
+            Log.i(TAG, "attendedTransfer() thread running...")
+            if (JamiService.attendedTransfer(transferId, targetID)) {
+                Log.i(TAG, "OK")
+            } else {
+                Log.i(TAG, "NOT OK")
+            }
+        }
+    }
+
+    var recordPath: String?
+        get() {
+            try {
+                return mExecutor.submit<String> { JamiService.getRecordPath() }.get()
+            } catch (e: Exception) {
+                Log.e(TAG, "Error running isCaptureMuted()", e)
+            }
+            return null
+        }
+        set(path) {
+            mExecutor.execute { JamiService.setRecordPath(path) }
+        }
+
+    fun toggleRecordingCall(id: String?): Boolean {
+        mExecutor.execute { JamiService.toggleRecording(id) }
+        return false
+    }
+
+    fun startRecordedFilePlayback(filepath: String): Boolean {
+        mExecutor.execute { JamiService.startRecordedFilePlayback(filepath) }
+        return false
+    }
+
+    fun stopRecordedFilePlayback() {
+        mExecutor.execute { JamiService.stopRecordedFilePlayback() }
+    }
+
+    fun sendTextMessage(callId: String, msg: String?) {
+        mExecutor.execute {
+            Log.i(TAG, "sendTextMessage() thread running...")
+            val messages = StringMap()
+            messages.setRaw("text/plain", Blob.fromString(msg))
+            JamiService.sendTextMessage(callId, messages, "", false)
+        }
+    }
+
+    fun sendAccountTextMessage(accountId: String, to: String, msg: String): Single<Long> {
+        return Single.fromCallable {
+            Log.i(TAG, "sendAccountTextMessage() running... $accountId $to $msg")
+            val msgs = StringMap()
+            msgs.setRaw("text/plain", Blob.fromString(msg))
+            JamiService.sendAccountTextMessage(accountId, to, msgs)
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun cancelMessage(accountId: String, messageID: Long): Completable {
+        return Completable.fromAction {
+            Log.i(TAG, "CancelMessage() running...   Account ID:  $accountId Message ID  $messageID")
+            JamiService.cancelMessage(accountId, messageID)
+        }.subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    private fun getCurrentCallForId(callId: String): Call? {
+        return currentCalls[callId]
+    }
+
+    /*public Call getCurrentCallForContactId(String contactId) {
+        for (Call call : currentCalls.values()) {
+            if (contactId.contains(call.getContact().getPrimaryNumber())) {
+                return call;
+            }
+        }
+        return null;
+    }*/
+    fun removeCallForId(callId: String) {
+        synchronized(currentCalls) {
+            currentCalls.remove(callId)
+            currentConferences.remove(callId)
+        }
+    }
+
+    private fun addCall(accountId: String, callId: String, from: Uri, direction: Call.Direction): Call {
+        synchronized(currentCalls) {
+            var call = currentCalls[callId]
+            if (call == null) {
+                val account = mAccountService.getAccount(accountId)!!
+                val contact = mContactService.findContact(account, from)
+                val conversationUri = contact.conversationUri.blockingFirst()
+                val conversation =
+                    if (conversationUri.equals(from)) account.getByUri(from) else account.getSwarm(conversationUri.rawRingId)
+                call = Call(callId, from.uri, accountId, conversation, contact, direction)
+                currentCalls[callId] = call
+            } else {
+                Log.w(TAG, "Call already existed ! $callId $from")
+            }
+            return call
+        }
+    }
+
+    private fun addConference(call: Call): Conference {
+        val confId = call.confId ?: call.daemonIdString!!
+        var conference = currentConferences[confId]
+        if (conference == null) {
+            conference = Conference(call)
+            currentConferences[confId] = conference
+            conferenceSubject.onNext(conference)
+        }
+        return conference
+    }
+
+    private fun parseCallState(callId: String, newState: String): Call? {
+        val callState = CallStatus.fromString(newState)
+        var call = currentCalls[callId]
+        if (call != null) {
+            call.setCallState(callState)
+            call.setDetails(JamiService.getCallDetails(callId).toNative())
+        } else if (callState !== CallStatus.OVER && callState !== CallStatus.FAILURE) {
+            val callDetails: Map<String?, String> = JamiService.getCallDetails(callId)
+            call = Call(callId, callDetails)
+            if (isEmpty(call.contactNumber)) {
+                Log.w(TAG, "No number")
+                return null
+            }
+            call.setCallState(callState)
+            val account = mAccountService.getAccount(call.account!!)!!
+            val contact = mContactService.findContact(account, fromString(call.contactNumber!!))
+            val registeredName = callDetails[Call.KEY_REGISTERED_NAME]
+            if (registeredName != null && !registeredName.isEmpty()) {
+                contact.setUsername(registeredName)
+            }
+            val conversation = account.getByUri(contact.conversationUri.blockingFirst())
+            call.contact = contact
+            call.conversation = conversation
+            Log.w(TAG, "parseCallState " + contact + " " + contact.conversationUri.blockingFirst() + " " + conversation + " " + conversation!!.participant)
+            currentCalls[callId] = call
+            updateConnectionCount()
+        }
+        return call
+    }
+
+    fun connectionUpdate(id: String?, state: Int) {
+        // Log.d(TAG, "connectionUpdate: " + id + " " + state);
+        /*switch(state) {
+            case 0:
+                currentConnections.add(id);
+                break;
+            case 1:
+            case 2:
+                currentConnections.remove(id);
+                break;
+        }
+        updateConnectionCount();*/
+    }
+
+    fun callStateChanged(callId: String, newState: String, detailCode: Int) {
+        Log.d(TAG, "call state changed: $callId, $newState, $detailCode")
+        try {
+            synchronized(currentCalls) {
+                parseCallState(callId, newState)?.let { call ->
+                    callSubject.onNext(call)
+                    if (call.callStatus === CallStatus.OVER) {
+                        currentCalls.remove(call.daemonIdString)
+                        currentConferences.remove(call.daemonIdString)
+                        updateConnectionCount()
+                    }
+                }
+            }
+        } catch (e: Exception) {
+            Log.w(TAG, "Exception during state change: ", e)
+        }
+    }
+
+    fun incomingCall(accountId: String, callId: String, from: String) {
+        Log.d(TAG, "incoming call: $accountId, $callId, $from")
+        val call = addCall(accountId, callId, fromStringWithName(from).first, Call.Direction.INCOMING)
+        callSubject.onNext(call)
+        updateConnectionCount()
+    }
+
+    fun incomingMessage(callId: String, from: String, messages: Map<String, String>) {
+        val call = currentCalls[callId]
+        if (call == null) {
+            Log.w(TAG, "incomingMessage: unknown call or no message: $callId $from")
+            return
+        }
+        call.appendToVCard(messages)?.let { vcard ->
+            mContactService.saveVCardContactData(call.contact!!, call.account!!, vcard)
+        }
+        if (messages.containsKey(MIME_TEXT_PLAIN)) {
+            mAccountService.incomingAccountMessage(call.account!!, null, callId, from, messages)
+        }
+    }
+
+    fun recordPlaybackFilepath(id: String, filename: String) {
+        Log.d(TAG, "record playback filepath: $id, $filename")
+        // todo needs more explanations on that
+    }
+
+    fun onRtcpReportReceived(callId: String) {
+        Log.i(TAG, "on RTCP report received: $callId")
+    }
+
+    fun removeConference(confId: String) {
+        mExecutor.execute { JamiService.removeConference(confId) }
+    }
+
+    fun joinParticipant(selCallId: String, dragCallId: String): Single<Boolean> {
+        return Single.fromCallable { JamiService.joinParticipant(selCallId, dragCallId) }
+            .subscribeOn(Schedulers.from(mExecutor))
+    }
+
+    fun addParticipant(callId: String, confId: String) {
+        mExecutor.execute { JamiService.addParticipant(callId, confId) }
+    }
+
+    fun addMainParticipant(confId: String) {
+        mExecutor.execute { JamiService.addMainParticipant(confId) }
+    }
+
+    fun detachParticipant(callId: String) {
+        mExecutor.execute { JamiService.detachParticipant(callId) }
+    }
+
+    fun joinConference(selConfId: String, dragConfId: String) {
+        mExecutor.execute { JamiService.joinConference(selConfId, dragConfId) }
+    }
+
+    fun hangUpConference(confId: String) {
+        mExecutor.execute { JamiService.hangUpConference(confId) }
+    }
+
+    fun holdConference(confId: String) {
+        mExecutor.execute { JamiService.holdConference(confId) }
+    }
+
+    fun unholdConference(confId: String) {
+        mExecutor.execute { JamiService.unholdConference(confId) }
+    }
+
+    fun isConferenceParticipant(callId: String): Boolean {
+        try {
+            return mExecutor.submit<Boolean> {
+                Log.i(TAG, "isConferenceParticipant() running...")
+                JamiService.isConferenceParticipant(callId)
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running isConferenceParticipant()", e)
+        }
+        return false
+    }
+
+    //todo remove condition when callDetails does not contains sips ids anymore
+    val conferenceList: Map<String, ArrayList<String>>?
+        get() {
+            try {
+                return mExecutor.submit(Callable {
+                    Log.i(TAG, "getConferenceList() running...")
+                    val callIds = JamiService.getCallList()
+                    val confs = HashMap<String, ArrayList<String>>(callIds.size)
+                    for (i in callIds.indices) {
+                        val callId = callIds[i]
+                        var confId = JamiService.getConferenceId(callId)
+                        val callDetails: Map<String, String> = JamiService.getCallDetails(callId).toNative()
+
+                        //todo remove condition when callDetails does not contains sips ids anymore
+                        if (!callDetails["PEER_NUMBER"]!!.contains("sips")) {
+                            if (confId == null || confId.isEmpty()) {
+                                confId = callId
+                            }
+                            var calls = confs[confId]
+                            if (calls == null) {
+                                calls = ArrayList()
+                                confs[confId] = calls
+                            }
+                            calls.add(callId)
+                        }
+                    }
+                    confs
+                }).get()
+            } catch (e: Exception) {
+                Log.e(TAG, "Error running isConferenceParticipant()", e)
+            }
+            return null
+        }
+
+    fun getParticipantList(confId: String?): List<String>? {
+        try {
+            return mExecutor.submit<ArrayList<String>> {
+                Log.i(TAG, "getParticipantList() running...")
+                ArrayList(JamiService.getParticipantList(confId))
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getParticipantList()", e)
+        }
+        return null
+    }
+
+    fun getConference(call: Call): Conference {
+        return addConference(call)
+    }
+
+    fun getConferenceId(callId: String): String {
+        return JamiService.getConferenceId(callId)
+    }
+
+    fun getConferenceState(callId: String): String? {
+        try {
+            return mExecutor.submit<String> {
+                Log.i(TAG, "getConferenceDetails() thread running...")
+                JamiService.getConferenceDetails(callId)["CONF_STATE"]
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getParticipantList()", e)
+        }
+        return null
+    }
+
+    fun getConference(id: String): Conference? {
+        return currentConferences[id]
+    }
+
+    fun getConferenceDetails(id: String): Map<String, String>? {
+        try {
+            return mExecutor.submit<HashMap<String, String>> {
+                Log.i(TAG, "getCredentials() thread running...")
+                JamiService.getConferenceDetails(id).toNative()
+            }.get()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error running getParticipantList()", e)
+        }
+        return null
+    }
+
+    fun conferenceCreated(confId: String) {
+        Log.d(TAG, "conference created: $confId")
+        var conf = currentConferences[confId]
+        if (conf == null) {
+            conf = Conference(confId)
+            currentConferences[confId] = conf
+        }
+        val participants = JamiService.getParticipantList(confId)
+        val map = JamiService.getConferenceDetails(confId)
+        conf.setState(map["STATE"])
+        for (callId in participants) {
+            val call = getCurrentCallForId(callId)
+            if (call != null) {
+                Log.d(TAG, "conference created: adding participant " + callId + " " + call.contact!!.displayName)
+                call.confId = confId
+                conf.addParticipant(call)
+            }
+            val rconf = currentConferences.remove(callId)
+            Log.d(TAG, "conference created: removing conference " + callId + " " + rconf + " now " + currentConferences.size)
+        }
+        conferenceSubject.onNext(conf)
+    }
+
+    fun conferenceRemoved(confId: String) {
+        Log.d(TAG, "conference removed: $confId")
+        val conf = currentConferences.remove(confId)
+        if (conf != null) {
+            for (call in conf.participants) {
+                call.confId = null
+            }
+            conf.removeParticipants()
+            conferenceSubject.onNext(conf)
+        }
+    }
+
+    fun conferenceChanged(confId: String, state: String) {
+        Log.d(TAG, "conference changed: $confId, $state")
+        try {
+            var conf = currentConferences[confId]
+            if (conf == null) {
+                conf = Conference(confId)
+                currentConferences[confId] = conf
+            }
+            conf.setState(state)
+            val participants: Set<String> = JamiService.getParticipantList(confId).toHashSet()
+            // Add new participants
+            for (callId in participants) {
+                if (!conf.contains(callId)) {
+                    val call = getCurrentCallForId(callId)
+                    if (call != null) {
+                        Log.d(TAG, "conference changed: adding participant " + callId + " " + call.contact!!.displayName)
+                        call.confId = confId
+                        conf.addParticipant(call)
+                    }
+                    currentConferences.remove(callId)
+                }
+            }
+
+            // Remove participants
+            val calls = conf.participants
+            var removed = false
+            val i = calls.iterator()
+            while (i.hasNext()) {
+                val call = i.next()
+                if (!participants.contains(call.daemonIdString)) {
+                    Log.d(TAG, "conference changed: removing participant " + call.daemonIdString + " " + call.contact!!.displayName)
+                    call.confId = null
+                    i.remove()
+                    removed = true
+                }
+            }
+            conferenceSubject.onNext(conf)
+            if (removed && conf.participants.size == 1) {
+                val call = conf.participants[0]
+                call.confId = null
+                addConference(call)
+            }
+        } catch (e: Exception) {
+            Log.w(TAG, "exception in conferenceChanged", e)
+        }
+    }
+
+    companion object {
+        private val TAG = CallService::class.simpleName!!
+        const val MIME_TEXT_PLAIN = "text/plain"
+        const val MIME_GEOLOCATION = "application/geo"
+        const val MEDIA_TYPE_AUDIO = "MEDIA_TYPE_AUDIO"
+        const val MEDIA_TYPE_VIDEO = "MEDIA_TYPE_VIDEO"
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/ContactService.java b/ring-android/libringclient/src/main/java/net/jami/services/ContactService.java
deleted file mode 100644
index 5762b1496..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/services/ContactService.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.services;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Settings;
-import net.jami.model.Uri;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import ezvcard.VCard;
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-
-/**
- * This service handles the contacts
- * - Load the contacts stored in the system
- * - Keep a local cache of the contacts
- * - Provide query tools to search contacts by id, number, ...
- */
-public abstract class ContactService {
-    private final static String TAG = ContactService.class.getSimpleName();
-
-    @Inject
-    PreferencesService mPreferencesService;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    @Inject
-    AccountService mAccountService;
-
-    public abstract Map<Long, Contact> loadContactsFromSystem(boolean loadRingContacts, boolean loadSipContacts);
-
-    protected abstract Contact findContactByIdFromSystem(Long contactId, String contactKey);
-    protected abstract Contact findContactBySipNumberFromSystem(String number);
-    protected abstract Contact findContactByNumberFromSystem(String number);
-
-    public abstract Completable loadContactData(Contact contact, String accountId);
-
-    public abstract void saveVCardContactData(Contact contact, String accountId, VCard vcard);
-    public abstract Single<VCard> saveVCardContact(String accountId, String uri, String displayName, String pictureB64);
-
-    public ContactService() {}
-
-    /**
-     * Load contacts from system and generate a local contact cache
-     *
-     * @param loadRingContacts if true, ring contacts will be taken care of
-     * @param loadSipContacts  if true, sip contacts will be taken care of
-     */
-    public Single<Map<Long, Contact>> loadContacts(final boolean loadRingContacts, final boolean loadSipContacts, final Account account) {
-        return Single.fromCallable(() -> {
-            Settings settings = mPreferencesService.getSettings();
-            if (settings.isAllowSystemContacts() && mDeviceRuntimeService.hasContactPermission()) {
-                return loadContactsFromSystem(loadRingContacts, loadSipContacts);
-            }
-            return new HashMap<>();
-        });
-    }
-
-    public Observable<Contact> observeContact(String accountId, Contact contact, boolean withPresence) {
-        //Log.w(TAG, "observeContact " + accountId + " " + contact.getUri() + " " + contact.isUser());
-        if (contact.isUser())
-            withPresence = false;
-        Uri uri = contact.getUri();
-        String uriString = uri.getRawUriString();
-        synchronized (contact) {
-            if (contact.getPresenceUpdates() == null) {
-                contact.setPresenceUpdates(Observable.<Boolean>create(emitter -> {
-                    emitter.onNext(false);
-                    contact.setPresenceEmitter(emitter);
-                    mAccountService.subscribeBuddy(accountId, uriString, true);
-                    emitter.setCancellable(() -> {
-                        mAccountService.subscribeBuddy(accountId, uriString, false);
-                        contact.setPresenceEmitter(null);
-                        emitter.onNext(false);
-                    });
-                })
-                        .replay(1)
-                        .refCount(5, TimeUnit.SECONDS));
-            }
-
-            if (contact.getUpdates() == null) {
-                contact.setUpdates(contact.getUpdatesSubject()
-                        .doOnSubscribe(d -> {
-                            if (!contact.isUsernameLoaded())
-                                mAccountService.lookupAddress(accountId, "", uri.getRawRingId());
-                            loadContactData(contact, accountId)
-                                    .subscribe(() -> {}, e -> {/*Log.e(TAG, "Error loading contact data: " + e.getMessage())*/});
-                        })
-                        .filter(c -> c.isUsernameLoaded() && c.detailsLoaded)
-                        .replay(1)
-                        .refCount());
-            }
-
-            return withPresence
-                    ? Observable.combineLatest(contact.getUpdates(), contact.getPresenceUpdates(), (c, p) -> {
-                //Log.w(TAG, "observeContact UPDATE " + c + " " + p);
-                return c;
-            })
-                    : contact.getUpdates();
-        }
-    }
-
-    public Observable<List<Contact>> observeContact(String accountId, List<Contact> contacts, boolean withPresence) {
-        if (contacts.isEmpty()) {
-            return Observable.just(Collections.emptyList());
-        } /*else if (contacts.size() == 1 || contacts.size() == 2) {
-
-            return observeContact(accountId, contacts.get(contacts.size() - 1), withPresence).map(Collections::singletonList);
-        } */else {
-            List<Observable<Contact>> observables = new ArrayList<>(contacts.size());
-            for (Contact contact : contacts) {
-                if (!contact.isUser())
-                    observables.add(observeContact(accountId, contact, withPresence));
-            }
-            if (observables.isEmpty())
-                return Observable.just(Collections.emptyList());
-            return Observable.combineLatest(observables, a -> {
-                List<Contact> obs = new ArrayList<>(a.length);
-                for (Object o : a)
-                    obs.add((Contact) o);
-                return obs;
-            });
-        }
-    }
-
-    public Single<Contact> getLoadedContact(String accountId, Contact contact, boolean withPresence) {
-        return observeContact(accountId, contact, withPresence)
-                .filter(c -> c.isUsernameLoaded() && c.detailsLoaded)
-                .firstOrError();
-    }
-    public Single<Contact> getLoadedContact(String accountId, Contact contact) {
-        return getLoadedContact(accountId, contact, false);
-    }
-
-    public Single<List<Contact>> getLoadedContact(String accountId, List<Contact> contacts, boolean withPresence) {
-        if (contacts.isEmpty())
-            return Single.just(Collections.emptyList());
-        return Observable.fromIterable(contacts)
-                .concatMapEager(contact -> getLoadedContact(accountId, contact, withPresence).toObservable())
-                .toList(contacts.size());
-    }
-
-    public List<Observable<Contact>> observeLoadedContact(String accountId, List<Contact> contacts, boolean withPresence) {
-        if (contacts.isEmpty())
-            return Collections.emptyList();
-        List<Observable<Contact>> ret = new ArrayList<>(contacts.size());
-        for (Contact contact : contacts)
-            ret.add(observeContact(accountId, contact, withPresence)
-                    .filter(c -> c.isUsernameLoaded() && c.detailsLoaded));
-        return ret;
-    }
-
-    /**
-     * Searches a contact in the local cache and then in the system repository
-     * In the last case, the contact is created and added to the local cache
-     *
-     * @return The found/created contact
-     */
-    public Contact findContactByNumber(Account account, String number) {
-        if (StringUtils.isEmpty(number) || account == null) {
-            return null;
-        }
-        return findContact(account, Uri.fromString(number));
-    }
-
-    public Contact findContact(Account account, Uri uri) {
-        if (uri == null || account == null) {
-            return null;
-        }
-
-        Contact contact = account.getContactFromCache(uri);
-        // TODO load system contact info into SIP contact
-        if (account.isSip()) {
-            loadContactData(contact, account.getAccountID()).subscribe(() -> {}, e -> Log.e(TAG, "Can't load contact data"));
-        }
-        return contact;
-    }
-}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt b/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt
new file mode 100644
index 000000000..755ea2e3c
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt
@@ -0,0 +1,183 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.services
+
+import ezvcard.VCard
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.ObservableEmitter
+import io.reactivex.rxjava3.core.Single
+import net.jami.model.Account
+import net.jami.model.Contact
+import net.jami.model.Uri
+import net.jami.utils.Log
+import java.util.*
+import java.util.concurrent.TimeUnit
+
+/**
+ * This service handles the contacts
+ * - Load the contacts stored in the system
+ * - Keep a local cache of the contacts
+ * - Provide query tools to search contacts by id, number, ...
+ */
+abstract class ContactService(
+    val mPreferencesService: PreferencesService,
+    val mDeviceRuntimeService: DeviceRuntimeService,
+    val mAccountService: AccountService
+) {
+    abstract fun loadContactsFromSystem(loadRingContacts: Boolean, loadSipContacts: Boolean): Map<Long, Contact>
+    protected abstract fun findContactByIdFromSystem(contactId: Long, contactKey: String): Contact?
+    protected abstract fun findContactBySipNumberFromSystem(number: String): Contact?
+    protected abstract fun findContactByNumberFromSystem(number: String): Contact?
+    abstract fun loadContactData(contact: Contact, accountId: String): Completable
+    abstract fun saveVCardContactData(contact: Contact, accountId: String, vcard: VCard)
+    abstract fun saveVCardContact(accountId: String, uri: String, displayName: String, pictureB64: String): Single<VCard>
+
+    /**
+     * Load contacts from system and generate a local contact cache
+     *
+     * @param loadRingContacts if true, ring contacts will be taken care of
+     * @param loadSipContacts  if true, sip contacts will be taken care of
+     */
+    fun loadContacts(loadRingContacts: Boolean, loadSipContacts: Boolean, account: Account?): Single<Map<Long, Contact>> {
+        return Single.fromCallable {
+            val settings = mPreferencesService.settings
+            if (settings.isAllowSystemContacts && mDeviceRuntimeService.hasContactPermission()) {
+                return@fromCallable loadContactsFromSystem(loadRingContacts, loadSipContacts)
+            }
+            HashMap()
+        }
+    }
+
+    fun observeContact(accountId: String, contact: Contact, withPresence: Boolean): Observable<Contact> {
+        //Log.w(TAG, "observeContact " + accountId + " " + contact.getUri() + " " + contact.isUser());
+        var withPresence = withPresence
+        if (contact.isUser) withPresence = false
+        val uri = contact.uri
+        val uriString = uri.rawUriString
+        synchronized(contact) {
+            if (contact.presenceUpdates == null) {
+                contact.presenceUpdates = Observable.create { emitter: ObservableEmitter<Boolean> ->
+                    emitter.onNext(false)
+                    contact.setPresenceEmitter(emitter)
+                    mAccountService.subscribeBuddy(accountId, uriString, true)
+                    emitter.setCancellable {
+                        mAccountService.subscribeBuddy(accountId, uriString, false)
+                        contact.setPresenceEmitter(null)
+                        emitter.onNext(false)
+                    }
+                }
+                    .replay(1)
+                    .refCount(5, TimeUnit.SECONDS)
+            }
+            if (contact.updates == null) {
+                contact.updates = contact.updatesSubject
+                    .doOnSubscribe {
+                        if (!contact.isUsernameLoaded) mAccountService.lookupAddress(accountId, "", uri.rawRingId)
+                        loadContactData(contact, accountId)
+                            .subscribe({}) { }
+                    }
+                    .filter { c: Contact -> c.isUsernameLoaded && c.detailsLoaded }
+                    .replay(1)
+                    .refCount()
+            }
+            return if (withPresence) Observable.combineLatest(
+                contact.updates,
+                contact.presenceUpdates,
+                { c: Contact, p: Boolean -> c }) else contact.updates!!
+        }
+    }
+
+    fun observeContact(accountId: String, contacts: List<Contact>, withPresence: Boolean): Observable<List<Contact>> {
+        return if (contacts.isEmpty()) {
+            Observable.just(emptyList())
+        } /*else if (contacts.size() == 1 || contacts.size() == 2) {
+
+            return observeContact(accountId, contacts.get(contacts.size() - 1), withPresence).map(Collections::singletonList);
+        } */ else {
+            val observables: MutableList<Observable<Contact>> = ArrayList(contacts.size)
+            for (contact in contacts) {
+                if (!contact.isUser) observables.add(observeContact(accountId, contact, withPresence))
+            }
+            if (observables.isEmpty()) Observable.just(emptyList()) else Observable.combineLatest(observables) { a: Array<Any> ->
+                val obs: MutableList<Contact> = ArrayList(a.size)
+                for (o in a) obs.add(o as Contact)
+                obs
+            }
+        }
+    }
+
+    fun getLoadedContact(accountId: String, contact: Contact, withPresence: Boolean): Single<Contact> {
+        return observeContact(accountId, contact, withPresence)
+            .filter { c: Contact -> c.isUsernameLoaded && c.detailsLoaded }
+            .firstOrError()
+    }
+
+    fun getLoadedContact(accountId: String, contact: Contact): Single<Contact> {
+        return getLoadedContact(accountId, contact, false)
+    }
+
+    fun getLoadedContact(accountId: String, contacts: List<Contact>, withPresence: Boolean): Single<List<Contact>> {
+        return if (contacts.isEmpty()) Single.just(emptyList()) else Observable.fromIterable(contacts)
+            .concatMapEager { contact: Contact -> getLoadedContact(accountId, contact, withPresence).toObservable() }
+            .toList(contacts.size)
+    }
+
+    fun observeLoadedContact(
+        accountId: String,
+        contacts: List<Contact>,
+        withPresence: Boolean
+    ): List<Observable<Contact>> {
+        if (contacts.isEmpty()) return emptyList()
+        val ret: MutableList<Observable<Contact>> = ArrayList(contacts.size)
+        for (contact in contacts) ret.add(observeContact(accountId, contact, withPresence)
+            .filter { c: Contact -> c.isUsernameLoaded && c.detailsLoaded })
+        return ret
+    }
+
+    /**
+     * Searches a contact in the local cache and then in the system repository
+     * In the last case, the contact is created and added to the local cache
+     *
+     * @return The found/created contact
+     */
+    fun findContactByNumber(account: Account, number: String): Contact? {
+        return if (number.isEmpty()) null else findContact(account, Uri.fromString(number))
+    }
+
+    fun findContact(account: Account, uri: Uri): Contact {
+        val contact = account.getContactFromCache(uri)
+        // TODO load system contact info into SIP contact
+        if (account.isSip) {
+            loadContactData(contact, account.accountID).subscribe({}) { e: Throwable? ->
+                Log.e(
+                    TAG,
+                    "Can't load contact data"
+                )
+            }
+        }
+        return contact
+    }
+
+    companion object {
+        private val TAG = ContactService::class.simpleName!!
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt b/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt
new file mode 100644
index 000000000..6c369270a
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt
@@ -0,0 +1,702 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.services
+
+import io.reactivex.rxjava3.core.Completable
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.disposables.CompositeDisposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.model.*
+import net.jami.model.Account.ContactLocationEntry
+import net.jami.model.Call.CallStatus
+import net.jami.model.Interaction.InteractionStatus
+import net.jami.model.Uri.Companion.fromString
+import net.jami.services.AccountService.RegisteredName
+import net.jami.smartlist.SmartListViewModel
+import net.jami.utils.FileUtils.moveFile
+import net.jami.utils.Log
+import net.jami.utils.Tuple
+import java.io.File
+import java.util.*
+import java.util.concurrent.TimeUnit
+
+class ConversationFacade(
+    private val mHistoryService: HistoryService,
+    private val mCallService: CallService,
+    private val mAccountService: AccountService,
+    private val mContactService: ContactService,
+    private val mNotificationService: NotificationService,
+    private val mHardwareService: HardwareService,
+    private val mDeviceRuntimeService: DeviceRuntimeService,
+    private val mPreferencesService: PreferencesService
+) {
+    private val mDisposableBag = CompositeDisposable()
+    val currentAccountSubject: Observable<Account> = mAccountService
+        .currentAccountSubject
+        .switchMapSingle { account: Account -> loadSmartlist(account) }
+    private val conversationSubject: Subject<Conversation> = PublishSubject.create()
+    val updatedConversation: Observable<Conversation>
+        get() = conversationSubject
+
+    fun startConversation(accountId: String, contactId: Uri): Single<Conversation> {
+        return getAccountSubject(accountId)
+            .map { account: Account -> account.getByUri(contactId) }
+    }
+
+    fun getAccountSubject(accountId: String): Single<Account> {
+        return mAccountService
+            .getAccountSingle(accountId)
+            .flatMap { account: Account -> loadSmartlist(account) }
+    }
+
+    val conversationsSubject: Observable<List<Conversation>>
+        get() = currentAccountSubject
+            .switchMap { obj: Account -> obj.getConversationsSubject() }
+
+    fun readMessages(accountId: String, contact: Uri): String? {
+        val account = mAccountService.getAccount(accountId)
+        return if (account != null) readMessages(account, account.getByUri(contact), true) else null
+    }
+
+    fun readMessages(account: Account, conversation: Conversation?, cancelNotification: Boolean): String? {
+        if (conversation != null) {
+            val lastMessage = readMessages(conversation)
+            if (lastMessage != null) {
+                account.refreshed(conversation)
+                if (mPreferencesService.settings.isAllowReadIndicator) {
+                    mAccountService.setMessageDisplayed(account.accountID, conversation.uri, lastMessage)
+                }
+                if (cancelNotification) {
+                    mNotificationService.cancelTextNotification(account.accountID, conversation.uri)
+                }
+            }
+            return lastMessage
+        }
+        return null
+    }
+
+    private fun readMessages(conversation: Conversation): String? {
+        var lastRead: String? = null
+        if (conversation.isSwarm) {
+            lastRead = conversation.readMessages()
+            if (lastRead != null) mHistoryService.setMessageRead(conversation.accountId, conversation.uri, lastRead)
+        } else {
+            val messages = conversation.rawHistory
+            for (e in messages.descendingMap().values) {
+                if (e.type != Interaction.InteractionType.TEXT) continue
+                if (e.isRead) {
+                    break
+                }
+                e.read()
+                val did = e.daemonId
+                if (lastRead == null && did != null && did != 0L) lastRead = java.lang.Long.toString(did, 16)
+                mHistoryService.updateInteraction(e, conversation.accountId).subscribe()
+            }
+        }
+        return lastRead
+    }
+
+    fun sendTextMessage(c: Conversation, to: Uri, txt: String?): Completable {
+        if (c.isSwarm) {
+            mAccountService.sendConversationMessage(c.accountId, c.uri, txt!!)
+            return Completable.complete()
+        }
+        return mCallService.sendAccountTextMessage(c.accountId, to.rawUriString, txt!!)
+            .map { id: Long ->
+                val message = TextMessage(null, c.accountId, java.lang.Long.toHexString(id), c, txt)
+                if (c.isVisible) message.read()
+                mHistoryService.insertInteraction(c.accountId, c, message).subscribe()
+                c.addTextMessage(message)
+                mAccountService.getAccount(c.accountId)!!.conversationUpdated(c)
+                message
+            }.ignoreElement()
+    }
+
+    fun sendTextMessage(c: Conversation, conf: Conference, txt: String?) {
+        mCallService.sendTextMessage(conf.id, txt)
+        val message = TextMessage(null, c.accountId, conf.id, c, txt!!)
+        message.read()
+        mHistoryService.insertInteraction(c.accountId, c, message).subscribe()
+        c.addTextMessage(message)
+    }
+
+    fun setIsComposing(accountId: String, conversationUri: Uri, isComposing: Boolean) {
+        mCallService.setIsComposing(accountId, conversationUri.uri, isComposing)
+    }
+
+    fun sendFile(conversation: Conversation, to: Uri, file: File?): Completable {
+        if (file == null || !file.exists() || !file.canRead()) {
+            return Completable.error(IllegalArgumentException("file not found or not readable"))
+        }
+        if (conversation.isSwarm) {
+            val destPath = mDeviceRuntimeService.getNewConversationPath(conversation.accountId, conversation.uri.rawRingId, file.name)
+            moveFile(file, destPath)
+            mAccountService.sendFile(conversation, destPath)
+            return Completable.complete()
+        }
+        return Single.fromCallable {
+            val transfer = DataTransfer(
+                conversation,
+                to.rawRingId,
+                conversation.accountId,
+                file.name,
+                true,
+                file.length(),
+                0,
+                null
+            )
+            mHistoryService.insertInteraction(conversation.accountId, conversation, transfer).blockingAwait()
+            transfer.destination = mDeviceRuntimeService.getConversationDir(conversation.uri.rawRingId)
+            transfer
+        }
+            .flatMap { t: DataTransfer -> mAccountService.sendFile(file, t) }
+            .flatMapCompletable { transfer: DataTransfer ->
+                Completable.fromAction {
+                    val destination = File(transfer.destination, transfer.storagePath)
+                    if (!mDeviceRuntimeService.hardLinkOrCopy(file, destination)) {
+                        Log.e(TAG, "sendFile: can't move file to $destination")
+                    }
+                }
+            }
+            .subscribeOn(Schedulers.io())
+    }
+
+    fun deleteConversationItem(conversation: Conversation?, element: Interaction) {
+        if (element.type === Interaction.InteractionType.DATA_TRANSFER) {
+            val transfer = element as DataTransfer
+            if (transfer.status === InteractionStatus.TRANSFER_ONGOING) {
+                mAccountService.cancelDataTransfer(
+                    conversation!!.accountId,
+                    conversation.uri.rawRingId,
+                    transfer.messageId,
+                    transfer.fileId!!
+                )
+            } else {
+                val file = mDeviceRuntimeService.getConversationPath(conversation!!.uri.rawRingId, transfer.storagePath)
+                mDisposableBag.add(Completable.mergeArrayDelayError(
+                    mHistoryService.deleteInteraction(element.id, element.account),
+                    Completable.fromAction { file.delete() }
+                        .subscribeOn(Schedulers.io()))
+                    .subscribe(
+                        { conversation.removeInteraction(transfer) }
+                    ) { e: Throwable? -> Log.e(TAG, "Can't delete file transfer", e) })
+            }
+        } else {
+            // handling is the same for calls and texts
+            mDisposableBag.add(mHistoryService.deleteInteraction(element.id, element.account)
+                .subscribeOn(Schedulers.io())
+                .subscribe(
+                    { conversation!!.removeInteraction(element) }
+                ) { e: Throwable? -> Log.e(TAG, "Can't delete message", e) })
+        }
+    }
+
+    fun cancelMessage(message: Interaction) {
+        val accountId = message.account!!
+        mDisposableBag.add(
+            Completable.mergeArrayDelayError(mCallService.cancelMessage(accountId, message.id.toLong()).subscribeOn(Schedulers.io()))
+                .andThen(startConversation(accountId, fromString(message.conversation!!.participant)))
+                .subscribe({ c: Conversation -> c.removeInteraction(message) }
+                ) { e: Throwable? -> Log.e(TAG, "Can't cancel message sending", e) })
+    }
+
+    /**
+     * Loads the smartlist from cache or database
+     *
+     * @param account the user account
+     * @return an account single
+     */
+    private fun loadSmartlist(account: Account): Single<Account> {
+        synchronized(account) {
+            account.historyLoader.let { loader ->
+                return loader ?: getSmartlist(account).apply {
+                    account.historyLoader = this
+                }
+            }
+        }
+    }
+
+    /**
+     * Loads history for a specific conversation from cache or database
+     *
+     * @param account         the user account
+     * @param conversationUri the conversation
+     * @return a conversation single
+     */
+    fun loadConversationHistory(account: Account, conversationUri: Uri): Single<Conversation> {
+        val conversation = account.getByUri(conversationUri)
+            ?: return Single.error(RuntimeException("Can't get conversation"))
+        synchronized(conversation) {
+            if (!conversation.isSwarm && conversation.id == null) {
+                return Single.just(conversation)
+            }
+            var ret = conversation.loaded
+            if (ret == null) {
+                ret = if (conversation.isSwarm) mAccountService.loadMore(conversation) else getConversationHistory(
+                    conversation
+                )
+                conversation.loaded = ret
+            }
+            return ret
+        }
+    }
+
+    private fun observeConversation(account: Account, conversation: Conversation, hasPresence: Boolean): Observable<SmartListViewModel> {
+        return Observable.merge(account.getConversationSubject()
+            .filter { c: Conversation -> c == conversation }
+            .startWithItem(conversation),
+            mContactService.observeContact(conversation.accountId, conversation.contacts, hasPresence))
+            .map { SmartListViewModel(conversation, hasPresence) }
+        /*return account.getConversationSubject()
+                .filter(c -> c == conversation)
+                .startWith(conversation)
+                .switchMap(c -> mContactService
+                        .observeContact(c.getAccountId(), c.getContacts(), hasPresence)
+                        .map(contact -> new SmartListViewModel(c, hasPresence)));*/
+    }
+
+    fun getSmartList(currentAccount: Observable<Account>, hasPresence: Boolean): Observable<List<Observable<SmartListViewModel>>> {
+        return currentAccount.switchMap { account: Account ->
+            account.getConversationsSubject()
+                .switchMapSingle { conversations -> Observable.fromIterable(conversations)
+                    .map { conversation: Conversation -> observeConversation(account, conversation, hasPresence) }
+                    .toList() } }
+    }
+
+    fun getContactList(currentAccount: Observable<Account>): Observable<List<SmartListViewModel>> {
+        return currentAccount.switchMap { account: Account ->
+            account.getConversationsSubject()
+                .switchMapSingle { conversations: List<Conversation>? ->
+                    Observable.fromIterable(conversations)
+                        .filter { conversation: Conversation -> !conversation.isSwarm }
+                        .map { conversation: Conversation -> SmartListViewModel(conversation, false) }
+                        .toList()
+                }
+        }
+    }
+
+    fun getPendingList(currentAccount: Observable<Account>): Observable<List<Observable<SmartListViewModel>>> {
+        return currentAccount.switchMap { account: Account ->
+            account.getPendingSubject()
+                .switchMapSingle { conversations -> Observable.fromIterable(conversations)
+                    .map { conversation: Conversation -> observeConversation(account, conversation, false) }
+                    .toList() } }
+    }
+
+    fun getSmartList(hasPresence: Boolean): Observable<List<Observable<SmartListViewModel>>> {
+        return getSmartList(mAccountService.currentAccountSubject, hasPresence)
+    }
+
+    val pendingList: Observable<List<Observable<SmartListViewModel>>>
+        get() = getPendingList(mAccountService.currentAccountSubject)
+    val contactList: Observable<List<SmartListViewModel>>
+        get() = getContactList(mAccountService.currentAccountSubject)
+
+    private fun getSearchResults(account: Account, query: String): Single<List<Observable<SmartListViewModel>>> {
+        val uri = fromString(query)
+        return if (account.isSip) {
+            val contact = account.getContactFromCache(uri)
+            mContactService.loadContactData(contact, account.accountID)
+                .andThen(Single.just(listOf(Observable.just(SmartListViewModel(account.accountID, contact, contact.primaryNumber, null)))))
+        } else if (uri.isHexId) {
+            mContactService.getLoadedContact(account.accountID, account.getContactFromCache(uri))
+                .map { contact -> listOf(Observable.just(SmartListViewModel(account.accountID, contact, contact.primaryNumber, null))) }
+        } else if (account.canSearch() && !query.contains("@")) {
+            mAccountService.searchUser(account.accountID, query)
+                .map(AccountService.UserSearchResult::resultsViewModels)
+        } else {
+            mAccountService.findRegistrationByName(account.accountID, "", query)
+                .map { result: RegisteredName ->
+                    if (result.state == 0)
+                        listOf(observeConversation(account, account.getByUri(result.address)!!, false))
+                    else
+                        emptyList()
+                }
+        }
+    }
+
+    private fun getSearchResults(account: Account, query: Observable<String>): Observable<List<Observable<SmartListViewModel>>> {
+        return query.switchMapSingle { q: String ->
+            if (q.isEmpty())
+                SmartListViewModel.EMPTY_LIST
+            else getSearchResults(account, q)
+        }.distinctUntilChanged()
+    }
+
+    fun getFullList(currentAccount: Observable<Account>, query: Observable<String>, hasPresence: Boolean): Observable<List<Observable<SmartListViewModel>>> {
+        return currentAccount.switchMap { account: Account ->
+            Observable.combineLatest<List<Conversation>, List<Observable<SmartListViewModel>>, String, List<Observable<SmartListViewModel>>>(
+                account.getConversationsSubject(),
+                getSearchResults(account, query),
+                query,
+                { conversations: List<Conversation>, searchResults: List<Observable<SmartListViewModel>>, q: String ->
+                    val newList: MutableList<Observable<SmartListViewModel>> =
+                        ArrayList(conversations.size + searchResults.size + 2)
+                    if (searchResults.isNotEmpty()) {
+                        newList.add(SmartListViewModel.TITLE_PUBLIC_DIR)
+                        newList.addAll(searchResults)
+                    }
+                    if (conversations.isNotEmpty()) {
+                        if (q.isEmpty()) {
+                            for (conversation in conversations)
+                                newList.add(observeConversation(account, conversation, hasPresence))
+                        } else {
+                            val lq = q.lowercase()
+                            newList.add(SmartListViewModel.TITLE_CONVERSATIONS)
+                            var nRes = 0
+                            for (conversation in conversations) {
+                                if (conversation.matches(lq)) {
+                                    newList.add(observeConversation(account, conversation, hasPresence))
+                                    nRes++
+                                }
+                            }
+                            if (nRes == 0) newList.removeAt(newList.size - 1)
+                        }
+                    }
+                    newList
+                })
+        }
+    }
+
+    /**
+     * Loads the smartlist from the database and updates the view
+     *
+     * @param account the user account
+     */
+    private fun getSmartlist(account: Account): Single<Account> {
+        val actions: MutableList<Completable?> = ArrayList(account.getConversations().size + 1)
+        for (c in account.getConversations()) {
+            if (c.isSwarm) actions.add(c.lastElementLoaded)
+        }
+        actions.add(mHistoryService.getSmartlist(account.accountID)
+            .flatMapCompletable { conversationHistoryList: List<Interaction> ->
+                Completable.fromAction {
+                    val conversations: MutableList<Conversation> = ArrayList()
+                    for (e in conversationHistoryList) {
+                        val conversation = account.getByUri(e.conversation!!.participant) ?: continue
+                        conversation.id = e.conversation!!.id
+                        conversation.addElement(e)
+                        conversations.add(conversation)
+                    }
+                    account.setHistoryLoaded(conversations)
+                }
+            })
+        return Completable.merge(actions)
+            .andThen(Single.just(account))
+            .cache()
+    }
+
+    /**
+     * Loads a conversation's history from the database
+     *
+     * @param conversation a conversation object with a valid conversation ID
+     * @return a conversation single
+     */
+    private fun getConversationHistory(conversation: Conversation): Single<Conversation> {
+        Log.d(TAG, "getConversationHistory() " + conversation.accountId + " " + conversation.uri)
+        return mHistoryService.getConversationHistory(conversation.accountId, conversation.id)
+            .map { loadedConversation: List<Interaction> ->
+                conversation.clearHistory(true)
+                conversation.setHistory(loadedConversation)
+                conversation
+            }
+            .cache()
+    }
+
+    fun clearHistory(accountId: String, contact: Uri): Completable {
+        return mHistoryService
+            .clearHistory(contact.uri, accountId, false)
+            .doOnSubscribe {
+                mAccountService.getAccount(accountId)?.clearHistory(contact, false)
+            }
+    }
+
+    fun clearAllHistory(): Completable {
+        return mAccountService.observableAccountList
+            .firstElement()
+            .flatMapCompletable { accounts: List<Account> ->
+                mHistoryService.clearHistory(accounts)
+                    .doOnSubscribe {
+                        for (account in accounts)
+                            account.clearAllHistory()
+                    }
+            }
+    }
+
+    fun updateTextNotifications(accountId: String, conversations: List<Conversation>) {
+        Log.d(TAG, "updateTextNotifications() " + accountId + " " + conversations.size)
+        for (conversation in conversations) {
+            mNotificationService.showTextNotification(accountId, conversation)
+        }
+    }
+
+    private fun parseNewMessage(txt: TextMessage) {
+        val accountId = txt.account!!
+        if (txt.isRead) {
+            if (txt.messageId == null) {
+                mHistoryService.updateInteraction(txt, accountId).subscribe()
+            }
+            if (mPreferencesService.settings.isAllowReadIndicator) {
+                if (txt.messageId != null) {
+                    mAccountService.setMessageDisplayed(txt.account, Uri(Uri.SWARM_SCHEME, txt.conversationId!!), txt.messageId)
+                } else {
+                    mAccountService.setMessageDisplayed(txt.account, Uri(Uri.JAMI_URI_SCHEME, txt.author!!), txt.daemonIdString)
+                }
+            }
+        }
+        getAccountSubject(accountId)
+            .flatMapObservable { obj: Account -> obj.getConversationsSubject() }
+            .firstOrError()
+            .subscribeOn(Schedulers.io())
+            .subscribe({ c: List<Conversation> -> updateTextNotifications(txt.account!!, c) })
+            { e: Throwable -> Log.e(TAG, e.message) }
+    }
+
+    fun acceptRequest(accountId: String, contactUri: Uri) {
+        mPreferencesService.removeRequestPreferences(accountId, contactUri.rawRingId)
+        mAccountService.acceptTrustRequest(accountId, contactUri)
+    }
+
+    fun discardRequest(accountId: String, contact: Uri) {
+        //mHistoryService.clearHistory(contact.uri, accountId, true).subscribe()
+        mPreferencesService.removeRequestPreferences(accountId, contact.rawRingId)
+        mAccountService.discardTrustRequest(accountId, contact)
+    }
+
+    private fun handleDataTransferEvent(transfer: DataTransfer) {
+        val conversation = mAccountService.getAccount(transfer.account!!)!!.onDataTransferEvent(transfer)
+        val status = transfer.status
+        Log.d(TAG, "handleDataTransferEvent $status " + transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.account)))
+        if (status === InteractionStatus.TRANSFER_AWAITING_HOST || status === InteractionStatus.FILE_AVAILABLE) {
+            if (transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.account))) {
+                mAccountService.acceptFileTransfer(conversation, transfer.fileId!!, transfer)
+                return
+            }
+        }
+        mNotificationService.handleDataTransferNotification(transfer, conversation, conversation.isVisible)
+    }
+
+    private fun onConfStateChange(conference: Conference) {
+        Log.d(TAG, "onConfStateChange Thread id: " + Thread.currentThread().id)
+    }
+
+    private fun onCallStateChange(call: Call) {
+        Log.d(TAG, "onCallStateChange Thread id: " + Thread.currentThread().id)
+        val newState = call.callStatus
+        val incomingCall = newState === CallStatus.RINGING && call.isIncoming
+        mHardwareService.updateAudioState(newState, incomingCall, !call.isAudioOnly)
+        val account = mAccountService.getAccount(call.account!!) ?: return
+        val contact = call.contact
+        val conversationId = call.conversationId
+        Log.w(TAG, "CallStateChange " + call.daemonIdString + " conversationId:" + conversationId)
+        val conversation = if (conversationId == null)
+            if (contact == null) null else account.getByUri(contact.uri)
+        else
+            account.getSwarm(conversationId)
+        var conference: Conference? = null
+        if (conversation != null) {
+            conference = conversation.getConference(call.daemonIdString)
+            if (conference == null) {
+                if (newState === CallStatus.OVER) return
+                conference = Conference(call)
+                conversation.addConference(conference)
+                account.updated(conversation)
+            }
+        }
+        Log.w(TAG, "CALL_STATE_CHANGED : updating call state to $newState")
+        if ((call.isRinging || newState === CallStatus.CURRENT) && call.timestamp == 0L) {
+            call.timestamp = System.currentTimeMillis()
+        }
+        if (incomingCall) {
+            mNotificationService.handleCallNotification(conference, false)
+            mHardwareService.setPreviewSettings()
+        } else if (newState === CallStatus.CURRENT && call.isIncoming
+            || newState === CallStatus.RINGING && !call.isIncoming
+        ) {
+            mNotificationService.handleCallNotification(conference, false)
+            mAccountService.sendProfile(call.daemonIdString!!, call.account!!)
+        } else if (newState === CallStatus.HUNGUP || newState === CallStatus.BUSY || newState === CallStatus.FAILURE || newState === CallStatus.OVER) {
+            mNotificationService.handleCallNotification(conference, true)
+            mHardwareService.closeAudioState()
+            val now = System.currentTimeMillis()
+            if (call.timestamp == 0L) {
+                call.timestamp = now
+            }
+            if (newState === CallStatus.HUNGUP || call.timestampEnd == 0L) {
+                call.timestampEnd = now
+            }
+            if (conference != null && conference.removeParticipant(call) && !conversation!!.isSwarm) {
+                Log.w(TAG, "Adding call history for conversation " + conversation.uri)
+                mHistoryService.insertInteraction(account.accountID, conversation, call).subscribe()
+                conversation.addCall(call)
+                if (call.isIncoming && call.isMissed) {
+                    mNotificationService.showMissedCallNotification(call)
+                }
+                account.updated(conversation)
+            }
+            mCallService.removeCallForId(call.daemonIdString!!)
+            if (conversation != null && conference!!.participants.isEmpty()) {
+                conversation.removeConference(conference)
+            }
+        }
+    }
+
+    fun placeCall(accountId: String, contactUri: Uri, video: Boolean): Single<Call> {
+        //String rawId = contactUri.getRawRingId();
+        return getAccountSubject(accountId).flatMap { account: Account ->
+            mCallService.placeCall(accountId, null, contactUri, video) }
+    }
+
+    fun cancelFileTransfer(accountId: String, conversationId: Uri, messageId: String?, fileId: String?) {
+        mAccountService.cancelDataTransfer(accountId,
+            if (conversationId.isSwarm) conversationId.rawRingId else "",
+            messageId, fileId!!)
+        mNotificationService.removeTransferNotification(accountId, conversationId, fileId)
+        val transfer = mAccountService.getAccount(accountId)?.getDataTransfer(fileId)
+        if (transfer != null)
+            deleteConversationItem(transfer.conversation as Conversation?, transfer)
+    }
+
+    fun removeConversation(accountId: String, conversationUri: Uri): Completable {
+        return if (conversationUri.isSwarm) {
+            // For a one to one conversation, contact is strongly related, so remove the contact.
+            // This will remove related conversations
+            val account = mAccountService.getAccount(accountId)
+            val conversation = account!!.getSwarm(conversationUri.rawRingId)
+            if (conversation != null && conversation.mode.blockingFirst() === Conversation.Mode.OneToOne) {
+                val contact = conversation.contact
+                mAccountService.removeContact(accountId, contact!!.uri.rawRingId, false)
+                Completable.complete()
+            } else {
+                mAccountService.removeConversation(accountId, conversationUri)
+            }
+        } else {
+            mHistoryService
+                .clearHistory(conversationUri.uri, accountId, true)
+                .doOnSubscribe {
+                    mAccountService.getAccount(accountId)?.clearHistory(conversationUri, true)
+                    mAccountService.removeContact(accountId, conversationUri.rawRingId, false)
+                }
+        }
+    }
+
+    fun banConversation(accountId: String, conversationUri: Uri) {
+        if (conversationUri.isSwarm) {
+            startConversation(accountId, conversationUri)
+                .subscribe { conversation: Conversation ->
+                    try {
+                        val contact = conversation.contact
+                        mAccountService.removeContact(accountId, contact!!.uri.rawUriString, true)
+                    } catch (e: Exception) {
+                        mAccountService.removeConversation(accountId, conversationUri)
+                    }
+                }
+            //return mAccountService.removeConversation(accountId, conversationUri);
+        } else {
+            mAccountService.removeContact(accountId, conversationUri.rawUriString, true)
+        }
+    }
+
+    fun createConversation(accountId: String, currentSelection: Collection<Contact>): Single<Conversation> {
+        val contactIds = currentSelection.map { contact -> contact.primaryNumber }
+        return mAccountService.startConversation(accountId, contactIds)
+    }
+
+    companion object {
+        private val TAG = ConversationFacade::class.simpleName!!
+    }
+
+    init {
+        mDisposableBag.add(mCallService.callsUpdates
+            .subscribe { call: Call -> onCallStateChange(call) })
+
+        /*mDisposableBag.add(mCallService.getConnectionUpdates()
+                    .subscribe(mNotificationService::onConnectionUpdate));*/
+        mDisposableBag.add(mCallService.confsUpdates
+            .observeOn(Schedulers.io())
+            .subscribe { conference: Conference -> onConfStateChange(conference) })
+        mDisposableBag.add(currentAccountSubject
+            .switchMap { a: Account -> a.getPendingSubject()
+                    .doOnNext { mNotificationService.showIncomingTrustRequestNotification(a) } }
+            .subscribe())
+        mDisposableBag.add(mAccountService.incomingRequests
+            .concatMapSingle { r: TrustRequest -> getAccountSubject(r.accountId) }
+            .subscribe(
+                { account: Account? -> mNotificationService.showIncomingTrustRequestNotification(account) }
+            ) { Log.e(TAG, "Error showing contact request") })
+        mDisposableBag.add(mAccountService
+            .incomingMessages
+            .concatMapSingle { msg: TextMessage ->
+                getAccountSubject(msg.account!!)
+                    .map { a: Account ->
+                        a.addTextMessage(msg)
+                        msg }
+            }
+            .subscribe({ txt: TextMessage -> parseNewMessage(txt) }
+            ) { e: Throwable -> Log.e(TAG, "Error adding text message", e) })
+        mDisposableBag.add(mAccountService.incomingSwarmMessages
+                .subscribe({ txt: TextMessage -> parseNewMessage(txt) },
+                    { e: Throwable -> Log.e(TAG, "Error adding text message", e) }))
+        mDisposableBag.add(mAccountService.locationUpdates
+            .concatMapSingle { location: AccountService.Location ->
+                getAccountSubject(location.account)
+                    .map { a: Account ->
+                        val expiration = a.onLocationUpdate(location)
+                        mDisposableBag.add(
+                            Completable.timer(expiration, TimeUnit.MILLISECONDS)
+                                .subscribe { a.maintainLocation() })
+                        location
+                    } }
+            .subscribe())
+        mDisposableBag.add(mAccountService.observableAccountList
+            .switchMap { accounts: List<Account> ->
+                val r: MutableList<Observable<Tuple<Account, ContactLocationEntry>>> = ArrayList(accounts.size)
+                for (a in accounts) r.add(a.locationUpdates.map { s: ContactLocationEntry -> Tuple(a, s) })
+                Observable.merge(r)
+            }
+            .distinctUntilChanged()
+            .subscribe { t: Tuple<Account, ContactLocationEntry> ->
+                Log.e(TAG, "Location reception started for " + t.second.contact)
+                mNotificationService.showLocationNotification(t.first, t.second.contact)
+                mDisposableBag.add(t.second.location.doOnComplete {
+                    mNotificationService.cancelLocationNotification(t.first, t.second.contact)
+                }.subscribe()) })
+        mDisposableBag.add(mAccountService
+            .messageStateChanges
+            .concatMapSingle { e: Interaction ->
+                getAccountSubject(e.account!!)
+                    .map { a: Account ->
+                        if (e.conversation == null)
+                            a.getSwarm(e.conversationId!!)
+                        else
+                            a.getByUri(e.conversation!!.participant)
+                    }
+                    .doOnSuccess { conversation: Conversation? -> conversation!!.updateInteraction(e) }
+            }
+            .subscribe({}) { e: Throwable -> Log.e(TAG, "Error updating text message", e) })
+        mDisposableBag.add(mAccountService.dataTransfers
+                .subscribe({ transfer: DataTransfer -> handleDataTransferEvent(transfer) },
+                     { e: Throwable -> Log.e(TAG, "Error adding data transfer", e) }))
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java
index bbe45d2b9..d947844bf 100644
--- a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java
+++ b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java
@@ -46,22 +46,11 @@ public class DaemonService {
 
     private static final String TAG = DaemonService.class.getSimpleName();
 
-    @Inject
     @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-
-    @Inject
-    HistoryService mHistoryService;
-
-    @Inject
-    protected CallService mCallService;
-
-    @Inject
-    protected HardwareService mHardwareService;
-
-    @Inject
-    protected AccountService mAccountService;
-
+    private final ScheduledExecutorService mExecutor;
+    private final CallService mCallService;
+    private final HardwareService mHardwareService;
+    private final AccountService mAccountService;
     private final SystemInfoCallbacks mSystemInfoCallbacks;
 
     // references must be kept to avoid garbage collection while pointers are stored in the daemon.
@@ -74,8 +63,12 @@ public class DaemonService {
 
     private boolean mDaemonStarted = false;
 
-    public DaemonService(SystemInfoCallbacks systemInfoCallbacks) {
+    public DaemonService(SystemInfoCallbacks systemInfoCallbacks, ScheduledExecutorService executor, CallService callService, HardwareService hardwareService, AccountService accountService) {
         mSystemInfoCallbacks = systemInfoCallbacks;
+        mExecutor = executor;
+        mCallService = callService;
+        mHardwareService = hardwareService;
+        mAccountService = accountService;
     }
 
     public interface SystemInfoCallbacks {
@@ -412,6 +405,10 @@ public class DaemonService {
             mAccountService.conversationRequestReceived(accountId, conversationId, metadata.toNative());
         }
 
+        @Override
+        public void conversationRequestDeclined(String accountId, String conversationId) {
+            mAccountService.conversationRequestDeclined(accountId, conversationId);
+        }
         @Override
         public void conversationMemberEvent(String accountId, String conversationId, String uri, int event) {
             mAccountService.conversationMemberEvent(accountId, conversationId, uri, event);
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java
deleted file mode 100644
index 80249df32..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.services;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import net.jami.daemon.IntVect;
-import net.jami.daemon.JamiService;
-import net.jami.daemon.StringMap;
-import net.jami.daemon.UintVect;
-import net.jami.model.Conference;
-import net.jami.model.Call;
-import net.jami.utils.Log;
-import net.jami.utils.StringUtils;
-import net.jami.utils.Tuple;
-
-import io.reactivex.rxjava3.core.Completable;
-import io.reactivex.rxjava3.core.Emitter;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.ObservableOnSubscribe;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public abstract class HardwareService {
-
-    private static final String TAG = HardwareService.class.getSimpleName();
-
-    @Inject
-    @Named("DaemonExecutor")
-    ScheduledExecutorService mExecutor;
-
-    @Inject
-    DeviceRuntimeService mDeviceRuntimeService;
-
-    @Inject
-    public PreferencesService mPreferenceService;
-
-    @Inject
-    @Named("UiScheduler")
-    protected Scheduler mUiScheduler;
-
-    public static class VideoEvent {
-        public boolean start = false;
-        public boolean started = false;
-        public int w = 0, h = 0;
-        public int rot = 0;
-        public String callId = null;
-    }
-    public static class BluetoothEvent {
-        public boolean connected;
-    }
-    public enum AudioOutput {
-        INTERNAL, SPEAKERS, BLUETOOTH
-    }
-    public static class AudioState {
-        private final AudioOutput outputType;
-        private final String outputName;
-
-        public AudioState(AudioOutput ot) { outputType = ot; outputName = null; }
-        public AudioState(AudioOutput ot, String name) { outputType = ot; outputName = name; }
-
-        public AudioOutput getOutputType() { return outputType; }
-        public String getOutputName() { return outputName; }
-    }
-    protected static final AudioState STATE_SPEAKERS = new AudioState(AudioOutput.SPEAKERS);
-    protected static final AudioState STATE_INTERNAL = new AudioState(AudioOutput.INTERNAL);
-
-    protected final Subject<VideoEvent> videoEvents = PublishSubject.create();
-    protected final Subject<BluetoothEvent> bluetoothEvents = PublishSubject.create();
-    protected final Subject<AudioState> audioStateSubject = BehaviorSubject.createDefault(STATE_INTERNAL);
-    protected final Subject<Boolean> connectivityEvents = BehaviorSubject.create();
-
-    public Observable<VideoEvent> getVideoEvents() {
-        return videoEvents;
-    }
-    public Observable<BluetoothEvent> getBluetoothEvents() {
-        return bluetoothEvents;
-    }
-    public Observable<AudioState> getAudioState() {
-        return audioStateSubject;
-    }
-    public Observable<Boolean> getConnectivityState() {
-        return connectivityEvents;
-    }
-
-    public abstract Completable initVideo();
-
-    public abstract boolean isVideoAvailable();
-
-    public abstract void updateAudioState(Call.CallStatus state, boolean incomingCall, boolean isOngoingVideo);
-
-    public abstract void closeAudioState();
-
-    public abstract boolean isSpeakerPhoneOn();
-
-    public abstract void toggleSpeakerphone(boolean checked);
-
-    public abstract void startRinging();
-
-    public abstract void stopRinging();
-
-    public abstract void abandonAudioFocus();
-
-    public abstract void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer);
-
-    public abstract void decodingStopped(String id, String shmPath, boolean isMixer);
-
-    public abstract void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates);
-
-    public abstract void setParameters(String camId, int format, int width, int height, int rate);
-
-    public abstract void startCapture(String camId);
-    public abstract boolean startScreenShare(Object mediaProjection);
-
-    public abstract boolean hasMicrophone();
-
-    public abstract void stopCapture();
-    public abstract void endCapture();
-    public abstract void stopScreenShare();
-
-    public abstract void requestKeyFrame();
-    public abstract void setBitrate(String device, int bitrate);
-
-    public abstract void addVideoSurface(String id, Object holder);
-    public abstract void updateVideoSurfaceId(String currentId, String newId);
-    public abstract void removeVideoSurface(String id);
-
-    public abstract void addPreviewVideoSurface(Object holder, Conference conference);
-    public abstract void updatePreviewVideoSurface(Conference conference);
-    public abstract void removePreviewVideoSurface();
-
-    public abstract void switchInput(String id, boolean setDefaultCamera);
-
-    public abstract void setPreviewSettings();
-
-    public abstract boolean hasCamera();
-    public abstract int getCameraCount();
-    public abstract Observable<Tuple<Integer, Integer>> getMaxResolutions();
-
-    public abstract boolean isPreviewFromFrontCamera();
-
-    public abstract boolean shouldPlaySpeaker();
-
-    public abstract void unregisterCameraDetectionCallback();
-
-    public abstract void startMediaHandler(String mediaHandlerId);
-
-    public abstract void stopMediaHandler();
-
-    public void connectivityChanged(boolean isConnected) {
-        Log.i(TAG, "connectivityChange() " + isConnected);
-        connectivityEvents.onNext(isConnected);
-        mExecutor.execute(JamiService::connectivityChanged);
-    }
-
-    protected void switchInput(final String id, final String uri) {
-        Log.i(TAG, "switchInput() " + uri);
-        mExecutor.execute(() -> JamiService.switchInput(id, uri));
-    }
-
-    public void setPreviewSettings(final Map<String, StringMap> cameraMaps) {
-        mExecutor.execute(() -> {
-            Log.i(TAG, "applySettings() thread running...");
-            for (Map.Entry<String, StringMap> entry : cameraMaps.entrySet()) {
-                JamiService.applySettings(entry.getKey(), entry.getValue());
-            }
-        });
-    }
-
-    public long startVideo(final String inputId, final Object surface, final int width, final int height) {
-        long inputWindow = JamiService.acquireNativeWindow(surface);
-        if (inputWindow == 0) {
-            return inputWindow;
-        }
-        JamiService.setNativeWindowGeometry(inputWindow, width, height);
-        JamiService.registerVideoCallback(inputId, inputWindow);
-        return inputWindow;
-    }
-
-    public void stopVideo(final String inputId, long inputWindow) {
-        if (inputWindow == 0) {
-            return;
-        }
-        JamiService.unregisterVideoCallback(inputId, inputWindow);
-        JamiService.releaseNativeWindow(inputWindow);
-    }
-
-    public abstract void setDeviceOrientation(int rotation);
-
-    protected abstract List<String> getVideoDevices();
-
-    private Observable<String> logs = null;
-    private Emitter<String> logEmitter = null;
-
-    synchronized public boolean isLogging() {
-        return logs != null;
-    }
-
-    synchronized public Observable<String> startLogs() {
-        if (logs == null) {
-            logs = Observable.create((ObservableOnSubscribe<String>) emitter -> {
-                Log.w(TAG, "ObservableOnSubscribe JamiService.monitor(true)");
-                logEmitter = emitter;
-                JamiService.monitor(true);
-                emitter.setCancellable(() -> {
-                    Log.w(TAG, "ObservableOnSubscribe CANCEL JamiService.monitor(false)");
-                    synchronized (HardwareService.this) {
-                        JamiService.monitor(false);
-                        logEmitter = null;
-                        logs = null;
-                    }
-                });
-            })
-                    .observeOn(Schedulers.io())
-                    .scan(new StringBuffer(1024), (sb, message) -> sb.append(message).append('\n'))
-                    .throttleLatest(500, TimeUnit.MILLISECONDS)
-                    .map(StringBuffer::toString)
-                    .replay(1)
-                    .autoConnect();
-        }
-        return logs;
-    }
-
-    synchronized public void stopLogs() {
-        if (logEmitter != null) {
-            Log.w(TAG, "stopLogs JamiService.monitor(false)");
-            JamiService.monitor(false);
-            logEmitter.onComplete();
-            logEmitter = null;
-            logs = null;
-        }
-    }
-
-    void logMessage(String message) {
-        if (logEmitter != null && !StringUtils.isEmpty(message)) {
-            logEmitter.onNext(message);
-        }
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt
new file mode 100644
index 000000000..91239b566
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt
@@ -0,0 +1,226 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.services
+
+import io.reactivex.rxjava3.core.*
+import io.reactivex.rxjava3.core.ObservableOnSubscribe
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.utils.StringUtils.isEmpty
+import net.jami.model.Call.CallStatus
+import net.jami.daemon.IntVect
+import net.jami.daemon.UintVect
+import net.jami.daemon.JamiService
+import net.jami.daemon.StringMap
+import net.jami.model.Conference
+import net.jami.utils.Log
+import net.jami.utils.Tuple
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+import kotlin.jvm.Synchronized
+
+abstract class HardwareService(
+    private val mExecutor: ScheduledExecutorService,
+    val mPreferenceService: PreferencesService,
+    protected val mUiScheduler: Scheduler
+) {
+    class VideoEvent {
+        var start = false
+        var started = false
+        var w = 0
+        var h = 0
+        var rot = 0
+        var callId: String? = null
+    }
+
+    class BluetoothEvent {
+        var connected = false
+    }
+
+    enum class AudioOutput {
+        INTERNAL, SPEAKERS, BLUETOOTH
+    }
+
+    class AudioState {
+        val outputType: AudioOutput
+        val outputName: String?
+
+        constructor(ot: AudioOutput) {
+            outputType = ot
+            outputName = null
+        }
+
+        constructor(ot: AudioOutput, name: String?) {
+            outputType = ot
+            outputName = name
+        }
+    }
+
+    protected val videoEvents: Subject<VideoEvent> = PublishSubject.create()
+    protected val bluetoothEvents: Subject<BluetoothEvent> = PublishSubject.create()
+    protected val audioStateSubject: Subject<AudioState> = BehaviorSubject.createDefault(STATE_INTERNAL)
+    protected val connectivityEvents: Subject<Boolean> = BehaviorSubject.create()
+    fun getVideoEvents(): Observable<VideoEvent> {
+        return videoEvents
+    }
+
+    fun getBluetoothEvents(): Observable<BluetoothEvent> {
+        return bluetoothEvents
+    }
+
+    val audioState: Observable<AudioState>
+        get() = audioStateSubject
+    val connectivityState: Observable<Boolean>
+        get() = connectivityEvents
+
+    abstract fun initVideo(): Completable
+    abstract val isVideoAvailable: Boolean
+    abstract fun updateAudioState(state: CallStatus?, incomingCall: Boolean, isOngoingVideo: Boolean)
+    abstract fun closeAudioState()
+    abstract val isSpeakerphoneOn: Boolean
+
+    abstract fun toggleSpeakerphone(checked: Boolean)
+    abstract fun startRinging()
+    abstract fun stopRinging()
+    abstract fun abandonAudioFocus()
+    abstract fun decodingStarted(id: String, shmPath: String, width: Int, height: Int, isMixer: Boolean)
+    abstract fun decodingStopped(id: String, shmPath: String, isMixer: Boolean)
+    abstract fun getCameraInfo(camId: String, formats: IntVect, sizes: UintVect, rates: UintVect)
+    abstract fun setParameters(camId: String, format: Int, width: Int, height: Int, rate: Int)
+    abstract fun startCapture(camId: String?)
+    abstract fun startScreenShare(mediaProjection: Any?): Boolean
+    abstract fun hasMicrophone(): Boolean
+    abstract fun stopCapture()
+    abstract fun endCapture()
+    abstract fun stopScreenShare()
+    abstract fun requestKeyFrame()
+    abstract fun setBitrate(device: String?, bitrate: Int)
+    abstract fun addVideoSurface(id: String?, holder: Any?)
+    abstract fun updateVideoSurfaceId(currentId: String?, newId: String?)
+    abstract fun removeVideoSurface(id: String?)
+    abstract fun addPreviewVideoSurface(holder: Any?, conference: Conference?)
+    abstract fun updatePreviewVideoSurface(conference: Conference?)
+    abstract fun removePreviewVideoSurface()
+    abstract fun switchInput(id: String?, setDefaultCamera: Boolean)
+    abstract fun setPreviewSettings()
+    abstract fun hasCamera(): Boolean
+    abstract val cameraCount: Int
+    abstract val maxResolutions: Observable<Tuple<Int, Int>>
+    abstract val isPreviewFromFrontCamera: Boolean
+    abstract fun shouldPlaySpeaker(): Boolean
+    abstract fun unregisterCameraDetectionCallback()
+    abstract fun startMediaHandler(mediaHandlerId: String?)
+    abstract fun stopMediaHandler()
+    fun connectivityChanged(isConnected: Boolean) {
+        Log.i(TAG, "connectivityChange() $isConnected")
+        connectivityEvents.onNext(isConnected)
+        mExecutor.execute { JamiService.connectivityChanged() }
+    }
+
+    protected fun switchInput(id: String?, uri: String) {
+        Log.i(TAG, "switchInput() $uri")
+        mExecutor.execute { JamiService.switchInput(id, uri) }
+    }
+
+    fun setPreviewSettings(cameraMaps: Map<String?, StringMap?>) {
+        mExecutor.execute {
+            Log.i(TAG, "applySettings() thread running...")
+            for ((key, value) in cameraMaps) {
+                JamiService.applySettings(key, value)
+            }
+        }
+    }
+
+    fun startVideo(inputId: String?, surface: Any?, width: Int, height: Int): Long {
+        val inputWindow = JamiService.acquireNativeWindow(surface)
+        if (inputWindow == 0L) {
+            return inputWindow
+        }
+        JamiService.setNativeWindowGeometry(inputWindow, width, height)
+        JamiService.registerVideoCallback(inputId, inputWindow)
+        return inputWindow
+    }
+
+    fun stopVideo(inputId: String?, inputWindow: Long) {
+        if (inputWindow == 0L) {
+            return
+        }
+        JamiService.unregisterVideoCallback(inputId, inputWindow)
+        JamiService.releaseNativeWindow(inputWindow)
+    }
+
+    abstract fun setDeviceOrientation(rotation: Int)
+    protected abstract val videoDevices: List<String?>?
+    private var logs: Observable<String>? = null
+    private var logEmitter: Emitter<String>? = null
+
+    @get:Synchronized
+    val isLogging: Boolean
+        get() = logs != null
+
+    @Synchronized
+    fun startLogs(): Observable<String> {
+        return logs ?: Observable.create(ObservableOnSubscribe { emitter: ObservableEmitter<String> ->
+            Log.w(TAG, "ObservableOnSubscribe JamiService.monitor(true)")
+            logEmitter = emitter
+            JamiService.monitor(true)
+            emitter.setCancellable {
+                Log.w(TAG, "ObservableOnSubscribe CANCEL JamiService.monitor(false)")
+                synchronized(this@HardwareService) {
+                    JamiService.monitor(false)
+                    logEmitter = null
+                    logs = null
+                }
+            }
+        } as ObservableOnSubscribe<String>)
+            .observeOn(Schedulers.io())
+            .scan(StringBuffer(1024), { sb: StringBuffer, message: String? -> sb.append(message).append('\n') })
+            .throttleLatest(500, TimeUnit.MILLISECONDS)
+            .map { obj: StringBuffer -> obj.toString() }
+            .replay(1)
+            .autoConnect()
+            .apply { logs = this }
+    }
+
+    @Synchronized
+    fun stopLogs() {
+        logEmitter?.let { emitter ->
+            Log.w(TAG, "stopLogs JamiService.monitor(false)")
+            JamiService.monitor(false)
+            emitter.onComplete()
+            logEmitter = null
+            logs = null
+        }
+    }
+
+    fun logMessage(message: String) {
+        if (message.isNotEmpty())
+            logEmitter?.onNext(message)
+    }
+
+    companion object {
+        private val TAG = HardwareService::class.java.simpleName
+        val STATE_SPEAKERS = AudioState(AudioOutput.SPEAKERS)
+        val STATE_INTERNAL = AudioState(AudioOutput.INTERNAL)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/LogService.java b/ring-android/libringclient/src/main/java/net/jami/services/LogService.kt
similarity index 65%
rename from ring-android/libringclient/src/main/java/net/jami/services/LogService.java
rename to ring-android/libringclient/src/main/java/net/jami/services/LogService.kt
index fd8166763..f807ecefa 100644
--- a/ring-android/libringclient/src/main/java/net/jami/services/LogService.java
+++ b/ring-android/libringclient/src/main/java/net/jami/services/LogService.kt
@@ -17,23 +17,15 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package net.jami.services;
-
-public interface LogService {
-
-    void e(String tag, String message);
-
-    void d(String tag, String message);
-
-    void w(String tag, String message);
-
-    void i(String tag, String message);
-
-    void e(String tag, String message, Throwable e);
-
-    void d(String tag, String message, Throwable e);
-
-    void w(String tag, String message, Throwable e);
-
-    void i(String tag, String message, Throwable e);
-}
+package net.jami.services
+
+interface LogService {
+    fun e(tag: String, message: String)
+    fun d(tag: String, message: String)
+    fun w(tag: String, message: String)
+    fun i(tag: String, message: String)
+    fun e(tag: String, message: String, e: Throwable)
+    fun d(tag: String, message: String, e: Throwable)
+    fun w(tag: String, message: String, e: Throwable)
+    fun i(tag: String, message: String, e: Throwable)
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java
index 1229695e3..7239c8d08 100644
--- a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java
+++ b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java
@@ -55,13 +55,10 @@ public interface NotificationService {
     void removeTransferNotification(String accountId, Uri conversationUri, String fileId);
     Object getDataTransferNotification(int notificationId);
 
-    void updateNotification(Object notification, int notificationId);
+    //void updateNotification(Object notification, int notificationId);
 
     Object getServiceNotification();
 
-
-
-
     void onConnectionUpdate(Boolean b);
 
     void showLocationNotification(Account first, Contact contact);
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java b/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java
index f5b36bcb6..2062fdf32 100644
--- a/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java
+++ b/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java
@@ -32,11 +32,13 @@ import io.reactivex.rxjava3.subjects.Subject;
 
 public abstract class PreferencesService {
 
-    @Inject
-    AccountService mAccountService;
+    private final AccountService mAccountService;
+    private final DeviceRuntimeService mDeviceService;
 
-    @Inject
-    DeviceRuntimeService mDeviceService;
+    public PreferencesService(AccountService accountService, DeviceRuntimeService deviceService) {
+        mAccountService = accountService;
+        mDeviceService = deviceService;
+    }
 
     private Settings mUserSettings;
     private final Subject<Settings> mSettingsSubject = BehaviorSubject.create();
diff --git a/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java b/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java
index 899995d75..87631ba8b 100644
--- a/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java
+++ b/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java
@@ -25,6 +25,7 @@ import net.jami.model.Account;
 import net.jami.utils.Tuple;
 import ezvcard.VCard;
 import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Observable;
 import io.reactivex.rxjava3.core.Single;
 
 public abstract class VCardService {
@@ -32,7 +33,7 @@ public abstract class VCardService {
     public static final int MAX_SIZE_SIP = 256 * 1024;
     public static final int MAX_SIZE_REQUEST = 16 * 1024;
 
-    public abstract Single<Tuple<String, Object>> loadProfile(Account account);
+    public abstract Observable<Tuple<String, Object>> loadProfile(Account account);
 
     public abstract Maybe<VCard> loadSmallVCard(String accountId, int maxSize);
     public Single<VCard> loadSmallVCardWithDefault(String accountId, int maxSize) {
diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java b/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java
index e71001547..d46793bd7 100644
--- a/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java
@@ -23,7 +23,7 @@ package net.jami.settings;
 import javax.inject.Inject;
 import javax.inject.Named;
 
-import net.jami.facades.ConversationFacade;
+import net.jami.services.ConversationFacade;
 import net.jami.model.Settings;
 import net.jami.mvp.GenericView;
 import net.jami.mvp.RootPresenter;
diff --git a/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java b/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java
index ec1ebb811..91021983c 100644
--- a/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java
+++ b/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java
@@ -23,6 +23,7 @@ package net.jami.share;
 import net.jami.services.AccountService;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 import net.jami.mvp.GenericView;
 import net.jami.mvp.RootPresenter;
@@ -31,11 +32,11 @@ import io.reactivex.rxjava3.core.Scheduler;
 import io.reactivex.rxjava3.schedulers.Schedulers;
 
 public class SharePresenter extends RootPresenter<GenericView<ShareViewModel>> {
-    private final net.jami.services.AccountService mAccountService;
+    private final AccountService mAccountService;
     private final Scheduler mUiScheduler;
 
     @Inject
-    public SharePresenter(AccountService accountService, Scheduler uiScheduler) {
+    public SharePresenter(AccountService accountService, @Named("UiScheduler") Scheduler uiScheduler) {
         mAccountService = accountService;
         mUiScheduler = uiScheduler;
     }
diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.java b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.java
deleted file mode 100644
index 5b6b22f29..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.smartlist;
-
-import net.jami.facades.ConversationFacade;
-import net.jami.model.Account;
-import net.jami.model.Contact;
-import net.jami.model.Uri;
-import net.jami.mvp.RootPresenter;
-import net.jami.services.AccountService;
-import net.jami.services.ContactService;
-import net.jami.utils.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Scheduler;
-import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.reactivex.rxjava3.subjects.BehaviorSubject;
-import io.reactivex.rxjava3.subjects.PublishSubject;
-import io.reactivex.rxjava3.subjects.Subject;
-
-public class SmartListPresenter extends RootPresenter<SmartListView> {
-
-    private static final String TAG = SmartListPresenter.class.getSimpleName();
-
-    private final AccountService mAccountService;
-    private final ContactService mContactService;
-    private final ConversationFacade mConversationFacade;
-
-    private Account mAccount;
-    private final Subject<String> mCurrentQuery = BehaviorSubject.createDefault("");
-    private final Subject<String> mQuery = PublishSubject.create();
-    private final Observable<String> mDebouncedQuery = mQuery.debounce(350, TimeUnit.MILLISECONDS);
-
-    private final Observable<Account> accountSubject;
-
-    private final Scheduler mUiScheduler;
-
-    private final CompositeDisposable mConversationDisposable = new CompositeDisposable();
-    private Disposable mQueryDisposable = null;
-
-    @Inject
-    public SmartListPresenter(AccountService accountService,
-                              ContactService contactService,
-                              ConversationFacade conversationFacade,
-                              @Named("UiScheduler") Scheduler uiScheduler) {
-        mAccountService = accountService;
-        mContactService = contactService;
-        mConversationFacade = conversationFacade;
-        mUiScheduler = uiScheduler;
-
-        accountSubject = mConversationFacade
-                .getCurrentAccountSubject()
-                .doOnNext(a -> mAccount = a);
-    }
-
-    @Override
-    public void bindView(SmartListView view) {
-        super.bindView(view);
-        mCompositeDisposable.clear();
-        mCompositeDisposable.add(mConversationDisposable);
-        loadConversations();
-    }
-
-    public void queryTextChanged(String query) {
-        if (query.isEmpty()) {
-            if (mQueryDisposable != null)  {
-                mQueryDisposable.dispose();
-                mQueryDisposable = null;
-            }
-            mCurrentQuery.onNext(query);
-        } else {
-            if (mQueryDisposable == null)  {
-                mQueryDisposable = mDebouncedQuery.subscribe(mCurrentQuery::onNext);
-            }
-            mQuery.onNext(query);
-        }
-    }
-
-    public void conversationClicked(SmartListViewModel viewModel) {
-        startConversation(viewModel.getAccountId(), viewModel.getUri());
-    }
-
-    public void conversationLongClicked(SmartListViewModel smartListViewModel) {
-        getView().displayConversationDialog(smartListViewModel);
-    }
-
-    public String getAccountID() {
-        return mAccount.getAccountID();
-    }
-
-    public void fabButtonClicked() {
-        getView().displayMenuItem();
-    }
-
-    private void startConversation(String accountId, Uri conversationUri) {
-        Log.w(TAG, "startConversation " + accountId + " " + conversationUri);
-        SmartListView view = getView();
-        if (view != null && conversationUri != null) {
-            view.goToConversation(accountId, conversationUri);
-        }
-    }
-
-    public void startConversation(Uri uri) {
-        getView().goToConversation(mAccount.getAccountID(), uri);
-    }
-
-    public void copyNumber(SmartListViewModel smartListViewModel) {
-        if (smartListViewModel.isSwarm()) {
-            // Copy first contact's URI for a swarm
-            // TODO other modes
-            Contact contact = smartListViewModel.getContacts().get(0);
-            getView().copyNumber(contact.getUri());
-        } else {
-            getView().copyNumber(smartListViewModel.getUri());
-        }
-    }
-
-    public void clearConversation(SmartListViewModel smartListViewModel) {
-        getView().displayClearDialog(smartListViewModel.getUri());
-    }
-
-    public void clearConversation(final Uri uri) {
-        mConversationDisposable.add(mConversationFacade
-                .clearHistory(mAccount.getAccountID(), uri)
-                .subscribeOn(Schedulers.computation()).subscribe());
-    }
-
-    public void removeConversation(SmartListViewModel smartListViewModel) {
-        getView().displayDeleteDialog(smartListViewModel.getUri());
-    }
-
-    public void removeConversation(Uri uri) {
-        mConversationDisposable.add(mConversationFacade
-                .removeConversation(mAccount.getAccountID(), uri)
-                .subscribe());
-    }
-
-    public void banContact(SmartListViewModel smartListViewModel) {
-        mConversationFacade.banConversation(smartListViewModel.getAccountId(), smartListViewModel.getUri());
-    }
-    public void clickQRSearch() {
-        getView().goToQRFragment();
-    }
-
-    void showConversations(Observable<List<Observable<SmartListViewModel>>> conversations) {
-        mConversationDisposable.clear();
-        getView().setLoading(true);
-
-        mConversationDisposable.add(conversations
-                .switchMap(viewModels -> viewModels.isEmpty() ? SmartListViewModel.EMPTY_RESULTS
-                        : Observable.combineLatest(viewModels, obs -> {
-                            List<SmartListViewModel> vms = new ArrayList<>(obs.length);
-                            for (Object ob : obs)
-                                vms.add((SmartListViewModel) ob);
-                            return vms;
-                        }))
-                .throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler)
-                .observeOn(mUiScheduler)
-                .subscribe(viewModels -> {
-                    final SmartListView view = getView();
-                    view.setLoading(false);
-                    if (viewModels.isEmpty()) {
-                        view.hideList();
-                        view.displayNoConversationMessage();
-                        return;
-                    }
-                    view.hideNoConversationMessage();
-                    view.updateList(viewModels, mConversationDisposable);
-                }, e -> Log.w(TAG, "showConversations error ", e)));
-    }
-
-    private void loadConversations() {
-        showConversations(mConversationFacade.getFullList(accountSubject, mCurrentQuery, true));
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt
new file mode 100644
index 000000000..5787d744f
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.smartlist
+
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Scheduler
+import io.reactivex.rxjava3.disposables.Disposable
+import io.reactivex.rxjava3.schedulers.Schedulers
+import io.reactivex.rxjava3.subjects.BehaviorSubject
+import io.reactivex.rxjava3.subjects.PublishSubject
+import io.reactivex.rxjava3.subjects.Subject
+import net.jami.model.Account
+import net.jami.model.Uri
+import net.jami.mvp.RootPresenter
+import net.jami.services.ConversationFacade
+import net.jami.utils.Log
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+
+class SmartListPresenter @Inject constructor(
+    private val mConversationFacade: ConversationFacade,
+    @param:Named("UiScheduler") private val mUiScheduler: Scheduler
+) : RootPresenter<SmartListView>() {
+    //private final CompositeDisposable mConversationDisposable = new CompositeDisposable();
+    private var mQueryDisposable: Disposable? = null
+    private val mCurrentQuery: Subject<String> = BehaviorSubject.createDefault("")
+    private val mQuery: Subject<String> = PublishSubject.create()
+    private val mDebouncedQuery = mQuery.debounce(350, TimeUnit.MILLISECONDS)
+    private val accountSubject: Observable<Account> = mConversationFacade
+        .currentAccountSubject
+        .doOnNext { a: Account -> mAccount = a }
+    private var mAccount: Account? = null
+
+    override fun bindView(view: SmartListView) {
+        super.bindView(view)
+        //mCompositeDisposable.clear();
+        //mCompositeDisposable.add(mConversationDisposable);
+        showConversations(mConversationFacade.getFullList(accountSubject, mCurrentQuery, true))
+    }
+
+    fun queryTextChanged(query: String) {
+        if (query.isEmpty()) {
+            if (mQueryDisposable != null) {
+                mQueryDisposable!!.dispose()
+                mQueryDisposable = null
+            }
+            mCurrentQuery.onNext(query)
+        } else {
+            if (mQueryDisposable == null) {
+                mQueryDisposable = mDebouncedQuery.subscribe { t: String -> mCurrentQuery.onNext(t) }
+            }
+            mQuery.onNext(query)
+        }
+    }
+
+    fun conversationClicked(viewModel: SmartListViewModel) {
+        startConversation(viewModel.accountId, viewModel.uri)
+    }
+
+    fun conversationLongClicked(smartListViewModel: SmartListViewModel?) {
+        view!!.displayConversationDialog(smartListViewModel)
+    }
+
+    fun fabButtonClicked() {
+        view!!.displayMenuItem()
+    }
+
+    private fun startConversation(accountId: String, conversationUri: Uri?) {
+        Log.w(TAG, "startConversation $accountId $conversationUri")
+        val view = view
+        if (view != null && conversationUri != null) {
+            view.goToConversation(accountId, conversationUri)
+        }
+    }
+
+    fun startConversation(uri: Uri?) {
+        view!!.goToConversation(mAccount!!.accountID, uri)
+    }
+
+    fun copyNumber(smartListViewModel: SmartListViewModel) {
+        if (smartListViewModel.isSwarm) {
+            // Copy first contact's URI for a swarm
+            // TODO other modes
+            val contact = smartListViewModel.contacts[0]
+            view!!.copyNumber(contact.uri)
+        } else {
+            view!!.copyNumber(smartListViewModel.uri)
+        }
+    }
+
+    fun clearConversation(smartListViewModel: SmartListViewModel) {
+        view!!.displayClearDialog(smartListViewModel.uri)
+    }
+
+    fun clearConversation(uri: Uri?) {
+        mCompositeDisposable.add(
+            mConversationFacade
+                .clearHistory(mAccount!!.accountID, uri!!)
+                .subscribeOn(Schedulers.computation()).subscribe()
+        )
+    }
+
+    fun removeConversation(smartListViewModel: SmartListViewModel) {
+        view!!.displayDeleteDialog(smartListViewModel.uri)
+    }
+
+    fun removeConversation(uri: Uri) {
+        mCompositeDisposable.add(
+            mConversationFacade.removeConversation(mAccount!!.accountID, uri)
+                .subscribe())
+    }
+
+    fun banContact(smartListViewModel: SmartListViewModel) {
+        mConversationFacade.banConversation(smartListViewModel.accountId, smartListViewModel.uri)
+    }
+
+    fun clickQRSearch() {
+        view!!.goToQRFragment()
+    }
+
+    private fun showConversations(conversations: Observable<List<Observable<SmartListViewModel>>>) {
+        //mConversationDisposable.clear();
+        view!!.setLoading(true)
+        mCompositeDisposable.add(conversations
+            .switchMap { viewModels: List<Observable<SmartListViewModel>> ->
+                if (viewModels.isEmpty()) SmartListViewModel.EMPTY_RESULTS else Observable.combineLatest<SmartListViewModel, List<SmartListViewModel>>(
+                    viewModels
+                ) { obs: Array<Any> -> //obs.map { it as SmartListViewModel }
+                    val vms: MutableList<SmartListViewModel> = ArrayList(obs.size)
+                    for (ob in obs) vms.add(ob as SmartListViewModel)
+                    vms
+                }.throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler)
+            }
+            .observeOn(mUiScheduler)
+            .subscribe({ viewModels: List<SmartListViewModel> ->
+                val view = view
+                view!!.setLoading(false)
+                if (viewModels.isEmpty()) {
+                    view.hideList()
+                    view.displayNoConversationMessage()
+                    return@subscribe
+                }
+                view.hideNoConversationMessage()
+                view.updateList(viewModels, mCompositeDisposable)
+            }) { e: Throwable -> Log.w(TAG, "showConversations error ", e) })
+    }
+
+    companion object {
+        private val TAG = SmartListPresenter::class.java.simpleName
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.java b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.java
deleted file mode 100644
index 29f4140ca..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.smartlist;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-import net.jami.model.Contact;
-import net.jami.model.Conversation;
-import net.jami.model.Interaction;
-import net.jami.model.Uri;
-
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-
-public class SmartListViewModel
-{
-    public static final Observable<SmartListViewModel> TITLE_CONVERSATIONS = Observable.just(new SmartListViewModel(Title.Conversations));
-    public static final Observable<SmartListViewModel> TITLE_PUBLIC_DIR = Observable.just(new SmartListViewModel(Title.PublicDirectory));
-    public static final Single<List<Observable<SmartListViewModel>>> EMPTY_LIST = Single.just(Collections.emptyList());
-    public static final Observable<List<SmartListViewModel>> EMPTY_RESULTS = Observable.just(Collections.emptyList());
-
-    private final String accountId;
-    private final Uri uri;
-    private final List<Contact> contact;
-    private final String uuid;
-    private final String contactName;
-    private final boolean hasUnreadTextMessage;
-    private boolean hasOngoingCall;
-
-    private final boolean showPresence;
-    private boolean isOnline = false;
-    private boolean isChecked = false;
-    private Observable<Boolean> isSelected = null;
-    private final Interaction lastEvent;
-    private boolean isSwarm = false;
-
-    public enum Title {
-        None,
-        Conversations,
-        PublicDirectory
-    }
-    private final Title title;
-
-    public SmartListViewModel(String accountId, Contact contact, Interaction lastEvent) {
-        this.accountId = accountId;
-        this.contact = Collections.singletonList(contact);
-        this.uri = contact.getUri();
-        uuid = uri.getRawUriString();
-        this.contactName = contact.getDisplayName();
-        hasUnreadTextMessage = (lastEvent != null) && !lastEvent.isRead();
-        this.hasOngoingCall = false;
-        this.lastEvent = lastEvent;
-        showPresence = true;
-        isOnline = contact.isOnline();
-        title = Title.None;
-    }
-    public SmartListViewModel(String accountId, Contact contact, String id, Interaction lastEvent) {
-        this.accountId = accountId;
-        this.contact = Collections.singletonList(contact);
-        uri = contact.getUri();
-        this.uuid = id;
-        this.contactName = contact.getDisplayName();
-        hasUnreadTextMessage = (lastEvent != null) && !lastEvent.isRead();
-        this.hasOngoingCall = false;
-        this.lastEvent = lastEvent;
-        showPresence = true;
-        isOnline = contact.isOnline();
-        title = Title.None;
-    }
-    public SmartListViewModel(Conversation conversation, List<Contact> contacts, boolean presence) {
-        this.accountId = conversation.getAccountId();
-        this.contact = contacts;
-        uri = conversation.getUri();
-        this.uuid = uri.getRawUriString();
-        this.contactName = conversation.getTitle();
-        Interaction lastEvent = conversation.getLastEvent();
-        hasUnreadTextMessage = (lastEvent != null) && !lastEvent.isRead();
-        this.hasOngoingCall = false;
-        this.lastEvent = lastEvent;
-        isSelected = conversation.getVisible();
-        isSwarm = conversation.isSwarm();
-        for (Contact contact : contacts) {
-            if (contact.isUser())
-                continue;
-            if (contact.isOnline()) {
-                isOnline = true;
-                break;
-            }
-        }
-        showPresence = presence;
-        title = Title.None;
-    }
-    public SmartListViewModel(Conversation conversation, boolean presence) {
-        this(conversation, conversation.getContacts(), presence);
-    }
-
-    private SmartListViewModel(Title title) {
-        contactName = null;
-        this.accountId = null;
-        this.contact = null;
-        this.uuid = null;
-        uri = null;
-        hasUnreadTextMessage = false;
-        lastEvent = null;
-        showPresence = false;
-        this.title = title;
-    }
-
-    public Uri getUri() {
-        return uri;
-    }
-
-    public boolean isSwarm() {
-        return isSwarm;
-    }
-
-    public List<Contact> getContacts() {
-        return contact;
-    }
-
-    public String getContactName() {
-        return contactName;
-    }
-
-    public long getLastInteractionTime() {
-        return (lastEvent == null) ? 0 : lastEvent.getTimestamp();
-    }
-
-    public boolean hasUnreadTextMessage() {
-        return hasUnreadTextMessage;
-    }
-
-    public boolean hasOngoingCall() {
-        return hasOngoingCall;
-    }
-
-    public String getUuid() {
-        return uuid;
-    }
-
-    /*public boolean isOnline() {
-        return isOnline;
-    }
-
-    public void setOnline(boolean online) {
-        if (showPresence)
-            isOnline = online;
-    }*/
-
-    public boolean showPresence() {
-        return showPresence;
-    }
-
-    public boolean isOnline() {
-        return isOnline;
-    }
-
-    public boolean isChecked() { return isChecked; }
-
-    public void setChecked(boolean checked) {
-        isChecked = checked;
-    }
-
-    public Observable<Boolean> getSelected() { return isSelected; }
-
-    public void setHasOngoingCall(boolean hasOngoingCall) {
-        this.hasOngoingCall = hasOngoingCall;
-    }
-
-    public Interaction getLastEvent() {
-        return lastEvent;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof SmartListViewModel))
-            return false;
-        SmartListViewModel other = (SmartListViewModel) o;
-        return other.getHeaderTitle() == getHeaderTitle()
-                && (getHeaderTitle() != Title.None
-                || (contact == other.contact
-                && Objects.equals(contactName, other.contactName)
-                && isOnline == other.isOnline
-                && lastEvent == other.lastEvent
-                && hasOngoingCall == other.hasOngoingCall
-                && hasUnreadTextMessage == other.hasUnreadTextMessage));
-    }
-
-    public String getAccountId() {
-        return accountId;
-    }
-
-    public Title getHeaderTitle() {
-        return title;
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt
new file mode 100644
index 000000000..ded69f43b
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt
@@ -0,0 +1,171 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.smartlist
+
+import io.reactivex.rxjava3.core.Observable
+import io.reactivex.rxjava3.core.Single
+import net.jami.model.Contact
+import net.jami.model.Conversation
+import net.jami.model.Interaction
+import net.jami.model.Uri
+
+class SmartListViewModel {
+    val accountId: String
+    val uri: Uri
+    val contacts: List<Contact>
+    val uuid: String?
+    val contactName: String?
+    private val hasUnreadTextMessage: Boolean
+    private var hasOngoingCall = false
+    private val showPresence: Boolean
+    var isOnline = false
+        private set
+    var isChecked = false
+    var selected: Observable<Boolean>? = null
+        private set
+    val lastEvent: Interaction?
+
+    enum class Title {
+        None, Conversations, PublicDirectory
+    }
+
+    val headerTitle: Title
+
+    constructor(accountId: String, contact: Contact, lastEvent: Interaction?) {
+        this.accountId = accountId
+        contacts = listOf(contact)
+        uri = contact.uri
+        uuid = uri.rawUriString
+        contactName = contact.displayName
+        hasUnreadTextMessage = lastEvent != null && !lastEvent.isRead
+        hasOngoingCall = false
+        this.lastEvent = lastEvent
+        showPresence = true
+        isOnline = contact.isOnline
+        headerTitle = Title.None
+    }
+
+    constructor(accountId: String, contact: Contact, id: String?, lastEvent: Interaction?) {
+        this.accountId = accountId
+        contacts = listOf(contact)
+        uri = contact.uri
+        uuid = id
+        contactName = contact.displayName
+        hasUnreadTextMessage = lastEvent != null && !lastEvent.isRead
+        hasOngoingCall = false
+        this.lastEvent = lastEvent
+        showPresence = true
+        isOnline = contact.isOnline
+        headerTitle = Title.None
+    }
+
+    constructor(conversation: Conversation, contacts: List<Contact>, presence: Boolean) {
+        accountId = conversation.accountId
+        this.contacts = contacts
+        uri = conversation.uri
+        uuid = uri.rawUriString
+        contactName = conversation.title
+        val lastEvent = conversation.lastEvent
+        hasUnreadTextMessage = lastEvent != null && !lastEvent.isRead
+        hasOngoingCall = false
+        this.lastEvent = lastEvent
+        selected = conversation.getVisible()
+        for (contact in contacts) {
+            if (contact.isUser) continue
+            if (contact.isOnline) {
+                isOnline = true
+                break
+            }
+        }
+        showPresence = presence
+        headerTitle = Title.None
+    }
+
+    constructor(conversation: Conversation, presence: Boolean) : this(conversation, conversation.contacts, presence) {}
+
+    private constructor(title: Title) {
+        contactName = null
+        accountId = ""
+        contacts = emptyList()
+        uuid = null
+        uri = Uri()
+        hasUnreadTextMessage = false
+        lastEvent = null
+        showPresence = false
+        headerTitle = title
+    }
+
+    val isSwarm: Boolean
+        get() = uri.isSwarm
+
+    /**
+     * Used to get contact for one to one or legacy conversations
+     */
+    fun getContact(): Contact? {
+        if (contacts!!.size == 1) return contacts[0]
+        for (c in contacts) {
+            if (!c.isUser) return c
+        }
+        return null
+    }
+
+    val lastInteractionTime: Long
+        get() = lastEvent?.timestamp ?: 0
+
+    fun hasUnreadTextMessage(): Boolean {
+        return hasUnreadTextMessage
+    }
+
+    fun hasOngoingCall(): Boolean {
+        return hasOngoingCall
+    }
+
+    /*public boolean isOnline() {
+        return isOnline;
+    }
+
+    public void setOnline(boolean online) {
+        if (showPresence)
+            isOnline = online;
+    }*/
+    fun showPresence(): Boolean {
+        return showPresence
+    }
+
+    fun setHasOngoingCall(hasOngoingCall: Boolean) {
+        this.hasOngoingCall = hasOngoingCall
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (other !is SmartListViewModel) return false
+        return (other.headerTitle == headerTitle
+                && (headerTitle != Title.None
+                || (contacts === other.contacts && contactName == other.contactName
+                && isOnline == other.isOnline && lastEvent === other.lastEvent && hasOngoingCall == other.hasOngoingCall && hasUnreadTextMessage == other.hasUnreadTextMessage)))
+    }
+
+    companion object {
+        val TITLE_CONVERSATIONS: Observable<SmartListViewModel> = Observable.just(SmartListViewModel(Title.Conversations))
+        val TITLE_PUBLIC_DIR: Observable<SmartListViewModel> = Observable.just(SmartListViewModel(Title.PublicDirectory))
+        val EMPTY_LIST: Single<List<Observable<SmartListViewModel>>> = Single.just(emptyList())
+        val EMPTY_RESULTS: Observable<List<SmartListViewModel>> = Observable.just(emptyList())
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.kt
similarity index 50%
rename from ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.java
rename to ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.kt
index c985305e2..8e028d39e 100644
--- a/ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/FileUtils.kt
@@ -16,56 +16,56 @@
  *  You should have received a copy of the GNU General Public License
  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package net.jami.utils;
+package net.jami.utils
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*
+import kotlin.Throws
 
-public class FileUtils {
-    private static final String TAG = FileUtils.class.getSimpleName();
+object FileUtils {
+    private val TAG = FileUtils::class.simpleName!!
 
-    public static void copyFile(InputStream in, OutputStream out) throws IOException {
+    @Throws(IOException::class)
+    fun copyFile(input: InputStream, out: OutputStream) {
         // Buffer size based on https://stackoverflow.com/questions/10143731/android-optimal-buffer-size
-        byte[] buffer = new byte[64 * 1024];
-        int read;
-        while ((read = in.read(buffer)) != -1) {
-            out.write(buffer, 0, read);
+        val buffer = ByteArray(64 * 1024)
+        var read: Int
+        while (input.read(buffer).also { read = it } != -1) {
+            out.write(buffer, 0, read)
         }
     }
 
-    public static boolean copyFile(File src, File dest) {
-        try (InputStream inputStream = new FileInputStream(src);
-             FileOutputStream outputStream = new FileOutputStream(dest)) {
-            copyFile(inputStream, outputStream);
-        } catch (IOException e) {
-            Log.w(TAG, "Can't copy file", e);
-            return false;
+    fun copyFile(src: File, dest: File): Boolean {
+        try {
+            FileInputStream(src).use { inputStream ->
+                FileOutputStream(dest).use { outputStream ->
+                    copyFile(inputStream, outputStream)
+                }
+            }
+        } catch (e: IOException) {
+            Log.w(TAG, "Can't copy file", e)
+            return false
         }
-        return true;
+        return true
     }
 
-    public static boolean moveFile(File file, File dest) {
+    @JvmStatic
+    fun moveFile(file: File, dest: File): Boolean {
         if (!file.exists() || !file.canRead()) {
-            Log.d(TAG, "moveFile: file is not accessible " + file.exists() + " " + file.canRead());
-            return false;
+            Log.d(TAG, "moveFile: file is not accessible " + file.exists() + " " + file.canRead())
+            return false
         }
-        if (file.equals(dest))
-            return true;
+        if (file == dest) return true
         if (!file.renameTo(dest)) {
-            Log.w(TAG, "moveFile: can't rename file, trying copy+delete to " + dest);
+            Log.w(TAG, "moveFile: can't rename file, trying copy+delete to $dest")
             if (!copyFile(file, dest)) {
-                Log.w(TAG, "moveFile: can't copy file to " + dest);
-                return false;
+                Log.w(TAG, "moveFile: can't copy file to $dest")
+                return false
             }
             if (!file.delete()) {
-                Log.w(TAG, "moveFile: can't delete old file from " + file);
+                Log.w(TAG, "moveFile: can't delete old file from $file")
             }
         }
-        Log.d(TAG, "moveFile: moved " + file + " to " + dest);
-        return true;
+        Log.d(TAG, "moveFile: moved $file to $dest")
+        return true
     }
-}
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.java
deleted file mode 100644
index 7cf554977..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.com>
- *
- * 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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package net.jami.utils;
-
-import java.math.BigInteger;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashSet;
-import java.util.Set;
-
-public class HashUtils {
-
-    private static final String TAG = HashUtils.class.getSimpleName();
-
-    private HashUtils() {
-    }
-
-    public static String md5(String s) {
-        return hash(s, "MD5");
-    }
-
-    public static String sha1(String s) {
-        return hash(s, "SHA-1");
-    }
-
-    private static String hash(final String s, final String algo) {
-        String result = null;
-        try {
-            MessageDigest messageDigest = MessageDigest.getInstance(algo);
-            messageDigest.update(s.getBytes(), 0, s.length());
-            result = new BigInteger(1, messageDigest.digest()).toString(16);
-        } catch (NoSuchAlgorithmException e) {
-            Log.e(TAG, "Not able to find MD5 algorithm", e);
-        }
-        return result;
-    }
-
-    @SafeVarargs
-    public static <T> Set<T> asSet(T... items) {
-        HashSet<T> s = new HashSet<>(items.length);
-        for (T t : items)
-            s.add(t);
-        return s;
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.kt
similarity index 51%
rename from ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.java
rename to ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.kt
index fc4cf8905..c2a2da1d6 100644
--- a/ring-android/app/src/main/java/cx/ring/utils/DeviceUtils.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/HashUtils.kt
@@ -17,34 +17,31 @@
  * along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
+package net.jami.utils
 
-package cx.ring.utils;
+import java.math.BigInteger
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
 
-import android.app.UiModeManager;
-import android.content.Context;
-import android.content.res.Configuration;
-
-import androidx.annotation.NonNull;
-
-import net.jami.utils.Log;
-
-import cx.ring.R;
-
-import static android.content.Context.UI_MODE_SERVICE;
-
-public class DeviceUtils {
-
-    private static final String TAG = DeviceUtils.class.getSimpleName();
-
-    private DeviceUtils() {
+object HashUtils {
+    private val TAG = HashUtils::class.java.simpleName
+    fun md5(s: String): String? {
+        return hash(s, "MD5")
     }
 
-    public static boolean isTv(@NonNull Context context) {
-        UiModeManager uiModeManager = (UiModeManager) context.getSystemService(UI_MODE_SERVICE);
-        return (uiModeManager != null && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION);
+    fun sha1(s: String): String? {
+        return hash(s, "SHA-1")
     }
 
-    public static boolean isTablet(@NonNull Context context) {
-        return context.getResources().getBoolean(R.bool.isTablet);
+    private fun hash(s: String, algo: String): String? {
+        var result: String? = null
+        try {
+            val messageDigest = MessageDigest.getInstance(algo)
+            messageDigest.update(s.toByteArray(), 0, s.length)
+            result = BigInteger(1, messageDigest.digest()).toString(16)
+        } catch (e: NoSuchAlgorithmException) {
+            Log.e(TAG, "Not able to find MD5 algorithm", e)
+        }
+        return result
     }
-}
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/Log.java b/ring-android/libringclient/src/main/java/net/jami/utils/Log.java
index 0267aee41..e075cb145 100644
--- a/ring-android/libringclient/src/main/java/net/jami/utils/Log.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/Log.java
@@ -22,11 +22,9 @@ package net.jami.utils;
 import net.jami.services.LogService;
 
 public class Log {
+    private static LogService mLogService;
 
-
-    private static net.jami.services.LogService mLogService;
-
-    public static void injectLogService (LogService service) {
+    public static void injectLogService(LogService service) {
         mLogService = service;
     }
 
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java b/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java
index 0d2593597..3eeb57eda 100644
--- a/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java
@@ -26,12 +26,12 @@ import java.util.TimerTask;
 
 public class NameLookupInputHandler {
     private static final int WAIT_DELAY = 350;
-    private final WeakReference<net.jami.services.AccountService> mAccountService;
+    private final WeakReference<AccountService> mAccountService;
     private final String mAccountId;
     private final Timer timer = new Timer(true);
     private NameTask lastTask = null;
 
-    public NameLookupInputHandler(net.jami.services.AccountService accountService, String accountId) {
+    public NameLookupInputHandler(AccountService accountService, String accountId) {
         mAccountService = new WeakReference<>(accountService);
         mAccountId = accountId;
     }
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.java b/ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.kt
similarity index 50%
rename from ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.java
rename to ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.kt
index 0c2530379..ca8d87547 100644
--- a/ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/ProfileChunk.kt
@@ -17,33 +17,14 @@
  * along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
+package net.jami.utils
 
-package net.jami.utils;
+import java.lang.StringBuilder
 
-import net.jami.daemon.StringVect;
-
-public class ProfileChunk {
-    public final static String TAG = ProfileChunk.class.getSimpleName();
-
-    private long mNumberOfParts;
-    private long mInsertedParts;
-    private StringVect mParts;
-
-    /**
-     * Constructor
-     *
-     * @param numberOfParts Number of part to complete the Profile
-     */
-    public ProfileChunk(long numberOfParts) {
-        net.jami.utils.Log.d(TAG, "Create ProfileChink of size " + numberOfParts);
-        this.mInsertedParts = 0;
-        this.mNumberOfParts = numberOfParts;
-        this.mParts = new StringVect();
-        this.mParts.reserve(mNumberOfParts);
-        for (int i = 0; i < mNumberOfParts; i++) {
-            this.mParts.add("");
-        }
-    }
+class ProfileChunk(private val numberOfParts: Int) {
+    private var mTotalSize = 0
+    private var mInsertedParts: Int = 0
+    private val mParts: MutableList<String> = ArrayList(numberOfParts)
 
     /**
      * Inserts a profile part in the data structure, at a given position
@@ -51,10 +32,11 @@ public class ProfileChunk {
      * @param part  the part to insert
      * @param index the given position to insert the part
      */
-    public void addPartAtIndex(String part, int index) {
-        mParts.set(index - 1, part);
-        mInsertedParts++;
-        Log.d(TAG, "Inserting part " + part + " at index " + index);
+    fun addPartAtIndex(part: String, index: Int) {
+        mParts[index - 1] = part
+        mTotalSize += part.length
+        mInsertedParts++
+        Log.d(TAG, "Inserting part $part at index $index")
     }
 
     /**
@@ -62,24 +44,33 @@ public class ProfileChunk {
      *
      * @return true if complete, false otherwise
      */
-    public boolean isProfileComplete() {
-        return this.mInsertedParts == this.mNumberOfParts;
-    }
+    val isProfileComplete: Boolean
+        get() = mInsertedParts == numberOfParts
 
     /**
      * Builds the profile based on the gathered parts.
      *
      * @return the complete profile as a String
      */
-    public String getCompleteProfile() {
-        if (this.isProfileComplete()) {
-            StringBuilder stringBuilder = new StringBuilder();
-            for (int i = 0; i < this.mParts.size(); ++i) {
-                stringBuilder.append(this.mParts.get(i));
+    val completeProfile: String?
+        get() = if (isProfileComplete) {
+            val stringBuilder = StringBuilder(mTotalSize)
+            for (part in mParts) {
+                stringBuilder.append(part)
             }
-            return stringBuilder.toString();
+            stringBuilder.toString()
         } else {
-            return null;
+            null
+        }
+
+    companion object {
+        val TAG = ProfileChunk::class.simpleName!!
+    }
+
+    init {
+        Log.d(TAG, "Create ProfileChink of size $numberOfParts")
+        for (i in 0 until numberOfParts) {
+            mParts.add("")
         }
     }
 }
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.java
deleted file mode 100644
index ed7642de3..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
- *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-package net.jami.utils;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-public final class StringUtils {
-
-    static private final Set<Character.UnicodeBlock> EMOJI_BLOCKS = new HashSet<>(Arrays.asList(
-            Character.UnicodeBlock.EMOTICONS,
-            Character.UnicodeBlock.DINGBATS,
-            Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS,
-            Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS,
-            Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_ARROWS,
-            Character.UnicodeBlock.ALCHEMICAL_SYMBOLS,
-            Character.UnicodeBlock.ARROWS,
-            Character.UnicodeBlock.ENCLOSED_ALPHANUMERIC_SUPPLEMENT,
-            Character.UnicodeBlock.TRANSPORT_AND_MAP_SYMBOLS,
-            Character.UnicodeBlock.VARIATION_SELECTORS // Ignore modifier
-    ));
-
-    public static boolean isEmpty(String s) {
-        return s == null || s.isEmpty();
-    }
-
-    public static boolean isEmpty(CharSequence s) {
-        return s == null || s.length() == 0;
-    }
-
-    public static String capitalize(String s) {
-        if (isEmpty(s)) {
-            return "";
-        }
-        char first = s.charAt(0);
-        if (Character.isUpperCase(first)) {
-            return s;
-        } else {
-            return Character.toUpperCase(first) + s.substring(1);
-        }
-    }
-    public static String toPassword(String s){
-        if(isEmpty(s)){
-            return "";
-        }
-        char[] chars = new char[s.length()];
-        Arrays.fill(chars, '*');
-        return new String(chars);
-    }
-
-    public static String toNumber(String s) {
-        if (s == null)
-            return null;
-        return s.replace("(", "")
-                .replace(")", "")
-                .replace("-", "")
-                .replace(" ", "");
-    }
-
-    public static String getFileExtension(String filename) {
-        int dot = filename.lastIndexOf('.');
-        if (dot == -1 || dot == 0)
-            return "";
-        return filename.substring(dot + 1);
-    }
-
-    public static Iterable<Integer> codePoints(final String string) {
-        return () -> new Iterator<Integer>() {
-            int nextIndex = 0;
-            public boolean hasNext() {
-                return nextIndex < string.length();
-            }
-            public Integer next() {
-                int result = string.codePointAt(nextIndex);
-                nextIndex += Character.charCount(result);
-                return result;
-            }
-            public void remove() {
-                throw new UnsupportedOperationException();
-            }
-        };
-    }
-
-    public static boolean isOnlyEmoji(final String message) {
-        if (message == null || message.isEmpty()) {
-            return false;
-        }
-        for (int codePoint : StringUtils.codePoints(message)) {
-            if (Character.isWhitespace(codePoint)) {
-                continue;
-            }
-            // Common Emoji range: https://en.wikipedia.org/wiki/Unicode_block
-            if (codePoint >= 0x1F000 && codePoint < 0x20000) {
-                continue;
-            }
-            Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint);
-            if (!EMOJI_BLOCKS.contains(block)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public static String join(String separator, List<String> values) {
-        if (values.isEmpty()) return "";//need at least one element
-        if (values.size() == 1) return values.get(0);
-        //all string operations use a new array, so minimize all calls possible
-        char[] sep = separator.toCharArray();
-
-        // determine final size and normalize nulls
-        int totalSize = (values.size() - 1) * sep.length;// separator size
-        for (int i = 0; i < values.size(); i++) {
-            totalSize += values.get(i).length();
-        }
-
-        //exact size; no bounds checks or resizes
-        char[] joined = new char[totalSize];
-        int pos = 0;
-        //note, we are iterating all the elements except the last one
-        for (int i = 0, end = values.size()-1; i < end; i++) {
-            System.arraycopy(values.get(i).toCharArray(), 0,
-                    joined, pos, values.get(i).length());
-            pos += values.get(i).length();
-            System.arraycopy(sep, 0, joined, pos, sep.length);
-            pos += sep.length;
-        }
-        //now, add the last element;
-        //this is why we checked values.length == 0 off the hop
-        System.arraycopy(values.get(values.size()-1).toCharArray(), 0,
-                joined, pos, values.get(values.size()-1).length());
-
-        return new String(joined);
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.kt b/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.kt
new file mode 100644
index 000000000..45d71562b
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/StringUtils.kt
@@ -0,0 +1,150 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
+ *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.utils
+
+import java.util.*
+
+object StringUtils {
+    private val EMOJI_BLOCKS: Set<Character.UnicodeBlock> = HashSet(listOf(
+        Character.UnicodeBlock.EMOTICONS,
+        Character.UnicodeBlock.DINGBATS,
+        Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS,
+        Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS,
+        Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_ARROWS,
+        Character.UnicodeBlock.ALCHEMICAL_SYMBOLS,
+        Character.UnicodeBlock.ARROWS,
+        Character.UnicodeBlock.ENCLOSED_ALPHANUMERIC_SUPPLEMENT,
+        Character.UnicodeBlock.TRANSPORT_AND_MAP_SYMBOLS,
+        Character.UnicodeBlock.VARIATION_SELECTORS // Ignore modifier
+    ))
+
+    @JvmStatic
+    fun isEmpty(s: String?): Boolean {
+        return s == null || s.isEmpty()
+    }
+
+    @JvmStatic
+    fun isEmpty(s: CharSequence?): Boolean {
+        return s == null || s.isEmpty()
+    }
+
+    fun capitalize(s: String): String {
+        if (isEmpty(s)) {
+            return ""
+        }
+        val first = s[0]
+        return if (Character.isUpperCase(first)) {
+            s
+        } else {
+            Character.toUpperCase(first).toString() + s.substring(1)
+        }
+    }
+
+    @JvmStatic
+    fun toPassword(s: String): String {
+        if (isEmpty(s)) {
+            return ""
+        }
+        val chars = CharArray(s.length)
+        Arrays.fill(chars, '*')
+        return String(chars)
+    }
+
+    @JvmStatic
+    fun toNumber(s: String?): String? {
+        return s?.replace("(", "")?.replace(")", "")?.replace("-", "")?.replace(" ", "")
+    }
+
+    @JvmStatic
+    fun getFileExtension(filename: String): String {
+        val dot = filename.lastIndexOf('.')
+        return if (dot == -1 || dot == 0) "" else filename.substring(dot + 1)
+    }
+
+    private fun codePoints(string: String): Iterable<Int> {
+        return Iterable {
+            object : Iterator<Int> {
+                var nextIndex = 0
+                override fun hasNext(): Boolean {
+                    return nextIndex < string.length
+                }
+
+                override fun next(): Int {
+                    val result = string.codePointAt(nextIndex)
+                    nextIndex += Character.charCount(result)
+                    return result
+                }
+            }
+        }
+    }
+
+    @JvmStatic
+    fun isOnlyEmoji(message: String?): Boolean {
+        if (message == null || message.isEmpty()) {
+            return false
+        }
+        for (codePoint in codePoints(message)) {
+            if (Character.isWhitespace(codePoint)) {
+                continue
+            }
+            // Common Emoji range: https://en.wikipedia.org/wiki/Unicode_block
+            if (codePoint in 0x1F000..0x1ffff) {
+                continue
+            }
+            val block = Character.UnicodeBlock.of(codePoint)
+            if (!EMOJI_BLOCKS.contains(block)) {
+                return false
+            }
+        }
+        return true
+    }
+
+    fun join(separator: String, values: List<String>): String {
+        if (values.isEmpty()) return "" //need at least one element
+        if (values.size == 1) return values[0]
+        //all string operations use a new array, so minimize all calls possible
+        val sep = separator.toCharArray()
+
+        // determine final size and normalize nulls
+        var totalSize = (values.size - 1) * sep.size // separator size
+        for (i in values.indices) {
+            totalSize += values[i].length
+        }
+
+        //exact size; no bounds checks or resizes
+        val joined = CharArray(totalSize)
+        var pos = 0
+        //note, we are iterating all the elements except the last one
+        var i = 0
+        val end = values.size - 1
+        while (i < end) {
+            System.arraycopy(values[i].toCharArray(), 0, joined, pos, values[i].length)
+            pos += values[i].length
+            System.arraycopy(sep, 0, joined, pos, sep.size)
+            pos += sep.size
+            i++
+        }
+        //now, add the last element;
+        //this is why we checked values.length == 0 off the hop
+        System.arraycopy(values[values.size - 1].toCharArray(), 0, joined, pos, values[values.size - 1].length)
+        return String(joined)
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.java b/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.java
deleted file mode 100644
index f4cf2116f..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package net.jami.utils;
-
-import net.jami.daemon.MessageVect;
-import net.jami.daemon.StringMap;
-import net.jami.daemon.StringVect;
-import net.jami.daemon.VectMap;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import net.jami.daemon.Message;
-
-public class SwigNativeConverter {
-
-    public static net.jami.daemon.VectMap toSwig(List<Map<String, String>> creds) {
-        net.jami.daemon.VectMap toReturn = new VectMap();
-        toReturn.reserve(creds.size());
-        for (Map<String, String> aTodecode : creds) {
-            toReturn.add(StringMap.toSwig(aTodecode));
-        }
-        return toReturn;
-    }
-
-    public static ArrayList<String> toJava(StringVect vector) {
-        return new ArrayList<>(vector);
-    }
-
-    public static ArrayList<Message> toJava(MessageVect vector) {
-        int size = vector.size();
-        ArrayList<Message> toReturn = new ArrayList<>(size);
-        for (int i = 0; i < size; i++)
-            toReturn.add(vector.get(i));
-        return toReturn;
-    }
-
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.kt b/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.kt
new file mode 100644
index 000000000..486d9ed93
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/SwigNativeConverter.kt
@@ -0,0 +1,45 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.utils
+
+import net.jami.daemon.*
+import java.util.ArrayList
+
+object SwigNativeConverter {
+    fun toSwig(creds: List<Map<String, String>>): VectMap {
+        val toReturn = VectMap()
+        toReturn.reserve(creds.size.toLong())
+        for (aTodecode in creds) {
+            toReturn.add(StringMap.toSwig(aTodecode))
+        }
+        return toReturn
+    }
+
+    fun toJava(vector: StringVect): ArrayList<String> {
+        return ArrayList(vector)
+    }
+
+    fun toJava(vector: MessageVect): ArrayList<Message> {
+        val size = vector.size
+        val toReturn = ArrayList<Message>(size)
+        for (i in 0 until size) toReturn.add(vector[i])
+        return toReturn
+    }
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/Tuple.java b/ring-android/libringclient/src/main/java/net/jami/utils/Tuple.kt
similarity index 53%
rename from ring-android/libringclient/src/main/java/net/jami/utils/Tuple.java
rename to ring-android/libringclient/src/main/java/net/jami/utils/Tuple.kt
index d7c26c089..d60d4c6a0 100644
--- a/ring-android/libringclient/src/main/java/net/jami/utils/Tuple.java
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/Tuple.kt
@@ -17,45 +17,34 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
-package net.jami.utils;
+package net.jami.utils
 
-public class Tuple<X, Y> {
-    public final X first;
-    public final Y second;
+class Tuple<X, Y>(@JvmField val first: X, @JvmField val second: Y) {
 
-    public Tuple(X first, Y second) {
-        this.first = first;
-        this.second = second;
+    override fun toString(): String {
+        return "($first,$second)"
     }
 
-    @Override
-    public String toString() {
-        return "(" + first + "," + second + ")";
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (other == this) {
-            return true;
+    override fun equals(other: Any?): Boolean {
+        if (other === this) {
+            return true
         }
-
-        if (!(other instanceof Tuple)) {
-            return false;
+        if (other !is Tuple<*, *>) {
+            return false
         }
-
-        Tuple<X, Y> other_ = (Tuple<X, Y>) other;
+        val other_ = other as Tuple<X, Y>
 
         // this may cause NPE if nulls are valid values for first or second.
         // The logic may be improved to handle nulls properly, if needed.
-        return other_.first.equals(this.first) && other_.second.equals(this.second);
+        return other_.first == first && other_.second == second
     }
 
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + ((first == null) ? 0 : first.hashCode());
-        result = prime * result + ((second == null) ? 0 : second.hashCode());
-        return result;
+    override fun hashCode(): Int {
+        val prime = 31
+        var result = 1
+        result = prime * result + (first?.hashCode() ?: 0)
+        result = prime * result + (second?.hashCode() ?: 0)
+        return result
     }
-}
+
+}
\ No newline at end of file
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.java
deleted file mode 100644
index bb9c56335..000000000
--- a/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
- *
- *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
- *
- *  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, write to the Free Software
- *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package net.jami.utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.HashMap;
-
-import ezvcard.Ezvcard;
-import ezvcard.VCard;
-import ezvcard.VCardVersion;
-import ezvcard.io.text.VCardWriter;
-import ezvcard.parameter.ImageType;
-import ezvcard.property.FormattedName;
-import ezvcard.property.Photo;
-import ezvcard.property.RawProperty;
-import ezvcard.property.Uid;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-
-public final class VCardUtils {
-    public static final String TAG = VCardUtils.class.getSimpleName();
-
-    public static final String MIME_PROFILE_VCARD = "x-ring/ring.profile.vcard";
-    public static final String VCARD_KEY_MIME_TYPE = "mimeType";
-    public static final String VCARD_KEY_PART = "part";
-    public static final String VCARD_KEY_OF = "of";
-
-    public static final String LOCAL_USER_VCARD_NAME = "profile.vcf";
-    private static final long VCARD_MAX_SIZE = 1024 * 1024 * 8;
-
-    private VCardUtils() {
-        // Hidden default constructor
-    }
-
-    public static Tuple<String, byte[]> readData(VCard vcard) {
-        String contactName = null;
-        byte[] photo = null;
-        if (vcard != null) {
-            if (!vcard.getPhotos().isEmpty()) {
-                try {
-                    photo = vcard.getPhotos().get(0).getData();
-                } catch (Exception e) {
-                    Log.w(TAG, "Can't read photo from VCard", e);
-                    photo = null;
-                }
-            }
-            FormattedName fname = vcard.getFormattedName();
-            if (fname != null) {
-                if (!StringUtils.isEmpty(fname.getValue())) {
-                    contactName = fname.getValue();
-                }
-            }
-        }
-        return new Tuple<>(contactName, photo);
-    }
-
-    public static VCard writeData(String uri, String displayName, byte[] picture) {
-        VCard vcard = new VCard();
-        vcard.setFormattedName(new FormattedName(displayName));
-        vcard.setUid(new Uid(uri));
-        if (picture != null) {
-            vcard.addPhoto(new Photo(picture, ImageType.JPEG));
-        }
-        vcard.removeProperties(RawProperty.class);
-        return vcard;
-    }
-
-    /**
-     * Parse the "elements" of the mime attributes to build a proper hashtable
-     *
-     * @param mime the mimetype as returned by the daemon
-     * @return a correct hashtable, null if invalid input
-     */
-    public static HashMap<String, String> parseMimeAttributes(String mime) {
-        String[] elements = mime.split(";");
-        HashMap<String, String> messageKeyValue = new HashMap<>();
-        if (elements.length < 2) {
-            return messageKeyValue;
-        }
-        messageKeyValue.put(VCARD_KEY_MIME_TYPE, elements[0]);
-        String[] pairs = elements[1].split(",");
-        for (String pair : pairs) {
-            String[] kv = pair.split("=");
-            messageKeyValue.put(kv[0].trim(), kv[1]);
-        }
-        return messageKeyValue;
-    }
-
-    public static void savePeerProfileToDisk(VCard vcard, String accountId, String filename, File filesDir) {
-        saveToDisk(vcard, filename, peerProfilePath(filesDir, accountId));
-    }
-
-    public static Single<VCard> saveLocalProfileToDisk(VCard vcard, String accountId, File filesDir) {
-        return Single.fromCallable(() -> {
-            saveToDisk(vcard, LOCAL_USER_VCARD_NAME, localProfilePath(filesDir, accountId));
-            return vcard;
-        });
-    }
-
-    /**
-     * Saves a vcard string to an internal new vcf file.
-     *
-     * @param vcard    the VCard to save
-     * @param filename the filename of the vcf
-     * @param path     the path of the vcf
-     */
-    private static void saveToDisk(VCard vcard, String filename, File path) {
-        if (vcard == null || StringUtils.isEmpty(filename)) {
-            return;
-        }
-        if (!path.exists()) {
-            path.mkdirs();
-        }
-
-        File file = new File(path, filename);
-        try (VCardWriter writer = new VCardWriter(file, VCardVersion.V2_1)) {
-            writer.getVObjectWriter().getFoldedLineWriter().setLineLength(null);
-            writer.write(vcard);
-        } catch (Exception e) {
-            Log.e(TAG, "Error while saving VCard to disk", e);
-        }
-    }
-
-    public static VCard loadPeerProfileFromDisk(File filesDir, String filename, String accountId) throws IOException {
-        File profileFolder = peerProfilePath(filesDir, accountId);
-        return loadFromDisk(new File(profileFolder, filename));
-    }
-
-    public static Single<VCard> loadLocalProfileFromDisk(File filesDir, String accountId) {
-        return Single.fromCallable(() -> {
-            String path = localProfilePath(filesDir, accountId).getAbsolutePath();
-            return loadFromDisk(new File(path, LOCAL_USER_VCARD_NAME));
-        });
-    }
-
-    public static Single<VCard> loadLocalProfileFromDiskWithDefault(File filesDir, String accountId) {
-        return loadLocalProfileFromDisk(filesDir, accountId)
-                .onErrorReturn(e -> setupDefaultProfile(filesDir, accountId));
-    }
-
-    /**
-     * Loads the vcard file from the disk
-     *
-     * @param path the filename of the vcard
-     * @return the VCard or null
-     */
-    private static VCard loadFromDisk(File path) throws IOException {
-        if (path == null || !path.exists()) {
-            // Log.d(TAG, "vcardPath not exist " + path);
-            return null;
-        }
-        if (path.length() > VCARD_MAX_SIZE) {
-            Log.w(TAG, "vcardPath too big: " + path.length() / 1024 + " kB");
-            return null;
-        }
-        return Ezvcard.parse(path).first();
-    }
-
-    public static String vcardToString(VCard vcard) {
-        StringWriter writer = new StringWriter();
-        VCardWriter vcwriter = new VCardWriter(writer, VCardVersion.V2_1);
-        vcwriter.getVObjectWriter().getFoldedLineWriter().setLineLength(null);
-        String stringVCard;
-        try {
-            vcwriter.write(vcard);
-            stringVCard = writer.toString();
-            vcwriter.close();
-            writer.close();
-        } catch (Exception e) {
-            Log.e(TAG, "Error while converting VCard to String", e);
-            stringVCard = null;
-        }
-
-        return stringVCard;
-    }
-
-    public static boolean isEmpty(VCard vCard) {
-        FormattedName name = vCard.getFormattedName();
-        return (name == null || name.getValue().isEmpty()) && vCard.getPhotos().isEmpty();
-    }
-
-    private static File peerProfilePath(File filesDir, String accountId) {
-        File accountDir = new File(filesDir, accountId);
-        File profileDir = new File(accountDir, "profiles");
-        profileDir.mkdirs();
-        return profileDir;
-    }
-
-    private static File localProfilePath(File filesDir, String accountId) {
-        File accountDir = new File(filesDir, accountId);
-        accountDir.mkdir();
-        return accountDir;
-    }
-
-    private static VCard setupDefaultProfile(File filesDir, String accountId) {
-        VCard vcard = new VCard();
-        vcard.setUid(new Uid(accountId));
-        saveLocalProfileToDisk(vcard, accountId, filesDir)
-                .subscribeOn(Schedulers.io())
-                .subscribe(vc -> {}, e -> Log.e(TAG, "Error while saving vcard", e));
-        return vcard;
-    }
-
-    public static Single<VCard> peerProfileReceived(File filesDir, String accountId, String peerId, File vcard) {
-        return Single.fromCallable(() -> {
-            String filename = peerId + ".vcf";
-            File peerProfilePath = VCardUtils.peerProfilePath(filesDir, accountId);
-            File file = new File(peerProfilePath, filename);
-            FileUtils.moveFile(vcard, file);
-            return VCardUtils.loadFromDisk(file);
-        }).subscribeOn(Schedulers.io());
-    }
-}
diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt b/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt
new file mode 100644
index 000000000..4cd45da4d
--- /dev/null
+++ b/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt
@@ -0,0 +1,231 @@
+/*
+ *  Copyright (C) 2004-2021 Savoir-faire Linux Inc.
+ *
+ *  Author: Romain Bertozzi <romain.bertozzi@savoirfairelinux.com>
+ *
+ *  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, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package net.jami.utils
+
+import net.jami.utils.StringUtils.isEmpty
+import net.jami.utils.FileUtils.moveFile
+import ezvcard.VCard
+import ezvcard.property.FormattedName
+import ezvcard.property.Uid
+import ezvcard.property.RawProperty
+import ezvcard.io.text.VCardWriter
+import ezvcard.VCardVersion
+import kotlin.Throws
+import ezvcard.Ezvcard
+import ezvcard.parameter.ImageType
+import ezvcard.property.Photo
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.schedulers.Schedulers
+import java.io.File
+import java.io.IOException
+import java.io.StringWriter
+import java.lang.Exception
+import java.util.HashMap
+
+object VCardUtils {
+    val TAG = VCardUtils::class.simpleName
+    const val MIME_PROFILE_VCARD = "x-ring/ring.profile.vcard"
+    const val VCARD_KEY_MIME_TYPE = "mimeType"
+    const val VCARD_KEY_PART = "part"
+    const val VCARD_KEY_OF = "of"
+    const val LOCAL_USER_VCARD_NAME = "profile.vcf"
+    private const val VCARD_MAX_SIZE = 1024L * 1024L * 8
+
+    fun readData(vcard: VCard?): Tuple<String?, ByteArray?> {
+        var contactName: String? = null
+        var photo: ByteArray? = null
+        if (vcard != null) {
+            if (vcard.photos.isNotEmpty()) {
+                photo = try {
+                    vcard.photos[0].data
+                } catch (e: Exception) {
+                    Log.w(TAG, "Can't read photo from VCard", e)
+                    null
+                }
+            }
+            val fname = vcard.formattedName
+            if (fname != null) {
+                if (!isEmpty(fname.value)) {
+                    contactName = fname.value
+                }
+            }
+        }
+        return Tuple(contactName, photo)
+    }
+
+    fun writeData(uri: String?, displayName: String?, picture: ByteArray?): VCard {
+        val vcard = VCard()
+        vcard.formattedName = FormattedName(displayName)
+        vcard.uid = Uid(uri)
+        if (picture != null) {
+            vcard.addPhoto(Photo(picture, ImageType.JPEG))
+        }
+        vcard.removeProperties(RawProperty::class.java)
+        return vcard
+    }
+
+    /**
+     * Parse the "elements" of the mime attributes to build a proper hashtable
+     *
+     * @param mime the mimetype as returned by the daemon
+     * @return a correct hashtable, null if invalid input
+     */
+    fun parseMimeAttributes(mime: String): HashMap<String, String> {
+        val elements = mime.split(";".toRegex()).toTypedArray()
+        val messageKeyValue = HashMap<String, String>()
+        if (elements.size < 2) {
+            return messageKeyValue
+        }
+        messageKeyValue[VCARD_KEY_MIME_TYPE] = elements[0]
+        val pairs = elements[1].split(",".toRegex()).toTypedArray()
+        for (pair in pairs) {
+            val kv = pair.split("=".toRegex()).toTypedArray()
+            messageKeyValue[kv[0].trim { it <= ' ' }] = kv[1]
+        }
+        return messageKeyValue
+    }
+
+    fun savePeerProfileToDisk(vcard: VCard?, accountId: String, filename: String, filesDir: File) {
+        saveToDisk(vcard, filename, peerProfilePath(filesDir, accountId))
+    }
+
+    @JvmStatic
+    fun saveLocalProfileToDisk(vcard: VCard, accountId: String, filesDir: File): Single<VCard> {
+        return Single.fromCallable {
+            saveToDisk(vcard, LOCAL_USER_VCARD_NAME, localProfilePath(filesDir, accountId))
+            vcard
+        }
+    }
+
+    /**
+     * Saves a vcard string to an internal new vcf file.
+     *
+     * @param vcard    the VCard to save
+     * @param filename the filename of the vcf
+     * @param path     the path of the vcf
+     */
+    private fun saveToDisk(vcard: VCard?, filename: String, path: File) {
+        if (vcard == null || isEmpty(filename)) {
+            return
+        }
+        if (!path.exists()) {
+            path.mkdirs()
+        }
+        val file = File(path, filename)
+        try {
+            VCardWriter(file, VCardVersion.V2_1).use { writer ->
+                writer.vObjectWriter.foldedLineWriter.lineLength = null
+                writer.write(vcard)
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Error while saving VCard to disk", e)
+        }
+    }
+
+    @Throws(IOException::class)
+    fun loadPeerProfileFromDisk(filesDir: File, filename: String?, accountId: String): VCard? {
+        val profileFolder = peerProfilePath(filesDir, accountId)
+        return loadFromDisk(File(profileFolder, filename))
+    }
+
+    fun loadLocalProfileFromDisk(filesDir: File, accountId: String): Single<VCard> {
+        return Single.fromCallable {
+            val path = localProfilePath(filesDir, accountId).absolutePath
+            loadFromDisk(File(path, LOCAL_USER_VCARD_NAME))
+        }
+    }
+
+    @JvmStatic
+    fun loadLocalProfileFromDiskWithDefault(filesDir: File, accountId: String): Single<VCard> {
+        return loadLocalProfileFromDisk(filesDir, accountId)
+            .onErrorReturn { e: Throwable? -> setupDefaultProfile(filesDir, accountId) }
+    }
+
+    /**
+     * Loads the vcard file from the disk
+     *
+     * @param path the filename of the vcard
+     * @return the VCard or null
+     */
+    @Throws(IOException::class)
+    private fun loadFromDisk(path: File?): VCard? {
+        if (path == null || !path.exists()) {
+            // Log.d(TAG, "vcardPath not exist " + path);
+            return null
+        }
+        if (path.length() > VCARD_MAX_SIZE) {
+            Log.w(TAG, "vcardPath too big: " + path.length() / 1024 + " kB")
+            return null
+        }
+        return Ezvcard.parse(path).first()
+    }
+
+    fun vcardToString(vcard: VCard?): String? {
+        val writer = StringWriter()
+        val vcwriter = VCardWriter(writer, VCardVersion.V2_1)
+        vcwriter.vObjectWriter.foldedLineWriter.lineLength = null
+        var stringVCard: String?
+        try {
+            vcwriter.write(vcard)
+            stringVCard = writer.toString()
+            vcwriter.close()
+            writer.close()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error while converting VCard to String", e)
+            stringVCard = null
+        }
+        return stringVCard
+    }
+
+    fun isEmpty(vCard: VCard): Boolean {
+        val name = vCard.formattedName
+        return (name == null || name.value.isEmpty()) && vCard.photos.isEmpty()
+    }
+
+    private fun peerProfilePath(filesDir: File, accountId: String): File {
+        val accountDir = File(filesDir, accountId)
+        val profileDir = File(accountDir, "profiles")
+        profileDir.mkdirs()
+        return profileDir
+    }
+
+    private fun localProfilePath(filesDir: File, accountId: String): File {
+        return File(filesDir, accountId).apply { mkdir() }
+    }
+
+    private fun setupDefaultProfile(filesDir: File, accountId: String): VCard {
+        val vcard = VCard()
+        vcard.uid = Uid(accountId)
+        saveLocalProfileToDisk(vcard, accountId, filesDir)
+            .subscribeOn(Schedulers.io())
+            .subscribe({}) { e -> Log.e(TAG, "Error while saving vcard", e) }
+        return vcard
+    }
+
+    fun peerProfileReceived(filesDir: File, accountId: String, peerId: String, vcard: File?): Single<VCard> {
+        return Single.fromCallable<VCard> {
+            val filename = "$peerId.vcf"
+            val peerProfilePath = peerProfilePath(filesDir, accountId)
+            val file = File(peerProfilePath, filename)
+            moveFile(vcard!!, file)
+            loadFromDisk(file)
+        }.subscribeOn(Schedulers.io())
+    }
+}
\ No newline at end of file
-- 
GitLab