diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index 210245ac97411816141b7e9486f7037872a43bea..fd97823ed4b95159425f7d90e5f49cb670deb6c6 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -324,6 +324,7 @@
 		649AD3C324B4CFC700A0236D /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6452144724B4ACDE007203D5 /* libsqlite3.tbd */; };
 		649AD3C624B4CFD500A0236D /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6452144524B4ACC8007203D5 /* libxml2.tbd */; };
 		649AD3C724B4D00100A0236D /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6452144124B4AC9F007203D5 /* libc++.tbd */; };
+		64DBCD2224DB3CF600CB5CA2 /* UserSearchResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 64DBCD2124DB3CF600CB5CA2 /* UserSearchResponse.m */; };
 		64F8127724B8AA5200A7DE6A /* MessageCellLocationSharingReceived.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F8127324B8AA5200A7DE6A /* MessageCellLocationSharingReceived.swift */; };
 		64F8127824B8AA5200A7DE6A /* MessageCellLocationSharingReceived.xib in Resources */ = {isa = PBXBuildFile; fileRef = 64F8127624B8AA5200A7DE6A /* MessageCellLocationSharingReceived.xib */; };
 		64F8127A24BBC19C00A7DE6A /* MessageCellLocationSharing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F8127924BBC19C00A7DE6A /* MessageCellLocationSharing.swift */; };
@@ -618,8 +619,6 @@
 		1A3CA32A1F102BB700283748 /* Chameleon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Chameleon.framework; path = Carthage/Build/iOS/Chameleon.framework; sourceTree = "<group>"; };
 		1A3D28A61F0EB9DB00B524EE /* Bool+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bool+String.swift"; sourceTree = "<group>"; };
 		1A3D28A81F0EBF0200B524EE /* UIView+Ring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Ring.swift"; sourceTree = "<group>"; };
-		1A5DC00A1F3558980075E8EF /* ContactsAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContactsAdapter.h; sourceTree = "<group>"; };
-		1A5DC00D1F3559070075E8EF /* ContactsAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContactsAdapter.mm; sourceTree = "<group>"; };
 		1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; };
 		1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; };
 		1A5DC0231F3564360075E8EF /* ContactRequestModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactRequestModel.swift; sourceTree = "<group>"; };
@@ -688,8 +687,6 @@
 		56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LookupNameResponse.m; sourceTree = "<group>"; };
 		56C715FD1F0D36C600770048 /* ContactsAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactsAdapter.h; sourceTree = "<group>"; };
 		56C715FE1F0D36C600770048 /* ContactsAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContactsAdapter.mm; sourceTree = "<group>"; };
-		56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; };
-		56C716021F0D466100770048 /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; };
 		5C093F001FB495830011D90E /* Differentiator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differentiator.framework; path = Carthage/Build/iOS/Differentiator.framework; sourceTree = "<group>"; };
 		5CE66F731FBF769B00EE9291 /* InitialLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = InitialLoadingViewController.storyboard; sourceTree = "<group>"; };
 		5CE66F741FBF769B00EE9291 /* InitialLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitialLoadingViewController.swift; sourceTree = "<group>"; };
@@ -778,6 +775,8 @@
 		645BDD6C24B7415A009129B1 /* MessageCellLocationSharingSent.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellLocationSharingSent.xib; sourceTree = "<group>"; };
 		645BDD7024B7415A009129B1 /* MessageCellLocationSharingSent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellLocationSharingSent.swift; sourceTree = "<group>"; };
 		645BDD8024B74BCB009129B1 /* LocationSharingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSharingService.swift; sourceTree = "<group>"; };
+		64DBCD1E24DB3CA900CB5CA2 /* UserSearchResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserSearchResponse.h; sourceTree = "<group>"; };
+		64DBCD2124DB3CF600CB5CA2 /* UserSearchResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserSearchResponse.m; sourceTree = "<group>"; };
 		64F8127324B8AA5200A7DE6A /* MessageCellLocationSharingReceived.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellLocationSharingReceived.swift; sourceTree = "<group>"; };
 		64F8127624B8AA5200A7DE6A /* MessageCellLocationSharingReceived.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageCellLocationSharingReceived.xib; sourceTree = "<group>"; };
 		64F8127924BBC19C00A7DE6A /* MessageCellLocationSharing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCellLocationSharing.swift; sourceTree = "<group>"; };
@@ -953,8 +952,6 @@
 				02C9B63E1E1D4E8C00F82F0C /* ServiceEvent.swift */,
 				564C44611E943DE6000F92B1 /* NameService.swift */,
 				564C44631E943E1E000F92B1 /* NameRegistrationAdapterDelegate.swift */,
-				56C716021F0D466100770048 /* ContactsService.swift */,
-				56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */,
 				1A5DC01D1F355DA70075E8EF /* ContactsAdapterDelegate.swift */,
 				1A5DC01F1F355DCF0075E8EF /* ContactsService.swift */,
 				62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */,
@@ -999,8 +996,6 @@
 				04399AA81D1C304300E99CD9 /* DRingAdapter.mm */,
 				04399AAA1D1C304300E99CD9 /* Utils.h */,
 				04399AAB1D1C304300E99CD9 /* Utils.mm */,
-				1A5DC00A1F3558980075E8EF /* ContactsAdapter.h */,
-				1A5DC00D1F3559070075E8EF /* ContactsAdapter.mm */,
 				563AEC741EA66487003A5641 /* AccountCreation */,
 				563AEC731EA6627F003A5641 /* NameRegistration */,
 				62A88D351F6C2E5F00F8AB18 /* PresenceAdapter.h */,
@@ -1514,9 +1509,9 @@
 				1A5DC02F1F3565AE0075E8EF /* SmartlistViewController.swift */,
 				1A2D18B51F29164700B2C785 /* SmartlistViewModel.swift */,
 				1A5DC0311F3566140075E8EF /* ConversationSection.swift */,
+				2662FC80246B793500FA7782 /* IncognitoSmartListViewController.storyboard */,
 				2662FC7C246B78E800FA7782 /* IncognitoSmartListViewController.swift */,
 				2662FC7E246B790400FA7782 /* IncognitoSmartListViewModel.swift */,
-				2662FC80246B793500FA7782 /* IncognitoSmartListViewController.storyboard */,
 			);
 			path = Smartlist;
 			sourceTree = "<group>";
@@ -1698,6 +1693,8 @@
 				564C445F1E943C37000F92B1 /* NameRegistrationAdapter.mm */,
 				56308BA51EA00E5700660275 /* NameRegistrationResponse.h */,
 				56308BA61EA00E5700660275 /* NameRegistrationResponse.m */,
+				64DBCD1E24DB3CA900CB5CA2 /* UserSearchResponse.h */,
+				64DBCD2124DB3CF600CB5CA2 /* UserSearchResponse.m */,
 			);
 			path = NameRegistration;
 			sourceTree = "<group>";
@@ -2287,6 +2284,7 @@
 				564C44621E943DE6000F92B1 /* NameService.swift in Sources */,
 				1A5DC02C1F3565250075E8EF /* MeViewController.swift in Sources */,
 				0EF78DE31FD0AE3000FC6966 /* ConversationsManager.swift in Sources */,
+				64DBCD2224DB3CF600CB5CA2 /* UserSearchResponse.m in Sources */,
 				263B715A246D9556007044C4 /* IncognitoSmartListCell.swift in Sources */,
 				66266FC021557D2F002757A6 /* ScanViewModel.swift in Sources */,
 				0E5A668322F0B1F100AA6820 /* ProgressView.swift in Sources */,
diff --git a/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.h b/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.h
index e75d6af129c577a170a9e307d5cfb1af79f66b32..f81b70051389e4ee0461b71b96b91efe12157db8 100644
--- a/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.h
+++ b/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.h
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+ *  Copyright (C) 2017-2020 Savoir-faire Linux Inc.
  *
  *  Author: Silbino Gonçalves Matado <silbino.gmatado@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
@@ -35,5 +36,6 @@
 - (void)lookupAddressWithAccount:(NSString*)account nameserver:(NSString*)nameserver
                          address:(NSString*)address;
 
+- (void)searchUserWithAccount:(NSString*)account query:(NSString*)query;
 
 @end
diff --git a/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.mm b/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.mm
index 671804703326cfb281df94f116d8d20db33628a8..7ba5357b0fc62349639af0108166808665cf8df6 100644
--- a/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.mm
+++ b/Ring/Ring/Bridging/NameRegistration/NameRegistrationAdapter.mm
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+ *  Copyright (C) 2017-2020 Savoir-faire Linux Inc.
  *
  *  Author: Silbino Gonçalves Matado <silbino.gmatado@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
@@ -24,6 +25,7 @@
 #import "dring/configurationmanager_interface.h"
 #import "LookupNameResponse.h"
 #import "NameRegistrationResponse.h"
+#import "UserSearchResponse.h"
 
 @implementation NameRegistrationAdapter
 
@@ -59,8 +61,8 @@ static id <NameRegistrationAdapterDelegate> _delegate;
     }));
 
     confHandlers.insert(exportable_callback<ConfigurationSignal::NameRegistrationEnded>([&](const std::string&account_id,
-                                                                                          int state,
-                                                                                          const std::string& name) {
+                                                                                            int state,
+                                                                                            const std::string& name) {
         if (NameRegistrationAdapter.delegate) {
             NameRegistrationResponse* response = [NameRegistrationResponse new];
             response.accountId = [NSString stringWithUTF8String:account_id.c_str()];
@@ -70,6 +72,20 @@ static id <NameRegistrationAdapterDelegate> _delegate;
         }
     }));
 
+    confHandlers.insert(exportable_callback<ConfigurationSignal::UserSearchEnded>([&](const std::string&account_id,
+                                                                                      int state,
+                                                                                      const std::string&query,
+                                                                                      const std::vector<std::map<std::string,std::string>>&results) {
+        if (NameRegistrationAdapter.delegate) {
+            UserSearchResponse* response = [UserSearchResponse new];
+            response.accountId = [NSString stringWithUTF8String:account_id.c_str()];
+            response.state = (UserSearchState)state;
+            response.query = [NSString stringWithUTF8String:query.c_str()];
+            response.results = [Utils vectorOfMapsToArray:results];
+            [NameRegistrationAdapter.delegate userSearchEndedWith:response];
+        }
+    }));
+
     registerSignalHandlers(confHandlers);
 }
 #pragma mark -
@@ -86,6 +102,10 @@ static id <NameRegistrationAdapterDelegate> _delegate;
     registerName(std::string([account UTF8String]), std::string([password UTF8String]), std::string([name UTF8String]));
 }
 
+- (void)searchUserWithAccount:(NSString*)account query:(NSString*)query {
+    searchUser(std::string([account UTF8String]), std::string([query UTF8String]));
+}
+
 #pragma mark NameRegistrationAdapterDelegate
 + (id <NameRegistrationAdapterDelegate>)delegate {
     return _delegate;
diff --git a/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.h b/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.h
new file mode 100644
index 0000000000000000000000000000000000000000..a57e09edaeec07e7d6aa3e2c33bf4802d874d7b2
--- /dev/null
+++ b/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (C) 2020 Savoir-faire Linux Inc.
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#import <Foundation/Foundation.h>
+
+//Represents the status of the user search response from to the daemon
+typedef NS_ENUM(NSInteger, UserSearchState) {
+    UserSearchStateFound = 0,
+    UserSearchStateInvalidName,
+    UserSearchStateNotFound,
+    UserSearchStateError
+};
+
+@interface UserSearchResponse : NSObject
+
+@property (nonatomic, retain) NSString* accountId;
+@property (nonatomic) UserSearchState state;
+@property (nonatomic, retain) NSString* query;
+@property (nonatomic, retain) NSArray* results;
+
+@end
diff --git a/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.m b/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.m
new file mode 100644
index 0000000000000000000000000000000000000000..f33831dc05a876e4f708268b8b240f927ecd652b
--- /dev/null
+++ b/Ring/Ring/Bridging/NameRegistration/UserSearchResponse.m
@@ -0,0 +1,25 @@
+/*
+ *  Copyright (C) 2020 Savoir-faire Linux Inc.
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+#import "UserSearchResponse.h"
+
+@implementation UserSearchResponse
+
+@end
diff --git a/Ring/Ring/Bridging/Ring-Bridging-Header.h b/Ring/Ring/Bridging/Ring-Bridging-Header.h
index e2316214e5a41c191c453d5a0a4adf596f0e2065..5cad15499ea494c6406cd7b1d75110ed42396215 100644
--- a/Ring/Ring/Bridging/Ring-Bridging-Header.h
+++ b/Ring/Ring/Bridging/Ring-Bridging-Header.h
@@ -28,6 +28,7 @@
 #import "DRingAdapter.h"
 #import "NameRegistrationAdapter.h"
 #import "LookupNameResponse.h"
+#import "UserSearchResponse.h"
 #import "RegistrationResponse.h"
 #import "NameRegistrationResponse.h"
 #import "MessagesAdapter.h"
diff --git a/Ring/Ring/Database/DBManager.swift b/Ring/Ring/Database/DBManager.swift
index e41357feed7e8af26a5430b7b9489057751258f6..2e1598debeb90eaf1cbca828fc84082cf6f4fd41 100644
--- a/Ring/Ring/Database/DBManager.swift
+++ b/Ring/Ring/Database/DBManager.swift
@@ -717,7 +717,8 @@ class DBManager {
         return self.interactionHepler.insert(item: interaction, dataBase: dataBase)
     }
 
-    func getProfile(for profileUri: String, createIfNotExists: Bool, accountId: String) throws -> Profile? {
+    func getProfile(for profileUri: String, createIfNotExists: Bool, accountId: String,
+                    alias: String? = nil, photo: String? = nil) throws -> Profile? {
         let type = profileUri.contains("ring") ? ProfileType.ring : ProfileType.sip
         if createIfNotExists && type == ProfileType.sip {
             self.dbConnections.createAccountfolder(for: accountId)
@@ -731,7 +732,7 @@ class DBManager {
                                     profileURI: profileUri) || !createIfNotExists {
             return getProfileFromPath(path: profilePath)
         }
-        let profile = Profile(profileUri, nil, nil, type.rawValue)
+        let profile = Profile(profileUri, alias, photo, type.rawValue)
         try self.saveProfile(profile: profile, path: profilePath)
         return getProfileFromPath(path: profilePath)
     }
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
index e1a6b51e45afb24a7e4b69be5c4cc2b4218ef97a..5bf8a8fc7d5739298689027f774a5ebf5ace33f8 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
@@ -30,9 +30,7 @@ import SwiftyBeaver
 // swiftlint:disable file_length
 class ConversationViewModel: Stateable, ViewModel {
 
-    /**
-     logguer
-     */
+    /// Logger
     private let log = SwiftyBeaver.self
 
     //Services
@@ -48,16 +46,14 @@ class ConversationViewModel: Stateable, ViewModel {
 
     private let injectionBag: InjectionBag
 
-    private var players = [String: PlayerViewModel]()
+    private let disposeBag = DisposeBag()
 
-    func getPlayer(messageID: String) -> PlayerViewModel? {
-        return players[messageID]
-    }
+    var messages = Variable([MessageViewModel]())
 
-    func setPlayer(messageID: String, player: PlayerViewModel) {
-        players[messageID] = player
-    }
+    private var players = [String: PlayerViewModel]()
 
+    func getPlayer(messageID: String) -> PlayerViewModel? { return players[messageID] }
+    func setPlayer(messageID: String, player: PlayerViewModel) { players[messageID] = player }
     func closeAllPlayers() {
         let queue = DispatchQueue.global(qos: .default)
         queue.sync {
@@ -76,18 +72,46 @@ class ConversationViewModel: Stateable, ViewModel {
     lazy var typingIndicator: Observable<Bool> = {
         return self.conversationsService
             .sharedResponseStream
-            .filter { [weak self] (event) -> Bool in
+            .filter({ [weak self] (event) -> Bool in
                 return event.eventType == ServiceEventType.messageTypingIndicator &&
                     event.getEventInput(ServiceEventInput.accountId) == self?.conversation.value.accountId &&
                     event.getEventInput(ServiceEventInput.peerUri) == self?.conversation.value.hash
-            }.map { (event) -> Bool in
-            if let status: Int = event.getEventInput(ServiceEventInput.state), status == 1 {
-                return true
-            }
-            return false
-            }
+            })
+            .map({ (event) -> Bool in
+                if let status: Int = event.getEventInput(ServiceEventInput.state), status == 1 {
+                    return true
+                }
+                return false
+            })
+    }()
+
+    private var contactUri: String { self.conversation.value.participantUri }
+
+    private var isJamsAccount: Bool { self.accountService.isJams(for: self.conversation.value.accountId) }
+
+    var isAccountSip: Bool = false
+
+    var displayName = Variable<String?>(nil)
+    var userName = Variable<String>("")
+    lazy var bestName: Observable<String> = {
+        return Observable
+            .combineLatest(userName.asObservable(),
+                           displayName.asObservable(),
+                           resultSelector: {(userName, displayname) in
+                            guard let displayname = displayname, !displayname.isEmpty else { return userName }
+                            return displayname
+            })
     }()
 
+    /// My contact's profile's image data
+    var profileImageData = Variable<Data?>(nil)
+    /// My profile's image data
+    var myOwnProfileImageData: Data?
+
+    var inviteButtonIsAvailable = BehaviorSubject(value: true)
+
+    var contactPresence = Variable<Bool>(false)
+
     required init(with injectionBag: InjectionBag) {
         self.injectionBag = injectionBag
         self.accountService = injectionBag.accountService
@@ -99,251 +123,104 @@ class ConversationViewModel: Stateable, ViewModel {
         self.dataTransferService = injectionBag.dataTransferService
         self.callService = injectionBag.callService
         self.locationSharingService = injectionBag.locationSharingService
-
-        dateFormatter.dateStyle = .medium
-        hourFormatter.dateFormat = "HH:mm"
-
-        self.subscribeLocationReceivedEvent()
-        self.subscribeProfileServiceEvent()
     }
 
-    private func subscribeLocationReceivedEvent() {
-        self.locationSharingService
-            .peerUriAndLocationReceived
-            .subscribe(onNext: { [weak self] tuple in
-                guard let self = self, let peerUri = tuple.0, let conversation = self.conversation else { return }
-                let coordinates = tuple.1
-                if peerUri == conversation.value.participantUri {
-                    self.myContactsLocation.onNext(coordinates)
-                }
-            })
-           .disposed(by: self.disposeBag)
+    private func setConversation(_ conversation: ConversationModel) {
+        self.conversation = Variable<ConversationModel>(conversation)
     }
 
-    private func subscribeProfileServiceEvent() {
-        guard let account = self.accountService.currentAccount else { return }
-        self.profileService
-            .getAccountProfile(accountId: account.id)
-            .subscribe(onNext: { [weak self] profile in
-                guard let self = self else { return }
-                if let photo = profile.photo,
-                    let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? {
-                    self.myOwnProfileImageData = data
-                }
-            })
-            .disposed(by: self.disposeBag)
+    convenience init(with injectionBag: InjectionBag, conversation: ConversationModel, user: JamiSearchViewModel.UserSearchModel) {
+        self.init(with: injectionBag)
+        self.userName.value = user.username
+        self.displayName.value = user.firstName + " " + user.lastName
+        self.profileImageData.value = user.profilePicture
+        self.setConversation(conversation) // required to trigger the didSet
     }
 
     var conversation: Variable<ConversationModel>! {
         didSet {
-            let contactUri = self.conversation.value.participantUri
 
-            self.conversationsService
-                .conversationsForCurrentAccount
-                .map({ [weak self] conversations in
-                    return conversations.filter({ conv -> Bool in
-                        let recipient1 = conv.participantUri
-                        let recipient2 = contactUri
-                        if recipient1 == recipient2 {
-                            return true
-                        }
-                        return false
-                    }).map({ [weak self] conversation -> (ConversationModel) in
-                        self?.conversation.value = conversation
-                        return conversation
-                    })
-                        .flatMap({ conversation in
-                            conversation.messages.map({ message -> MessageViewModel? in
-                                if let injBag = self?.injectionBag {
-                                    let lastDisplayed = self?.isLastDisplayed(messageId: message.messageId) ?? false
-                                    return MessageViewModel(withInjectionBag: injBag, withMessage: message, isLastDisplayed: lastDisplayed)
-                                }
-                                return nil
-                            })
-                        }).filter { (message) -> Bool in
-                            message != nil
-                        }.map { (message) -> MessageViewModel in
-                         return message!
-                        }
-                })
-                .observeOn(MainScheduler.instance)
-                .subscribe(onNext: { [weak self] messageViewModels in
-                    guard let self = self else { return }
-                    var msg = messageViewModels
-                    if self.peerComposingMessage {
-                        let msgModel = MessageModel(withId: "",
-                                                    receivedDate: Date(),
-                                                    content: "       ",
-                                                    authorURI: self.conversation.value.participantUri,
-                                                    incoming: true)
-                        let composingIndicator = MessageViewModel(withInjectionBag: self.injectionBag, withMessage: msgModel, isLastDisplayed: false)
-                        composingIndicator.isComposingIndicator = true
-                        msg.append(composingIndicator)
-                    }
-                    self.messages.value = msg
-                }).disposed(by: self.disposeBag)
-
-            self.contactsService
-                .getContactRequestVCard(forContactWithRingId: self.conversation.value.hash)
-                .subscribe(onSuccess: { [weak self] vCard in
-                    guard let imageData = vCard.imageData else {
-                        self?.log.warning("vCard for ringId: \(contactUri) has no image")
-                        return
+            if self.isJamsAccount { // fixes image and displayname not showing when adding contact for first time
+                if let profile = self.contactsService.getProfile(uri: self.contactUri, accountId: self.conversation.value.accountId),
+                    let alias = profile.alias, let photo = profile.photo {
+                    self.displayName.value = alias
+                    if let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? {
+                        self.profileImageData.value = data
                     }
-                    self?.profileImageData.value = imageData
-                    self?.displayName.value = VCardUtils.getName(from: vCard)
-                })
-                .disposed(by: self.disposeBag)
+                }
+            }
 
-            self.profileService
-                .getProfile(uri: contactUri,
-                            createIfNotexists: false,
-                            accountId: self.conversation.value.accountId)
-                .subscribe(onNext: { [weak self] profile in
-                    self?.displayName.value = profile.alias
-                    if let photo = profile.photo,
-                        let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? {
-                        self?.profileImageData.value = data
-                    }
-                }).disposed(by: disposeBag)
+            self.subscribeConversationServiceConversations()
+
+            if !self.isJamsAccount {
+                self.subscribeContactServiceRequestVCard()
+            }
+            self.subscribeProfileServiceContactPhoto()
+
+            // Used for location sharing feature
+            self.subscribeLocationServiceLocationReceived()
+            self.subscribeProfileServiceMyPhoto()
 
-            if let account = self.accountService
-                .getAccount(fromAccountId: self.conversation.value.accountId),
+            if let account = self.accountService.getAccount(fromAccountId: self.conversation.value.accountId),
                 account.type == AccountType.sip {
-                    self.userName.value = self.conversation.value.hash
-                    self.isAccountSip = true
-                    return
+                self.userName.value = self.conversation.value.hash
+                self.isAccountSip = true
+                return
             }
+
             // invite and block buttons
-            let contact = self.contactsService.contact(withUri: contactUri)
+            let contact = self.contactsService.contact(withUri: self.contactUri)
             if contact != nil {
                 self.inviteButtonIsAvailable.onNext(false)
             }
 
-            self.contactsService.contactStatus.filter({ cont in
-                return cont.uriString == contactUri
-            })
-                .subscribe(onNext: { [weak self] _ in
-                    self?.inviteButtonIsAvailable.onNext(false)
-                }).disposed(by: self.disposeBag)
-
-            // subscribe to presence updates for the conversation's associated contact
-            if let contactPresence = self.presenceService
-                .contactPresence[self.conversation.value.hash] {
-                self.contactPresence = contactPresence
-            } else {
-                self.contactPresence.value = false
-                presenceService.sharedResponseStream
-                    .filter({ [weak self] serviceEvent in
-                        guard let uri: String = serviceEvent
-                            .getEventInput(ServiceEventInput.uri),
-                            let accountID: String = serviceEvent
-                                .getEventInput(ServiceEventInput.accountId) else { return false }
-                        return uri == self?.conversation.value.hash &&
-                            accountID == self?.conversation.value.accountId
-                    }).subscribe(onNext: { [weak self] _ in
-                        self?.subscribePresence()
-                    }).disposed(by: self.disposeBag)
-            }
+            self.subscribeContactServiceContactStatus()
 
-            if let contactUserName = contact?.userName {
-                self.userName.value = contactUserName
-            } else {
-                self.userName.value = self.conversation.value.hash
-                // Return an observer for the username lookup
-                self.nameService.usernameLookupStatus
-                    .filter({ [weak self] lookupNameResponse in
-                        return lookupNameResponse.address != nil &&
-                            (lookupNameResponse.address == contactUri ||
-                        lookupNameResponse.address == self?.conversation.value.hash)
-                    }).subscribe(onNext: { [weak self] lookupNameResponse in
-                        if let name = lookupNameResponse.name, !name.isEmpty {
-                            self?.userName.value = name
-                            contact?.userName = name
-                        } else if let address = lookupNameResponse.address {
-                            self?.userName.value = address
-                        }
-                    }).disposed(by: disposeBag)
-
-                self.nameService.lookupAddress(withAccount: self.conversation.value.accountId, nameserver: "", address: self.conversation.value.hash)
-            }
-            self.typingIndicator
-                .subscribe(onNext: { [weak self] (typing) in
-                if typing {
-                    self?.addComposingIndicatorMsg()
+            self.subscribePresenceServiceContactPresence()
+
+            if !self.isJamsAccount || contact != nil {
+                if let contactUserName = contact?.userName {
+                    self.userName.value = contactUserName
                 } else {
-                    self?.removeComposingIndicatorMsg()
+                    self.userName.value = self.conversation.value.hash
+
+                    self.subscribeUserServiceLookupStatus()
+                    self.nameService.lookupAddress(withAccount: self.conversation.value.accountId, nameserver: "", address: self.conversation.value.hash)
                 }
-            }).disposed(by: self.disposeBag)
-        }
-    }
+            }
 
-    func subscribePresence() {
-        if let contactPresence = self.presenceService
-            .contactPresence[self.conversation.value.hash] {
-            self.contactPresence = contactPresence
-        } else {
-            self.contactPresence.value = false
+            self.subscribeConversationServiceTypingIndicator()
         }
     }
 
     //Displays the entire date ( for messages received before the current week )
-    private let dateFormatter = DateFormatter()
+    private lazy var dateFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateStyle = .medium
+        return formatter
+    }()
 
     //Displays the hour of the message reception ( for messages received today )
-    private let hourFormatter = DateFormatter()
-
-    private let disposeBag = DisposeBag()
-
-    var messages = Variable([MessageViewModel]())
-
-    var displayName = Variable<String?>(nil)
-
-    var userName = Variable<String>("")
-
-    var isAccountSip: Bool = false
-
-    lazy var bestName: Observable<String> = {
-        return Observable
-            .combineLatest(userName.asObservable(),
-                           displayName.asObservable()) {(userName, displayname) in
-                            guard let displayname = displayname, !displayname.isEmpty else { return userName }
-                            return displayname
-            }
+    private lazy var hourFormatter: DateFormatter = {
+        let formatter = DateFormatter()
+        formatter.dateFormat = "HH:mm"
+        return formatter
     }()
 
-    // My contact's
-    var profileImageData = Variable<Data?>(nil)
-
-    // Mine
-    var myOwnProfileImageData: Data?
-
-    var inviteButtonIsAvailable = BehaviorSubject(value: true)
-
-    var contactPresence = Variable<Bool>(false)
-
-    var unreadMessages: String {
-       return self.unreadMessagesCount.description
+    private var unreadMessagesCount: Int {
+        let unreadMessages = self.conversation.value.messages.filter({ $0.status != .displayed && !$0.isTransfer && $0.incoming })
+        return unreadMessages.count
     }
 
-    var hasUnreadMessages: Bool {
-        return unreadMessagesCount > 0
-    }
+    var unreadMessages: String { self.unreadMessagesCount.description }
 
-    var lastMessage: String {
-        let messages = self.messages.value
-        if let lastMessage = messages.last?.content {
-            return lastMessage
-        } else {
-            return ""
-        }
-    }
+    var hasUnreadMessages: Bool { unreadMessagesCount > 0 }
+
+    var lastMessage: String { self.messages.value.last?.content ?? "" }
 
     var lastMessageReceivedDate: String {
 
-        guard let lastMessageDate = self.conversation.value.messages.last?.receivedDate else {
-            return ""
-        }
+        guard let lastMessageDate = self.conversation.value.messages.last?.receivedDate else { return "" }
 
         let dateToday = Date()
 
@@ -370,13 +247,9 @@ class ConversationViewModel: Stateable, ViewModel {
         }
     }
 
-    var hideNewMessagesLabel: Bool {
-        return self.unreadMessagesCount == 0
-    }
+    var hideNewMessagesLabel: Bool { self.unreadMessagesCount == 0 }
 
-    var hideDate: Bool {
-        return self.conversation.value.messages.isEmpty
-    }
+    var hideDate: Bool { self.conversation.value.messages.isEmpty }
 
     func sendMessage(withContent content: String) {
         // send a contact request if this is the first message (implicitly not a contact)
@@ -400,12 +273,8 @@ class ConversationViewModel: Stateable, ViewModel {
     }
 
     func setMessagesAsRead() {
-        guard let account = self.accountService.currentAccount else {
-            return
-        }
-        guard let ringId = AccountModelHelper(withAccount: account).ringId  else {
-            return
-        }
+        guard let account = self.accountService.currentAccount,
+              let ringId = AccountModelHelper(withAccount: account).ringId else { return }
 
         self.conversationsService
             .setMessagesAsRead(forConversation: self.conversation.value,
@@ -417,12 +286,9 @@ class ConversationViewModel: Stateable, ViewModel {
     }
 
     func setMessageAsRead(daemonId: String, messageId: Int64) {
-        guard let account = self.accountService.currentAccount else {
-            return
-        }
-        guard let accountURI = AccountModelHelper(withAccount: account).ringId  else {
-            return
-        }
+        guard let account = self.accountService.currentAccount,
+              let accountURI = AccountModelHelper(withAccount: account).ringId else { return }
+
         self.conversationsService
             .setMessageAsRead(daemonId: daemonId,
                               messageID: messageId,
@@ -445,23 +311,18 @@ class ConversationViewModel: Stateable, ViewModel {
         self.messages.value.removeAll(where: { $0.messageId == messageId })
     }
 
-    private var unreadMessagesCount: Int {
-        let unreadMessages = self.conversation.value.messages
-            .filter({ message in
-                return message.status != .displayed &&
-                    !message.isTransfer && message.incoming
-        })
-        return unreadMessages.count
-    }
-
     func sendContactRequest() {
-        if let contact = self.contactsService
-            .contact(withUri: self.conversation.value.participantUri),
-            contact.banned {
-            return
+        guard let currentAccount = self.accountService.currentAccount else { return }
+
+        if self.isJamsAccount {
+            _ = self.contactsService.createProfile(with: self.contactUri,
+                                                   alias: self.displayName.value!,
+                                                   photo: self.profileImageData.value!.base64EncodedString(),
+                                                   accountId: currentAccount.id)
         }
 
-        guard let currentAccount = self.accountService.currentAccount else {
+        if let contact = self.contactsService.contact(withUri: self.conversation.value.participantUri),
+            contact.banned {
             return
         }
 
@@ -472,10 +333,13 @@ class ConversationViewModel: Stateable, ViewModel {
                 self?.log.info("contact request sent")
                 }, onError: { [weak self] (error) in
                     self?.log.info(error)
-            }).disposed(by: self.disposeBag)
-        self.presenceService.subscribeBuddy(withAccountId: currentAccount.id,
-                                            withUri: self.conversation.value.hash,
-                                            withFlag: true)
+            })
+            .disposed(by: self.disposeBag)
+
+        self.presenceService
+            .subscribeBuddy(withAccountId: currentAccount.id,
+                            withUri: self.conversation.value.hash,
+                            withFlag: true)
     }
 
     func block() {
@@ -694,7 +558,191 @@ class ConversationViewModel: Stateable, ViewModel {
     var myContactsLocation = BehaviorSubject<CLLocationCoordinate2D?>(value: nil)
 }
 
-// MARK: Sharing my location
+// MARK: Conversation didSet functions
+extension ConversationViewModel {
+
+    private func subscribeConversationServiceConversations() {
+        let contactUri = self.contactUri
+
+        self.conversationsService
+            .conversationsForCurrentAccount
+            .map({ [weak self] conversations in
+                return conversations
+                    .filter({ conv -> Bool in
+                        let recipient1 = conv.participantUri
+                        let recipient2 = contactUri
+                        return recipient1 == recipient2
+                    })
+                    .map({ [weak self] conversation -> (ConversationModel) in
+                        self?.conversation.value = conversation
+                        return conversation
+                    })
+                    .flatMap({ conversation in
+                        conversation.messages.map({ message -> MessageViewModel? in
+                            if let injBag = self?.injectionBag {
+                                let lastDisplayed = self?.isLastDisplayed(messageId: message.messageId) ?? false
+                                return MessageViewModel(withInjectionBag: injBag, withMessage: message, isLastDisplayed: lastDisplayed)
+                            }
+                            return nil
+                        })
+                    })
+                    .filter({ (message) -> Bool in
+                        message != nil
+                    })
+                    .map({ (message) -> MessageViewModel in
+                        return message!
+                    })
+            })
+            .observeOn(MainScheduler.instance)
+            .subscribe(onNext: { [weak self] messageViewModels in
+                guard let self = self else { return }
+                var msg = messageViewModels
+                if self.peerComposingMessage {
+                    let msgModel = MessageModel(withId: "",
+                                                receivedDate: Date(),
+                                                content: "       ",
+                                                authorURI: self.conversation.value.participantUri,
+                                                incoming: true)
+                    let composingIndicator = MessageViewModel(withInjectionBag: self.injectionBag, withMessage: msgModel, isLastDisplayed: false)
+                    composingIndicator.isComposingIndicator = true
+                    msg.append(composingIndicator)
+                }
+                self.messages.value = msg
+            })
+            .disposed(by: self.disposeBag)
+    }
+
+    private func subscribeLocationServiceLocationReceived() {
+        self.locationSharingService
+            .peerUriAndLocationReceived
+            .subscribe(onNext: { [weak self] tuple in
+                guard let self = self, let peerUri = tuple.0, let conversation = self.conversation else { return }
+                let coordinates = tuple.1
+                if peerUri == conversation.value.participantUri {
+                    self.myContactsLocation.onNext(coordinates)
+                }
+            })
+           .disposed(by: self.disposeBag)
+    }
+
+    private func subscribeContactServiceRequestVCard() {
+        self.contactsService
+            .getContactRequestVCard(forContactWithRingId: self.conversation.value.hash)
+            .subscribe(onSuccess: { [weak self] vCard in
+                guard let imageData = vCard.imageData else {
+                    self?.log.warning("vCard for ringId: \(String(describing: self?.contactUri)) has no image")
+                    return
+                }
+                self?.profileImageData.value = imageData
+                self?.displayName.value = VCardUtils.getName(from: vCard)
+            })
+            .disposed(by: self.disposeBag)
+    }
+
+    private func subscribeProfileServiceContactPhoto() {
+        self.profileService
+            .getProfile(uri: self.contactUri,
+                        createIfNotexists: false,
+                        accountId: self.conversation.value.accountId)
+            .subscribe(onNext: { [weak self] profile in
+                self?.displayName.value = profile.alias
+                if let photo = profile.photo,
+                    let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? {
+                    self?.profileImageData.value = data
+                }
+            })
+            .disposed(by: disposeBag)
+    }
+
+    private func subscribeProfileServiceMyPhoto() {
+        guard let account = self.accountService.currentAccount else { return }
+        self.profileService
+            .getAccountProfile(accountId: account.id)
+            .subscribe(onNext: { [weak self] profile in
+                guard let self = self else { return }
+                if let photo = profile.photo,
+                    let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data? {
+                    self.myOwnProfileImageData = data
+                }
+            })
+            .disposed(by: self.disposeBag)
+    }
+
+    private func subscribePresenceServiceContactPresence() {
+        // subscribe to presence updates for the conversation's associated contact
+        if let contactPresence = self.presenceService.contactPresence[self.conversation.value.hash] {
+            self.contactPresence = contactPresence
+        } else {
+            self.contactPresence.value = false
+            self.presenceService
+                .sharedResponseStream
+                .filter({ [weak self] serviceEvent in
+                    guard let uri: String = serviceEvent.getEventInput(ServiceEventInput.uri),
+                        let accountID: String = serviceEvent.getEventInput(ServiceEventInput.accountId) else { return false }
+                    return uri == self?.conversation.value.hash && accountID == self?.conversation.value.accountId
+                })
+                .subscribe(onNext: { [weak self] _ in
+                    self?.subscribePresence()
+                })
+                .disposed(by: self.disposeBag)
+        }
+    }
+
+    private func subscribePresence() {
+        if let contactPresence = self.presenceService
+            .contactPresence[self.conversation.value.hash] {
+            self.contactPresence = contactPresence
+        } else {
+            self.contactPresence.value = false
+        }
+    }
+
+    private func subscribeUserServiceLookupStatus() {
+        let contact = self.contactsService.contact(withUri: self.contactUri)
+
+        // Return an observer for the username lookup
+        self.nameService
+            .usernameLookupStatus
+            .filter({ [weak self] lookupNameResponse in
+                return lookupNameResponse.address != nil &&
+                    (lookupNameResponse.address == self?.contactUri ||
+                        lookupNameResponse.address == self?.conversation.value.hash)
+            })
+            .subscribe(onNext: { [weak self] lookupNameResponse in
+                if let name = lookupNameResponse.name, !name.isEmpty {
+                    self?.userName.value = name
+                    contact?.userName = name
+                } else if let address = lookupNameResponse.address {
+                    self?.userName.value = address
+                }
+            })
+            .disposed(by: disposeBag)
+    }
+
+    private func subscribeConversationServiceTypingIndicator() {
+        self.typingIndicator
+            .subscribe(onNext: { [weak self] (typing) in
+                if typing {
+                    self?.addComposingIndicatorMsg()
+                } else {
+                    self?.removeComposingIndicatorMsg()
+                }
+            })
+            .disposed(by: self.disposeBag)
+    }
+
+    private func subscribeContactServiceContactStatus() {
+        self.contactsService
+            .contactStatus
+            .filter({ [weak self] in $0.uriString == self?.contactUri })
+            .subscribe(onNext: { [weak self] _ in
+                self?.inviteButtonIsAvailable.onNext(false)
+            })
+            .disposed(by: self.disposeBag)
+    }
+}
+
+// MARK: Location sharing
 extension ConversationViewModel {
 
     func isAlreadySharingLocation() -> Bool {
diff --git a/Ring/Ring/Features/Conversations/SmartList/Cells/ConversationCell.swift b/Ring/Ring/Features/Conversations/SmartList/Cells/ConversationCell.swift
index dde20c9b48c25b808f785dce3fac5e818fdfbec1..7787d527912d1a5445aa20570c7d069545a66a79 100644
--- a/Ring/Ring/Features/Conversations/SmartList/Cells/ConversationCell.swift
+++ b/Ring/Ring/Features/Conversations/SmartList/Cells/ConversationCell.swift
@@ -60,26 +60,16 @@ class ConversationCell: UITableViewCell, NibReusable {
     func configureFromItem(_ item: ConversationSection.Item) {
         // avatar
         Observable<(Data?, String)>.combineLatest(item.profileImageData.asObservable(),
-                                                  item.userName.asObservable(),
-                                                  item.displayName.asObservable()) { profileImage, username, displayName in
-                                                    if let displayName = displayName, !displayName.isEmpty {
-                                                        return (profileImage, displayName)
-                                                    }
-                                                    return (profileImage, username)
-        }
+                                                  item.bestName.asObservable()) { ($0, $1) }
             .observeOn(MainScheduler.instance)
             .startWith((item.profileImageData.value, item.userName.value))
-            .subscribe({ [weak self] profileData -> Void in
-                guard let data = profileData.element?.1 else {
-                    return
-                }
+            .subscribe({ [weak self] profileData in
+                guard let data = profileData.element?.1 else { return }
+
                 self?.avatarView.subviews.forEach({ $0.removeFromSuperview() })
-                self?.avatarView
-                    .addSubview(
-                        AvatarView(profileImageData: profileData.element?.0,
-                                   username: data,
-                                   size: self?.avatarSize ?? 40))
-                return
+                self?.avatarView.addSubview(AvatarView(profileImageData: profileData.element?.0,
+                                                       username: data,
+                                                       size: self?.avatarSize ?? 40))
             })
             .disposed(by: self.disposeBag)
 
@@ -89,12 +79,12 @@ class ConversationCell: UITableViewCell, NibReusable {
 
         // presence
         if self.presenceIndicator != nil {
-        item.contactPresence.asObservable()
-            .observeOn(MainScheduler.instance)
-            .map { value in !value }
-            .bind(to: self.presenceIndicator!.rx.isHidden)
-            .disposed(by: self.disposeBag)
-            }
+            item.contactPresence.asObservable()
+                .observeOn(MainScheduler.instance)
+                .map { value in !value }
+                .bind(to: self.presenceIndicator!.rx.isHidden)
+                .disposed(by: self.disposeBag)
+        }
 
         // username
         item.bestName.asObservable()
diff --git a/Ring/Ring/Features/Conversations/SmartList/ConversationSection.swift b/Ring/Ring/Features/Conversations/SmartList/ConversationSection.swift
index 0fac99be4ddb32ee44f920a49deb67d46975d9fd..2484a4d51ee5a7d1dfe5379b9f35ec42e9ff299c 100644
--- a/Ring/Ring/Features/Conversations/SmartList/ConversationSection.swift
+++ b/Ring/Ring/Features/Conversations/SmartList/ConversationSection.swift
@@ -22,7 +22,7 @@ import RxDataSources
 
 struct ConversationSection {
     var header: String
-    var items: [ConversationViewModel]
+    var items: [Item]
 }
 
 extension ConversationSection: SectionModelType {
diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
index 746d2604a02056a97c1fc53c668f2b3279d42ec1..ab15c1fed9cdc35eabac76e86663fe1398bbbb9a 100644
--- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
+++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
@@ -46,9 +46,7 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
     private let profileService: ProfilesService
     private let callService: CallsService
 
-    lazy var currentAccount: AccountModel? = {
-        return self.accountsService.currentAccount
-    }()
+    var currentAccount: AccountModel? { self.accountsService.currentAccount }
 
     var searching = PublishSubject<Bool>()
 
@@ -57,8 +55,7 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
     lazy var hideNoConversationsMessage: Observable<Bool> = {
         return Observable<Bool>
             .combineLatest(self.conversations,
-                           self.searching.asObservable()
-                            .startWith(false),
+                           self.searching.asObservable().startWith(false),
                            resultSelector: {(conversations, searching) -> Bool in
                             if searching { return true }
                             if let convf = conversations.first {
@@ -73,16 +70,17 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
         return self.accountsService
             .accountsObservable.asObservable()
             .map({ [weak self] accountsModels in
-            var items = [AccountItem]()
-            guard let self = self else { return items }
-            for account in accountsModels {
-                items.append(AccountItem(account: account,
-                                         profileObservable: self.profileService.getAccountProfile(accountId: account.id)))
-            }
-            return items
-        })
+                var items = [AccountItem]()
+                guard let self = self else { return items }
+                for account in accountsModels {
+                    items.append(AccountItem(account: account,
+                                             profileObservable: self.profileService.getAccountProfile(accountId: account.id)))
+                }
+                return items
+            })
     }()
 
+    /// For FilterConversationDataSource protocol
     var conversationViewModels = [ConversationViewModel]()
 
     func networkConnectionState() -> ConnectionType {
@@ -221,6 +219,7 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
             .disposed(by: self.disposeBag)
     }
 
+    /// For FilterConversationDataSource protocol
     func conversationFound(conversation: ConversationViewModel?, name: String) {
         contactFoundConversation.value = conversation
     }
@@ -273,7 +272,8 @@ class SmartlistViewModel: Stateable, ViewModel, FilterConversationDataSource {
         }
     }
 
-    func showConversation (withConversationViewModel conversationViewModel: ConversationViewModel) {
+    /// For FilterConversationDataSource protocol
+    func showConversation(withConversationViewModel conversationViewModel: ConversationViewModel) {
         self.stateSubject.onNext(ConversationState.conversationDetail(conversationViewModel:
         conversationViewModel))
     }
diff --git a/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchView.swift b/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchView.swift
index 00b9aac65b78e3b5fb5d285f16c10763f0bd32a0..b26571e8a8433057e9f55008184abfe3f4652bf1 100644
--- a/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchView.swift
+++ b/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchView.swift
@@ -2,6 +2,7 @@
 *  Copyright (C) 2020 Savoir-faire Linux Inc.
 *
 *  Author: Kateryna Kostiuk <kateryna.kostiuk@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
@@ -24,7 +25,8 @@ import RxDataSources
 import RxCocoa
 import Reusable
 
-class JamiSearchView: NSObject, UITableViewDelegate {
+class JamiSearchView: NSObject {
+
     @IBOutlet weak var searchBar: UISearchBar!
     @IBOutlet weak var searchingLabel: UILabel!
     @IBOutlet weak var searchResultsTableView: UITableView!
@@ -38,9 +40,9 @@ class JamiSearchView: NSObject, UITableViewDelegate {
     let incognitoHeaderHeight: CGFloat = 0
 
     func configure(with injectionBag: InjectionBag, source: FilterConversationDataSource, isIncognito: Bool) {
-        viewModel = JamiSearchViewModel(with: injectionBag, source: source)
+        self.viewModel = JamiSearchViewModel(with: injectionBag, source: source)
         self.isIncognito = isIncognito
-        setUpView()
+        self.setUpView()
     }
 
     private func setUpView() {
@@ -54,43 +56,49 @@ class JamiSearchView: NSObject, UITableViewDelegate {
     }
 
     private func configureSearchResult() {
-        let cellType = isIncognito ? IncognitoSmartListCell.self : SmartListCell.self
-        searchResultsTableView.register(cellType: cellType)
-
-        searchResultsTableView.rowHeight = isIncognito ? incognitoCellHeight : SmartlistConstants.smartlistRowHeight
-        searchResultsTableView.backgroundColor = UIColor.jamiBackgroundColor
-        if !isIncognito {
-            searchResultsTableView.tableFooterView = UIView()
+        if self.isIncognito {
+            self.searchResultsTableView.register(cellType: IncognitoSmartListCell.self)
+            self.searchResultsTableView.rowHeight = self.incognitoCellHeight
+
+        } else {
+            self.searchResultsTableView.register(cellType: SmartListCell.self)
+            self.searchResultsTableView.rowHeight = SmartlistConstants.smartlistRowHeight
+            self.searchResultsTableView.tableFooterView = UIView()
         }
-        searchResultsTableView.rx.setDelegate(self).disposed(by: disposeBag)
-        let configureCell: (TableViewSectionedDataSource, UITableView, IndexPath, ConversationSection.Item)
-            -> UITableViewCell = {
-                (   dataSource: TableViewSectionedDataSource<ConversationSection>,
-                tableView: UITableView,
-                indexPath: IndexPath,
-                conversationItem: ConversationSection.Item) in
-
-                let cell = self.isIncognito ?
-                    tableView.dequeueReusableCell(for: indexPath,
-                                                  cellType: IncognitoSmartListCell.self) :
-                    tableView.dequeueReusableCell(for: indexPath,
-                                                  cellType: SmartListCell.self)
-                cell.configureFromItem(conversationItem)
-                return cell
+        self.searchResultsTableView.backgroundColor = UIColor.jamiBackgroundColor
+
+        self.searchResultsTableView.rx.setDelegate(self).disposed(by: disposeBag)
+
+        let configureCell: (TableViewSectionedDataSource, UITableView, IndexPath, ConversationSection.Item) -> UITableViewCell = {
+            (dataSource: TableViewSectionedDataSource<ConversationSection>,
+            tableView: UITableView,
+            indexPath: IndexPath,
+            conversationItem: ConversationSection.Item) in
+
+            let cellType = self.isIncognito ? IncognitoSmartListCell.self : SmartListCell.self
+            let cell = tableView.dequeueReusableCell(for: indexPath, cellType: cellType)
+            cell.configureFromItem(conversationItem)
+            return cell
         }
         let searchResultsDatasource = RxTableViewSectionedReloadDataSource<ConversationSection>(configureCell: configureCell)
-        viewModel.searchResults.map { (conversations) -> Bool in
-            return conversations.isEmpty
-        }.subscribe(onNext: { [weak self] (hideFooterView) in
-            self?.searchResultsTableView.tableFooterView?.isHidden = hideFooterView
-        }).disposed(by: disposeBag)
 
-        self.viewModel.searchResults
+        self.viewModel
+            .searchResults
+            .map({ (conversations) -> Bool in return conversations.isEmpty })
+            .subscribe(onNext: { [weak self] (hideFooterView) in
+                self?.searchResultsTableView.tableFooterView?.isHidden = hideFooterView })
+            .disposed(by: disposeBag)
+
+        self.viewModel
+            .searchResults
             .bind(to: self.searchResultsTableView.rx.items(dataSource: searchResultsDatasource))
             .disposed(by: disposeBag)
-        searchResultsTableView.rx.itemSelected.subscribe(onNext: { [weak self] indexPath in
-            self?.searchResultsTableView.deselectRow(at: indexPath, animated: true)
-        }).disposed(by: disposeBag)
+
+        self.searchResultsTableView.rx.itemSelected
+            .subscribe(onNext: { [weak self] indexPath in
+                self?.searchResultsTableView.deselectRow(at: indexPath, animated: true) })
+            .disposed(by: disposeBag)
+
         searchResultsDatasource.titleForHeaderInSection = { dataSource, index in
             return dataSource.sectionModels[index].header
         }
@@ -101,10 +109,18 @@ class JamiSearchView: NSObject, UITableViewDelegate {
             .disposed(by: disposeBag)
         searchingLabel.textColor = UIColor.jamiLabelColor
 
-        self.viewModel.isSearching.subscribe(onNext: { [weak self] (isSearching) in
-            self?.searchResultsTableView.isHidden = !isSearching
-            self?.searchingLabel.isHidden = !isSearching
-        }).disposed(by: disposeBag)
+        self.viewModel.isSearching
+            .subscribe(onNext: { [weak self] (isSearching) in
+                self?.searchResultsTableView.isHidden = !isSearching
+                self?.searchingLabel.isHidden = !isSearching
+            })
+            .disposed(by: disposeBag)
+
+        self.viewModel.searchStatus
+            .subscribe(onNext: { [weak self] (searchText) in
+                self?.searchResultsTableView.contentInset.top = searchText.isEmpty ? 0 : 24
+            })
+            .disposed(by: disposeBag)
     }
 
     private func configureSearchBar() {
@@ -152,8 +168,10 @@ class JamiSearchView: NSObject, UITableViewDelegate {
         searchBar.placeholder = L10n.Smartlist.searchBarPlaceholder
         searchBar.backgroundColor = UIColor.clear
     }
+}
 
 // MARK: UITableViewDelegate
+extension JamiSearchView: UITableViewDelegate {
 
     func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
         guard let headerView = view as? UITableViewHeaderFooterView else { return }
diff --git a/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchViewModel.swift b/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchViewModel.swift
index e6b22fbf41ee9d823c6c26528313deb4f73fe5a8..0d0c95140482655cc2a2c8d38bd6a8cc92551358 100644
--- a/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchViewModel.swift
+++ b/Ring/Ring/Features/Conversations/views/JamiSearchView/JamiSearchViewModel.swift
@@ -2,6 +2,7 @@
 *  Copyright (C) 2020 Savoir-faire Linux Inc.
 *
 *  Author: Kateryna Kostiuk <kateryna.kostiuk@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
@@ -21,6 +22,7 @@
 import Foundation
 import RxSwift
 import RxCocoa
+import SwiftyBeaver
 
 protocol FilterConversationDataSource {
     var conversationViewModels: [ConversationViewModel] { get set }
@@ -31,6 +33,10 @@ protocol FilterConversationDataSource {
 
 class JamiSearchViewModel {
 
+    typealias UserSearchModel = (username: String, firstName: String, lastName: String, organization: String, jamiId: String, profilePicture: Data?)
+
+    let log = SwiftyBeaver.self
+
     //Services
     private let nameService: NameService
     private let accountsService: AccountsService
@@ -40,15 +46,24 @@ class JamiSearchViewModel {
 
     lazy var searchResults: Observable<[ConversationSection]> = {
         return Observable<[ConversationSection]>
-            .combineLatest(self.contactFoundConversation
-                .asObservable(),
+            .combineLatest(self.contactFoundConversation.asObservable(),
                            self.filteredResults.asObservable(),
-                           resultSelector: { contactFoundConversation, filteredResults in
+                           self.jamsResults.asObservable(),
+                           resultSelector: { contactFoundConversation, filteredResults, jamsResults in
                             var sections = [ConversationSection]()
+                            let jamsResults = JamiSearchViewModel.removeFilteredConversations(from: jamsResults,
+                                                                                              with: filteredResults)
+                            if !jamsResults.isEmpty {
+                                sections.append(ConversationSection(header: L10n.Smartlist.results, items: jamsResults))
+                            } else if contactFoundConversation != nil {
+                                let contactFoundConversation = JamiSearchViewModel.removeFilteredConversations(from: [contactFoundConversation!],
+                                                                                                               with: filteredResults)
+                                if !contactFoundConversation.isEmpty {
+                                    sections.append(ConversationSection(header: L10n.Smartlist.results, items: contactFoundConversation))
+                                }
+                            }
                             if !filteredResults.isEmpty {
                                 sections.append(ConversationSection(header: L10n.Smartlist.conversations, items: filteredResults))
-                            } else if contactFoundConversation != nil {
-                                sections.append(ConversationSection(header: L10n.Smartlist.results, items: [contactFoundConversation!]))
                             }
                             return sections
             }).observeOn(MainScheduler.instance)
@@ -56,6 +71,7 @@ class JamiSearchViewModel {
 
     private var contactFoundConversation = BehaviorRelay<ConversationViewModel?>(value: nil)
     private var filteredResults = Variable([ConversationViewModel]())
+    private let jamsResults = BehaviorRelay<[ConversationViewModel]>(value: [])
 
     let searchBarText = Variable<String>("")
     var isSearching: Observable<Bool>!
@@ -66,7 +82,7 @@ class JamiSearchViewModel {
         self.nameService = injectionBag.nameService
         self.accountsService = injectionBag.accountService
         self.injectionBag = injectionBag
-        dataSource = source
+        self.dataSource = source
 
         //Observes if the user is searching
         self.isSearching = searchBarText.asObservable()
@@ -85,46 +101,94 @@ class JamiSearchViewModel {
         //Observe username lookup
         self.nameService.usernameLookupStatus
             .observeOn(MainScheduler.instance)
-            .subscribe(onNext: { [unowned self, unowned injectionBag] usernameLookupStatus in
-                if usernameLookupStatus.state == .found &&
-                    (usernameLookupStatus.name == self.searchBarText.value
-                        || usernameLookupStatus.address == self.searchBarText.value) {
-                    if let conversation = self.dataSource.conversationViewModels.filter({ conversationViewModel in
-                        conversationViewModel.conversation.value.participantUri == usernameLookupStatus.address || conversationViewModel.conversation.value.hash == usernameLookupStatus.address
-                    }).first {
+            .subscribe(onNext: { [unowned self, unowned injectionBag] lookupResponse in
+                if lookupResponse.state == .found && (lookupResponse.name == self.searchBarText.value || lookupResponse.address == self.searchBarText.value) {
+                    if let conversation = self.dataSource.conversationViewModels
+                                                          .filter({ conversationViewModel in
+                                                              conversationViewModel.conversation.value.participantUri == lookupResponse.address ||
+                                                              conversationViewModel.conversation.value.hash == lookupResponse.address }).first {
                         self.contactFoundConversation.accept(conversation)
                         self.dataSource.conversationFound(conversation: conversation, name: self.searchBarText.value)
-                    } else {
-                        if self.contactFoundConversation.value?.conversation.value
-                            .participantUri != usernameLookupStatus.address && self.contactFoundConversation.value?.conversation.value
-                                .hash != usernameLookupStatus.address {
-                            if let account = self.accountsService.currentAccount {
-                                let uri = JamiURI.init(schema: URIType.ring, infoHach: usernameLookupStatus.address)
-                                //Create new converation
-                                let conversation = ConversationModel(withParticipantUri: uri, accountId: account.id)
-                                let newConversation = ConversationViewModel(with: injectionBag)
-                                newConversation.conversation = Variable<ConversationModel>(conversation)
-                                self.contactFoundConversation.accept(newConversation)
-                                self.dataSource.conversationFound(conversation: newConversation, name: self.searchBarText.value)
-                            }
-                        }
+
+                    } else if self.contactFoundConversation.value?.conversation.value.participantUri != lookupResponse.address &&
+                        self.contactFoundConversation.value?.conversation.value.hash != lookupResponse.address,
+                        let account = self.accountsService.currentAccount {
+
+                        let uri = JamiURI.init(schema: URIType.ring, infoHach: lookupResponse.address)
+                        //Create new converation
+                        let conversation = ConversationModel(withParticipantUri: uri, accountId: account.id)
+                        let newConversation = ConversationViewModel(with: injectionBag)
+                        newConversation.conversation = Variable<ConversationModel>(conversation)
+                        self.contactFoundConversation.accept(newConversation)
+                        self.dataSource.conversationFound(conversation: newConversation, name: self.searchBarText.value)
                     }
                     self.searchStatus.onNext("")
                 } else {
-                    if self.filteredResults.value.isEmpty
-                        && self.contactFoundConversation.value == nil {
+                    if self.filteredResults.value.isEmpty && self.contactFoundConversation.value == nil {
                         self.searchStatus.onNext(L10n.Smartlist.noResults)
                     } else {
                         self.searchStatus.onNext("")
                     }
                 }
             }).disposed(by: disposeBag)
+
+        self.nameService
+            .userSearchResponseShared
+            .observeOn(MainScheduler.instance)
+            .subscribe(onNext: { [weak self] nameSearchResponse in
+                guard let self = self,
+                      let results = nameSearchResponse.results as? [[String: String]],
+                      let account = self.accountsService.currentAccount else { return }
+
+                let deserializeUser = { (dictionary: [String: String]) -> UserSearchModel? in
+                    guard let username = dictionary["username"], let firstName = dictionary["firstName"],
+                          let lastName = dictionary["lastName"], let organization = dictionary["organization"],
+                          let jamiId = dictionary["id"] ?? dictionary["jamiId"], let base64Encoded = dictionary["profilePicture"]
+                        else { return nil }
+
+                    return UserSearchModel(username: username, firstName: firstName,
+                                           lastName: lastName, organization: organization, jamiId: jamiId,
+                                           profilePicture: NSData(base64Encoded: base64Encoded,
+                                                                  options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data?)
+                }
+
+                var newConversations: [ConversationViewModel] = []
+                for result in results {
+                    if let user = deserializeUser(result) {
+
+                        let uri = JamiURI.init(schema: URIType.ring, infoHach: user.jamiId)
+                        let newConversation = ConversationViewModel(with: injectionBag,
+                                                                    conversation: ConversationModel(withParticipantUri: uri, accountId: account.id),
+                                                                    user: user)
+                        newConversations.append(newConversation)
+                    }
+                }
+                self.jamsResults.accept(newConversations)
+
+                if self.filteredResults.value.isEmpty && self.jamsResults.value.isEmpty {
+                    self.searchStatus.onNext(L10n.Smartlist.noResults)
+                } else {
+                    self.searchStatus.onNext("")
+                }
+            }).disposed(by: self.disposeBag)
+    }
+
+    static func removeFilteredConversations(from conversationViewModels: [ConversationViewModel],
+                                            with filteredResults: [ConversationViewModel]) -> [ConversationViewModel] {
+        return conversationViewModels
+            .filter({ [filteredResults] found -> Bool in
+                return filteredResults
+                    .first(where: { (filtered) -> Bool in
+                        found.conversation.value.participantUri == filtered.conversation.value.participantUri
+                    }) == nil
+            })
     }
 
     private func search(withText text: String) {
         guard let currentAccount = self.accountsService.currentAccount else { return }
 
         self.contactFoundConversation.accept(nil)
+        self.jamsResults.accept([])
         self.dataSource.conversationFound(conversation: nil, name: "")
         self.filteredResults.value.removeAll()
         self.searchStatus.onNext("")
@@ -132,16 +196,26 @@ class JamiSearchViewModel {
         if text.isEmpty { return }
 
         //Filter conversations
-        let filteredConversations = self.dataSource.conversationViewModels
-            .filter({conversationViewModel in
-                conversationViewModel.conversation.value.participantUri == text
-                || conversationViewModel.conversation.value.hash == text
-            })
+        let filteredConversations =
+            self.dataSource.conversationViewModels
+                .filter({conversationViewModel in
+                    conversationViewModel.conversation.value.accountId == currentAccount.id &&
+                        (conversationViewModel.conversation.value.participantUri == text ||
+                            conversationViewModel.conversation.value.hash == text ||
+                            conversationViewModel.userName.value.capitalized.contains(text.capitalized) ||
+                            (conversationViewModel.displayName.value ?? "").capitalized.contains(text.capitalized))
+                })
 
         if !filteredConversations.isEmpty {
             self.filteredResults.value = filteredConversations
         }
 
+        if self.accountsService.isJams(for: currentAccount.id) {
+            self.nameService.searchUser(withAccount: currentAccount.id, query: text)
+            self.searchStatus.onNext(L10n.Smartlist.searching)
+            return
+        }
+
         if currentAccount.type == AccountType.sip {
             let uri = JamiURI.init(schema: URIType.sip, infoHach: text, account: currentAccount)
             let conversation = ConversationModel(withParticipantUri: uri,
diff --git a/Ring/Ring/Features/Me/Me/MeViewModel.swift b/Ring/Ring/Features/Me/Me/MeViewModel.swift
index 5a0ed59465d55acf45310808ee8753168dc14093..f551049fc4b2d82a4ad57111a203a32a526223e1 100644
--- a/Ring/Ring/Features/Me/Me/MeViewModel.swift
+++ b/Ring/Ring/Features/Me/Me/MeViewModel.swift
@@ -211,15 +211,26 @@ class MeViewModel: ViewModel, Stateable {
     }()
 
     lazy var otherJamiSettings: Observable<SettingsSection> = {
-        return Observable
-            .just(SettingsSection.accountSettings( items: [.sectionHeader(title: L10n.AccountPage.other),
-                                                           .peerDiscovery,
-                                                           .blockedList,
-                                                           .accountState(state: self.accountStatus),
-                                                           .enableAccount,
-                                                           .changePassword,
-                                                           .boothMode,
-                                                           .removeAccount]))
+        let items: [SettingsSection.SectionRow] = [.sectionHeader(title: L10n.AccountPage.other),
+                                                   .peerDiscovery,
+                                                   .blockedList,
+                                                   .accountState(state: self.accountStatus),
+                                                   .enableAccount,
+                                                   .changePassword,
+                                                   .boothMode,
+                                                   .removeAccount]
+
+        return Observable.combineLatest(Observable.just(items),
+                                        self.accountService.currentAccountChanged.asObservable().startWith(nil),
+                                        resultSelector: { (items, _) in
+                var items = items
+                if let currentAccount = self.accountService.currentAccount,
+                    self.accountService.isJams(for: currentAccount.id) {
+                    items.remove(at: items.count - 2) //remove .boothMode
+                    items.remove(at: items.count - 2) //remove .changePassword
+                }
+                return SettingsSection.accountSettings(items: items)
+            })
     }()
 
     func hasPassword() -> Bool {
diff --git a/Ring/Ring/Services/AccountsService.swift b/Ring/Ring/Services/AccountsService.swift
index d51d970b2e68dd4376ba1f43ef9605079d764430..9ffba73464a5c03377a5c9389a5b79b82c736d87 100644
--- a/Ring/Ring/Services/AccountsService.swift
+++ b/Ring/Ring/Services/AccountsService.swift
@@ -341,7 +341,7 @@ class AccountsService: AccountAdapterDelegate {
 
         //~ Filter the daemon signals to isolate the "account created" one.
         let filteredDaemonSignals = self.sharedResponseStream
-            .filter { (serviceEvent) -> Bool in
+            .filter({ (serviceEvent) -> Bool in
                 if serviceEvent.getEventInput(ServiceEventInput.accountId) != newAccountId { return false }
                 if serviceEvent.getEventInput(ServiceEventInput.registrationState) == ErrorGeneric {
                     throw AccountCreationError.generic
@@ -352,7 +352,7 @@ class AccountsService: AccountAdapterDelegate {
                 let isRegistered = serviceEvent.getEventInput(ServiceEventInput.registrationState) == Registered
                 let notRegistered = serviceEvent.getEventInput(ServiceEventInput.registrationState) == Unregistered
                 return isRegistrationStateChanged && (isRegistered || notRegistered)
-            }
+            })
 
         //~ Make sure that we have the correct account added in the daemon, and return it.
         return Observable
@@ -934,6 +934,11 @@ class AccountsService: AccountAdapterDelegate {
         return false
     }
 
+    func isJams(for accountId: String) -> Bool {
+        let accountDetails = self.getAccountDetails(fromAccountId: accountId)
+        return !accountDetails.get(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.managerUri)).isEmpty
+    }
+
     func enableAccount(enable: Bool, accountId: String) {
         self.switchAccountPropertyTo(state: enable, accountId: accountId, property: ConfigKeyModel(withKey: ConfigKey.accountEnable))
     }
diff --git a/Ring/Ring/Services/ContactsService.swift b/Ring/Ring/Services/ContactsService.swift
index 2694f75c4ec03f73227792fb0d2cd21dcbeac00b..97c964ba94874d1721fa7c27c6e6e702f0fc2bf8 100644
--- a/Ring/Ring/Services/ContactsService.swift
+++ b/Ring/Ring/Services/ContactsService.swift
@@ -383,6 +383,14 @@ extension ContactsService: ContactsAdapterDelegate {
         }
     }
 
+    func createProfile(with contactUri: String, alias: String, photo: String, accountId: String) -> Profile? {
+        do {
+            return try self.dbManager.getProfile(for: contactUri, createIfNotExists: true, accountId: accountId, alias: alias, photo: photo)
+        } catch {
+            return nil
+        }
+    }
+
     func removeAllContacts(for accountId: String) {
         DispatchQueue.global(qos: .background).async {
             for contact in self.contacts.value {
diff --git a/Ring/Ring/Services/NameRegistrationAdapterDelegate.swift b/Ring/Ring/Services/NameRegistrationAdapterDelegate.swift
index 664acceb089d73a09d8ccbdf32b41723f725e59e..460abf36465160d532b2339e547fa01d518eb21e 100644
--- a/Ring/Ring/Services/NameRegistrationAdapterDelegate.swift
+++ b/Ring/Ring/Services/NameRegistrationAdapterDelegate.swift
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+ *  Copyright (C) 2017-2020 Savoir-faire Linux Inc.
  *
  *  Author: Silbino Gonçalves Matado <silbino.gmatado@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
@@ -21,4 +22,5 @@
 @objc protocol NameRegistrationAdapterDelegate {
     func registeredNameFound(with response: LookupNameResponse)
     func nameRegistrationEnded(with response: NameRegistrationResponse)
+    func userSearchEnded(with response: UserSearchResponse)
 }
diff --git a/Ring/Ring/Services/NameService.swift b/Ring/Ring/Services/NameService.swift
index a16b0ad69a336c646b94933ea690e339fbe1fad8..a295af50d963e9266cc5da7fa98fb2bff8d019be 100644
--- a/Ring/Ring/Services/NameService.swift
+++ b/Ring/Ring/Services/NameService.swift
@@ -1,7 +1,8 @@
 /*
- *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+ *  Copyright (C) 2017-2020 Savoir-faire Linux Inc.
  *
  *  Author: Silbino Gonçalves Matado <silbino.gmatado@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
@@ -34,42 +35,43 @@ enum UsernameValidationStatus {
 
 let registeredNamesKey = "REGISTERED_NAMES_KEY"
 
-class NameService: NameRegistrationAdapterDelegate {
-    /**
-     logguer
-     */
+class NameService {
+
+    /// Logger
     private let log = SwiftyBeaver.self
 
-    /**
-     Used to make lookup name request to the daemon
-    */
+    private let disposeBag = DisposeBag()
+
+    /// Used to make lookup name request to the daemon
     private let nameRegistrationAdapter: NameRegistrationAdapter
 
     private var delayedLookupNameCall: DispatchWorkItem?
 
     private let lookupNameCallDelay = 0.5
 
-    /**
-     Status of the current username validation request
-     */
+    /// Status of the current username validation request
     var usernameValidationStatus = PublishSubject<UsernameValidationStatus>()
     private let registrationStatus = PublishSubject<ServiceEvent>()
     var sharedRegistrationStatus: Observable<ServiceEvent>
 
+    /// Status of the current username lookup request
+    var usernameLookupStatus = PublishSubject<LookupNameResponse>()
+
+    private let userSearchResponseStream = PublishSubject<UserSearchResponse>()
+    /// Triggered when we receive a UserSearchResponse from the daemon
+    let userSearchResponseShared: Observable<UserSearchResponse>
+
     init(withNameRegistrationAdapter nameRegistrationAdapter: NameRegistrationAdapter) {
         self.nameRegistrationAdapter = nameRegistrationAdapter
         self.sharedRegistrationStatus = registrationStatus.share()
+
+        self.userSearchResponseStream.disposed(by: self.disposeBag)
+        self.userSearchResponseShared = self.userSearchResponseStream.share()
+
         NameRegistrationAdapter.delegate = self
     }
 
-    /**
-     Status of the current username lookup request
-     */
-    var usernameLookupStatus = PublishSubject<LookupNameResponse>()
-
-    /**
-    Make a username lookup request to the daemon
-     */
+    /// Make a username lookup request to the daemon
     func lookupName(withAccount account: String, nameserver: String, name: String) {
 
         //Cancel previous lookups...
@@ -92,16 +94,12 @@ class NameService: NameRegistrationAdapterDelegate {
         }
     }
 
-    /**
-     Make an address lookup request to the daemon
-    */
+    /// Make an address lookup request to the daemon
     func lookupAddress(withAccount account: String, nameserver: String, address: String) {
         self.nameRegistrationAdapter.lookupAddress(withAccount: account, nameserver: nameserver, address: address)
     }
 
-    /**
-     Register the username into the the blockchain
-     */
+    /// Register the username into the the blockchain
     func registerName(withAccount account: String, password: String, name: String) {
         self.nameRegistrationAdapter.registerName(withAccount: account, password: password, name: name)
     }
@@ -122,13 +120,13 @@ class NameService: NameRegistrationAdapterDelegate {
             })
 
         let filteredDaemonSignals = self.sharedRegistrationStatus
-            .filter { (serviceEvent) -> Bool in
+            .filter({ (serviceEvent) -> Bool in
                 if serviceEvent.getEventInput(ServiceEventInput.accountId) != account { return false }
                 if serviceEvent.eventType != .nameRegistrationEnded {
                     return false
                 }
                 return true
-            }
+            })
         return Observable
             .combineLatest(registerName.asObservable(), filteredDaemonSignals.asObservable()) { (_, serviceEvent) -> Bool in
                 guard let status: NameRegistrationState = serviceEvent.getEventInput(ServiceEventInput.state)
@@ -142,7 +140,14 @@ class NameService: NameRegistrationAdapterDelegate {
             }
     }
 
-    // MARK: NameService delegate
+    /// Make a user search request to the daemon
+    func searchUser(withAccount account: String, query: String) {
+        self.nameRegistrationAdapter.searchUser(withAccount: account, query: query)
+    }
+}
+
+// MARK: NameRegistrationAdapterDelegate
+extension NameService: NameRegistrationAdapterDelegate {
 
     internal func registeredNameFound(with response: LookupNameResponse) {
 
@@ -176,4 +181,22 @@ class NameService: NameRegistrationAdapterDelegate {
         event.addEventInput(.accountId, value: response.accountId)
         self.registrationStatus.onNext(event)
     }
+
+    internal func userSearchEnded(with response: UserSearchResponse) {
+        switch response.state {
+        case .found:
+            self.userSearchResponseStream.onNext(response)
+        case .invalidName:
+            self.userSearchResponseStream.onNext(response)
+            self.log.warning("User search invalid name")
+        case.notFound:
+            self.userSearchResponseStream.onNext(response)
+            self.log.warning("User search not found")
+        case .error:
+            self.userSearchResponseStream.onNext(response)
+            self.log.error("User search error")
+        @unknown default:
+            self.log.error("User search unknown default")
+        }
+    }
 }