From b7ff1de901a9630b56fc6784d8625b2e4f0a3143 Mon Sep 17 00:00:00 2001
From: Maxime Callet <maxime.callet@savoirfairelinux.com>
Date: Thu, 23 Sep 2021 17:26:52 -0400
Subject: [PATCH] call: split local and peer video surface management

Change-Id: If8341dd461821d83a11d9f98764be3e4474ede4c
---
 .../main/java/cx/ring/client/CallActivity.kt  |  23 ++--
 .../client/ConversationSelectionActivity.kt   |   3 +-
 .../java/cx/ring/fragments/CallFragment.kt    | 127 ++++++------------
 .../cx/ring/services/HardwareServiceImpl.kt   |  77 ++++-------
 .../ring/services/NotificationServiceImpl.kt  |  24 ++--
 .../java/cx/ring/tv/call/TVCallFragment.kt    |  87 +++++++++---
 .../app/src/main/res/layout/tv_frag_call.xml  |  78 +++++------
 .../src/main/res/values-fr-rFR/strings.xml    |   3 +-
 .../app/src/main/res/values/strings.xml       |   3 +-
 .../kotlin/net/jami/call/CallPresenter.kt     |  62 ++++-----
 .../src/main/kotlin/net/jami/call/CallView.kt |   5 +-
 .../net/jami/services/HardwareService.kt      |  17 +--
 12 files changed, 243 insertions(+), 266 deletions(-)

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
index 5f043279c..c70d04d53 100644
--- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.kt
+++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.kt
@@ -96,17 +96,20 @@ class CallActivity : AppCompatActivity() {
 
     private fun handleNewIntent(intent: Intent) {
         val action = intent.action
-        val hasVideo = intent.getBooleanExtra(CallFragment.KEY_HAS_VIDEO, false)
+        val wantVideo = intent.getBooleanExtra(CallFragment.KEY_HAS_VIDEO, false)
         if (Intent.ACTION_CALL == action) {
             val contactId = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
-            val callFragment = CallFragment.newInstance(action, fromIntent(intent), contactId, hasVideo)
+            val callFragment = CallFragment.newInstance(action, fromIntent(intent), contactId, wantVideo)
             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(action, confId, hasVideo)
-            supportFragmentManager.beginTransaction()
-                .replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit()
+            val currentId = callFragment?.arguments?.get(NotificationService.KEY_CALL_ID)
+            if (currentId != confId) {
+                val callFragment = CallFragment.newInstance(action, confId, wantVideo)
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.main_call_layout, callFragment, CALL_FRAGMENT_TAG).commit()
+            }
         }
     }
 
@@ -125,7 +128,6 @@ class CallActivity : AppCompatActivity() {
     }
 
     public override fun onUserLeaveHint() {
-        val callFragment = callFragment
         callFragment?.onUserLeave()
     }
 
@@ -183,16 +185,11 @@ class CallActivity : AppCompatActivity() {
     // 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
+            return supportFragmentManager.findFragmentByTag(CALL_FRAGMENT_TAG) as CallFragment?
         }
 
     companion object {
+        private val TAG = CallActivity::class.simpleName!!
         const val ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT"
         private const val CALL_FRAGMENT_TAG = "CALL_FRAGMENT_TAG"
 
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
index 9176a1231..1a9016422 100644
--- a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt
+++ b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt
@@ -36,6 +36,7 @@ import net.jami.services.ConversationFacade
 import net.jami.model.Account
 import net.jami.model.Conference
 import net.jami.services.CallService
+import net.jami.services.NotificationService
 import net.jami.smartlist.SmartListViewModel
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -74,7 +75,7 @@ class ConversationSelectionActivity : AppCompatActivity() {
 
     public override fun onStart() {
         super.onStart()
-        val conference: Conference? = intent?.getStringExtra(CallFragment.KEY_CONF_ID)?.let { confId -> mCallService.getConference(confId) }
+        val conference: Conference? = intent?.getStringExtra(NotificationService.KEY_CALL_ID)?.let { confId -> mCallService.getConference(confId) }
         mDisposable.add(mConversationFacade
             .currentAccountSubject
             .switchMap { a: Account -> a.getConversationsViewModels(false) }
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
index 69b49bb8d..96559dcca 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
@@ -152,7 +152,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
             }
             else if (action == Intent.ACTION_VIEW || action == CallActivity.ACTION_CALL_ACCEPT) {
                 Log.w(TAG, "DEBUG fn initPresenter [CallFragment.kt] -> requesting fn initIncomingCall( CONF_ID, GET_CALL)")
-                presenter.initIncomingCall(args.getString(KEY_CONF_ID)!!, action == Intent.ACTION_VIEW)
+                presenter.initIncomingCall(args.getString(NotificationService.KEY_CALL_ID)!!, action == Intent.ACTION_VIEW)
             }
         }
     }
@@ -200,37 +200,9 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         }
     }
 
-    override fun onStart() {
-        super.onStart()
-        Log.w(TAG, "DEBUG fn onStart [CallFragment.kt] -> on start, value hasVideo = ${presenter.wantVideo}, restartvideo : $restartVideo | restartPreview : $restartPreview")
-        if (restartVideo && restartPreview) {
-            displayVideoSurface(true, !presenter.isPipMode && presenter.wantVideo)
-            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(
@@ -693,26 +665,22 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
     }
 
     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)
+        Log.w(CallPresenter.TAG, "DEBUG onPrepareOptionsMenu")
         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()
+        when (item.itemId) {
+            android.R.id.home -> presenter.chatClick()
+            R.id.menuitem_dialpad -> presenter.dialpadClick()
+            R.id.menuitem_video_plugins -> displayVideoPluginsCarousel()
+            else -> return super.onOptionsItemSelected(item)
         }
         return true
     }
@@ -732,34 +700,23 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
     }
 
     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
+        Log.w(TAG, "DEBUG fn displayContactBubble -> $display")
+        binding?.apply {
+            contactBubbleLayout.visibility = if (display) View.VISIBLE else View.GONE
         }
     }
 
-    override fun displayVideoSurface(
-        displayVideoSurface: Boolean,
-        displayPreviewContainer: Boolean
-    ) {
-        Log.w(TAG, "DEBUG fn displayVideoSurface [CallFragment.kt] -> changement des affichages videos  displayVideoSurface: $displayVideoSurface; displayPreviewContainer: $displayPreviewContainer")
-        binding!!.videoSurface.visibility = if (displayVideoSurface) View.VISIBLE else View.GONE
+    override fun displayPeerVideo(display: Boolean) {
+        Log.w(TAG, "DEBUG fn displayPeerVideo -> $display")
+        binding!!.videoSurface.visibility = if (display) View.VISIBLE else View.GONE
+        displayContactBubble(!display)
+    }
 
-        if (isChoosePluginMode) {
-            Log.w(TAG, "DEBUG fn displayVideoSurface |1| inside => if (isChoosePluginMode) {displayPreviewContainer: $displayPreviewContainer}")
-            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 {
-            Log.w(TAG, "DEBUG fn displayVideoSurface |2| inside => else { displayPreviewContainer : $displayPreviewContainer}")
-            binding!!.pluginPreviewSurface.visibility = View.GONE
-            binding!!.pluginPreviewContainer.visibility = View.GONE
-            binding!!.previewContainer.visibility =
-                if (displayPreviewContainer) View.VISIBLE else View.GONE
-        }
-        updateMenu()
+    override fun displayLocalVideo(display: Boolean) {
+        Log.w(TAG, "DEBUG fn displayLocalVideo -> $display")
+        /*binding!!.pluginPreviewSurface.visibility = View.GONE
+        binding!!.pluginPreviewContainer.visibility = View.GONE*/
+        binding!!.previewContainer.visibility = if (display) View.VISIBLE else View.GONE
     }
 
     // todo Change function name, this name is misleading, this function concerns PIP preview
@@ -853,6 +810,10 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
                     activity.intent = Intent(Intent.ACTION_VIEW,
                         ConversationPath.toUri(call.account!!, conversationUri), context, CallActivity::class.java)
                         .apply { putExtra(NotificationService.KEY_CALL_ID, call.confId ?: call.daemonIdString) }
+                    arguments = Bundle().apply {
+                        putString(KEY_ACTION, Intent.ACTION_VIEW)
+                        putString(NotificationService.KEY_CALL_ID, call.confId ?: call.daemonIdString)
+                    }
                 }
             }
             if (hasProfileName) {
@@ -968,24 +929,19 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         isSpeakerOn: Boolean, hasMultipleCamera: Boolean, canDial: Boolean,
         showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveVideo: Boolean
     ) {
-        //
-        if (!hasActiveVideo) {
-            binding!!.callCameraSwitchBtn.isChecked = true
-            binding!!.callCameraSwitchBtn.setImageResource(R.drawable.baseline_videocam_off_24)
-        }
-        binding!!.callCameraFlipBtn.visibility = if (hasMultipleCamera && !binding!!.callCameraSwitchBtn.isChecked) View.VISIBLE else View.GONE
-
-        if (dialPadBtn != null) {
-            dialPadBtn!!.isVisible = canDial
-        }
-        if (pluginsMenuBtn != null) {
-            pluginsMenuBtn!!.isVisible = showPluginBtn
+        Log.w(CallPresenter.TAG, "DEBUG initMenu hasActiveVideo: $hasActiveVideo; hasMultipleCamera: $hasMultipleCamera")
+        binding?.apply {
+            callSpeakerBtn.visibility = if (hasActiveVideo) View.GONE else View.VISIBLE
+            callCameraSwitchBtn.isChecked = !hasActiveVideo
+            callCameraSwitchBtn.setImageResource(if (hasActiveVideo) R.drawable.baseline_videocam_24 else R.drawable.baseline_videocam_off_24)
+            callCameraFlipBtn.visibility = if (hasMultipleCamera && hasActiveVideo) View.VISIBLE else View.GONE
         }
+        dialPadBtn?.isVisible = canDial
+        pluginsMenuBtn?.isVisible = showPluginBtn
         updateMenu()
     }
 
-    override fun initNormalStateDisplay(audioOnly: Boolean, isMuted: Boolean) {
-        Log.w(TAG, "initNormalStateDisplay")
+    override fun initNormalStateDisplay(isMuted: Boolean) {
         binding?.apply {
             shapeRipple.stopRipple()
             callAcceptBtn.visibility = View.GONE
@@ -993,7 +949,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
             callRefuseBtn.visibility = View.GONE
             callControlGroup.visibility = View.VISIBLE
             callHangupBtn.visibility = View.VISIBLE
-            contactBubbleLayout.visibility = if (audioOnly) View.VISIBLE else View.GONE
+            contactBubbleLayout.visibility =  View.VISIBLE
             callMicBtn.isChecked = isMuted
         }
         requireActivity().invalidateOptionsMenu()
@@ -1203,8 +1159,10 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
 
     //todo if videomode, should mute/unmute audio output, if audio only, should switch between speaker options
     fun speakerClicked() {
-        presenter.speakerClick(binding!!.callSpeakerBtn.isChecked)
-        //binding!!.callSpeakerBtn.setImageResource(if (binding!!.callSpeakerBtn.isChecked) R.drawable.baseline_sound_on_24 else R.drawable.baseline_sound_off_24)
+        binding?.let {
+            presenter.speakerClick(it.callSpeakerBtn.isChecked)
+            //it.callSpeakerBtn.setImageResource(if (it.callSpeakerBtn.isChecked) R.drawable.baseline_sound_on_24 else R.drawable.baseline_sound_off_24)
+        }
     }
 
     private fun startScreenShare(mediaProjection: MediaProjection?) {
@@ -1273,7 +1231,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
     override fun startAddParticipant(conferenceId: String) {
         startActivityForResult(Intent(Intent.ACTION_PICK)
                 .setClass(requireActivity(), ConversationSelectionActivity::class.java)
-                .putExtra(KEY_CONF_ID, conferenceId),
+                .putExtra(NotificationService.KEY_CALL_ID, conferenceId),
             REQUEST_CODE_ADD_PARTICIPANT)
     }
 
@@ -1401,7 +1359,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         }
 
         //change preview image
-        displayVideoSurface(true, presenter.wantVideo)
+        //displayPeerVideo(true, presenter.wantVideo)
     }
 
     /**
@@ -1455,7 +1413,6 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         val TAG = CallFragment::class.simpleName!!
         const val ACTION_PLACE_CALL = "PLACE_CALL"
         const val KEY_ACTION = "action"
-        const val KEY_CONF_ID = "confId"
         const val KEY_HAS_VIDEO = "HAS_VIDEO"
         private const val REQUEST_CODE_ADD_PARTICIPANT = 6
         private const val REQUEST_PERMISSION_INCOMING = 1003
@@ -1463,6 +1420,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         private const val REQUEST_CODE_SCREEN_SHARE = 7
 
         fun newInstance(action: String, path: ConversationPath?, contactId: String?, hasVideo: Boolean): CallFragment {
+            Log.w(TAG, "DEBUG newInstance $action $path $contactId $hasVideo")
             return CallFragment().apply {
                 arguments = Bundle().apply {
                     putString(KEY_ACTION, action)
@@ -1474,10 +1432,11 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         }
 
         fun newInstance(action: String, confId: String?, hasVideo: Boolean): CallFragment {
+            Log.w(TAG, "DEBUG newInstance $action $confId $hasVideo")
             return CallFragment().apply {
                 arguments = Bundle().apply {
                     putString(KEY_ACTION, action)
-                    putString(KEY_CONF_ID, confId)
+                    putString(NotificationService.KEY_CALL_ID, confId)
                     putBoolean(KEY_HAS_VIDEO, hasVideo)
                 }
             }
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
index 77b0d779e..0b52f3e6b 100644
--- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt
+++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt
@@ -320,30 +320,22 @@ class HardwareServiceImpl(
     }
 
     override fun decodingStarted(id: String, shmPath: String, width: Int, height: Int, isMixer: Boolean) {
-        Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height)
+        Log.i(TAG, "DEBUG decodingStarted() " + id + " " + width + "x" + height)
         val shm = Shm(id, width, height)
         videoInputs[id] = shm
+        videoEvents.onNext(VideoEvent(id, start = true))
         videoSurfaces[id]?.get()?.let { holder ->
             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
+                Log.w(TAG, "DJamiService.decodingStarted() no window !")
+            } else {
+                videoEvents.onNext(VideoEvent(shm.id, started = true, w = shm.w, h = shm.h))
             }
-            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")
+        Log.i(TAG, "DEBUG decodingStopped() $id")
         val shm = videoInputs.remove(id) ?: return
         if (shm.window != 0L) {
             try {
@@ -352,9 +344,14 @@ class HardwareServiceImpl(
                 Log.e(TAG, "decodingStopped error$e")
             }
             shm.window = 0
+            videoEvents.onNext(VideoEvent(id, started = false))
         }
     }
 
+    override fun hasInput(id: String): Boolean {
+        return videoInputs[id] !== null
+    }
+
     override fun getCameraInfo(camId: String, formats: IntVect, sizes: UintVect, rates: UintVect) {
         // Use a larger resolution for Android 6.0+, 64 bits devices
         val useLargerSize =
@@ -444,9 +441,7 @@ class HardwareServiceImpl(
         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)
+            videoEvents.onNext(VideoEvent(start = true))
             return
         }
         val conf = mCameraPreviewCall.get()
@@ -491,12 +486,12 @@ class HardwareServiceImpl(
             )
         }
         cameraService.setPreviewParams(videoParams)
-        val event = VideoEvent()
-        event.started = true
-        event.w = videoParams.width
-        event.h = videoParams.height
-        event.rot = videoParams.rotation
-        videoEvents.onNext(event)
+        videoEvents.onNext(VideoEvent(
+            started = true,
+            w = videoParams.width,
+            h = videoParams.height,
+            rot = videoParams.rotation
+        ))
     }
 
     override fun stopCapture() {
@@ -521,11 +516,7 @@ class HardwareServiceImpl(
         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)
+            videoEvents.onNext(VideoEvent(started = false))
         }
         mIsCapturing = false
     }
@@ -543,17 +534,10 @@ class HardwareServiceImpl(
         }
         if (shm == null || shm.window == 0L) {
             Log.i(TAG, "DJamiService.addVideoSurface() no window !")
-            val event = VideoEvent()
-            event.start = true
-            videoEvents.onNext(event)
-            return
+            //videoEvents.onNext(VideoEvent(id, start = true))
+        } else {
+            videoEvents.onNext(VideoEvent(shm.id, started = true, w = shm.w, h = shm.h))
         }
-        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) {
@@ -607,10 +591,7 @@ class HardwareServiceImpl(
             }
             shm.window = 0
         }
-        val event = VideoEvent()
-        event.callId = shm.id
-        event.started = false
-        videoEvents.onNext(event)
+        //videoEvents.onNext(VideoEvent(shm.id, started = false))
     }
 
     override fun removePreviewVideoSurface() {
@@ -642,12 +623,12 @@ class HardwareServiceImpl(
         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)
+            videoEvents.onNext(VideoEvent(
+                started = true,
+                w = videoParams.width,
+                h = videoParams.height,
+                rot = videoParams.rotation
+            ))
         }
     }
 
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
index 044f6035d..cac0492a5 100644
--- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt
+++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt
@@ -145,31 +145,27 @@ class NotificationServiceImpl(
                         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
-                        )
-                    )
+                                .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString), PendingIntent.FLAG_ONE_SHOT))
                 Log.w(TAG, "DEBUG fn buildCallNotification [NotificationServiceImpl.kt] -> conference.hasvideo = ${conference.hasVideo()} ")
 
                 if (conference.hasVideo()){
                         messageNotificationBuilder
-                            .addAction(R.id.btnAcceptAudio, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_audio) 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)
+                            .addAction(R.drawable.baseline_call_24, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_audio) else mContext.getText(R.string.action_call_hold_accept),
+                                PendingIntent.getService(mContext, random.nextInt(), Intent(if (ongoingCallId == null) DRingService.ACTION_CALL_ACCEPT else DRingService.ACTION_CALL_HOLD_ACCEPT)
                                     .setClass(mContext, DRingService::class.java)
-                                    .putExtra(NotificationService.KEY_END_ID, ongoingCallId)
+                                    .putExtra(NotificationService.KEY_HOLD_ID, ongoingCallId)
                                     .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString)
-                                    .putExtra(CallFragment.KEY_HAS_VIDEO, false), PendingIntent.FLAG_ONE_SHOT)
-                            )
-                            .addAction(R.id.btnAcceptVideo, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_video) 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)
+                                    .putExtra(CallFragment.KEY_HAS_VIDEO, false), PendingIntent.FLAG_ONE_SHOT))
+                            .addAction(R.drawable.baseline_videocam_24, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_video) else mContext.getText(R.string.action_call_hold_accept_video),
+                                PendingIntent.getService(mContext, random.nextInt(), Intent(if (ongoingCallId == null) DRingService.ACTION_CALL_ACCEPT else DRingService.ACTION_CALL_HOLD_ACCEPT)
                                     .setClass(mContext, DRingService::class.java)
-                                    .putExtra(NotificationService.KEY_END_ID, ongoingCallId)
+                                    .putExtra(NotificationService.KEY_HOLD_ID, ongoingCallId)
                                     .putExtra(NotificationService.KEY_CALL_ID, call.daemonIdString)
-                                    .putExtra(CallFragment.KEY_HAS_VIDEO, true), PendingIntent.FLAG_ONE_SHOT)
-                            )
+                                    .putExtra(CallFragment.KEY_HAS_VIDEO, true), PendingIntent.FLAG_ONE_SHOT))
 
                     } else {
                         messageNotificationBuilder.addAction(
-                            R.id.btnAcceptAudio, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_audio) else mContext.getText(R.string.action_call_end_accept),
+                            R.drawable.baseline_call_24, if (ongoingCallId == null) mContext.getText(R.string.action_call_accept_audio) 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)
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
index fce5146af..0d06744f4 100644
--- 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
@@ -76,6 +76,7 @@ import net.jami.model.Contact
 import net.jami.model.Uri
 import net.jami.services.DeviceRuntimeService
 import net.jami.services.HardwareService.AudioState
+import net.jami.services.NotificationService
 import java.util.*
 import javax.inject.Inject
 import kotlin.math.max
@@ -119,7 +120,7 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
             if (action == CallFragment.ACTION_PLACE_CALL || action == Intent.ACTION_CALL)
                 prepareCall(false)
             else if (action == Intent.ACTION_VIEW || action == CallActivity.ACTION_CALL_ACCEPT)
-                presenter.initIncomingCall(args.getString(CallFragment.KEY_CONF_ID)!!, action == Intent.ACTION_VIEW)
+                presenter.initIncomingCall(args.getString(NotificationService.KEY_CALL_ID)!!, action == Intent.ACTION_VIEW)
         }
     }
 
@@ -262,10 +263,17 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
     override fun displayContactBubble(display: Boolean) {
         binding!!.contactBubbleLayout.visibility = if (display) View.VISIBLE else View.GONE
     }
+    override fun displayPeerVideo(display: Boolean) {
+        Log.w(CallFragment.TAG, "DEBUG fn displayPeerVideo -> $display")
+        binding!!.videoSurface.visibility = if (display) View.VISIBLE else View.GONE
+        displayContactBubble(!display)
+    }
 
-    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 displayLocalVideo(display: Boolean) {
+        Log.w(CallFragment.TAG, "DEBUG fn displayLocalVideo -> $display")
+        /*binding!!.pluginPreviewSurface.visibility = View.GONE
+        binding!!.pluginPreviewContainer.visibility = View.GONE*/
+        binding!!.previewContainer.visibility = if (display) View.VISIBLE else View.GONE
     }
 
     override fun displayPreviewSurface(display: Boolean) {
@@ -320,20 +328,17 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
         }
     }
 
-    override fun initMenu(
-        isSpeakerOn: Boolean, displayFlip: Boolean, canDial: Boolean,
-        showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveVideo: Boolean
-    ) {
+    override fun initMenu(isSpeakerOn: Boolean, displayFlip: Boolean, canDial: Boolean, showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveVideo: Boolean) {
     }
 
-    override fun initNormalStateDisplay(audioOnly: Boolean, muted: Boolean) {
+    override fun initNormalStateDisplay(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
+            contactBubbleLayout.visibility = View.VISIBLE
         }
         requireActivity().invalidateOptionsMenu()
         handleVisibilityTimer()
@@ -492,31 +497,71 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
     }
 
     @SuppressLint("RestrictedApi")
-    override fun updateConfInfo(info: List<ParticipantInfo>) {
-        val binding = binding!!
+    override fun updateConfInfo(participantInfo: List<ParticipantInfo>) {
+        val binding = binding ?: return
+        mConferenceMode = participantInfo.size > 1
         binding.participantLabelContainer.removeAllViews()
-        if (info.isNotEmpty()) {
+        if (participantInfo.isNotEmpty()) {
+            val username = if (participantInfo.size > 1)
+                "Conference with ${participantInfo.size} people"
+            else participantInfo[0].contact.displayName
+            val displayName = if (participantInfo.size > 1) null else participantInfo[0].contact.displayName
+            val hasProfileName = displayName != null && !displayName.contentEquals(username)
+            val call = participantInfo[0].call
+            if (call != null) {
+                val conversationUri = if (call.conversationId != null)
+                    Uri(Uri.SWARM_SCHEME, call.conversationId!!)
+                else call.contact!!.conversationUri.blockingFirst()
+                activity?.let { activity ->
+                    activity.intent = Intent(Intent.ACTION_VIEW,
+                        ConversationPath.toUri(call.account!!, conversationUri), context, TVCallActivity::class.java)
+                        .apply { putExtra(NotificationService.KEY_CALL_ID, call.confId ?: call.daemonIdString) }
+                }
+                arguments = Bundle().apply {
+                    putString(CallFragment.KEY_ACTION, Intent.ACTION_VIEW)
+                    putString(NotificationService.KEY_CALL_ID, call.confId ?: call.daemonIdString)
+                }
+            }
+            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(participantInfo[0].contact)
+                .withCircleCrop(true)
+                .withPresence(false)
+                .build(requireActivity()))
+
             val inflater = LayoutInflater.from(binding.participantLabelContainer.context)
-            for (i in info) {
+            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 (info.isEmpty()) View.GONE else View.VISIBLE
+        binding.participantLabelContainer.visibility = if (participantInfo.isEmpty()) View.GONE else View.VISIBLE
         if (!mConferenceMode) {
-            binding.confControlGroup!!.visibility = View.GONE
+            binding.confControlGroup.visibility = View.GONE
         } else {
-            binding.confControlGroup!!.visibility = View.VISIBLE
-            confAdapter?.apply { updateFromCalls(info) }
+            binding.confControlGroup.visibility = View.VISIBLE
+            confAdapter?.apply { updateFromCalls(participantInfo) }
             // Create new adapter
-                ?: ConfParticipantAdapter(info, object : ConfParticipantSelected {
+                ?: ConfParticipantAdapter(participantInfo, object : ConfParticipantSelected {
                     override fun onParticipantSelected(view: View, contact: ParticipantInfo) {
                         val maximized = presenter.isMaximized(contact)
                         val popup = PopupMenu(view.context, view)
@@ -594,7 +639,7 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
     override fun startAddParticipant(conferenceId: String) {
         startActivityForResult(Intent(Intent.ACTION_PICK)
                 .setClass(requireActivity(), ConversationSelectionActivity::class.java)
-                .putExtra(CallFragment.KEY_CONF_ID, conferenceId),
+                .putExtra(NotificationService.KEY_CALL_ID, conferenceId),
             REQUEST_CODE_ADD_PARTICIPANT)
     }
 
@@ -690,7 +735,7 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
         fun newInstance(action: String, confId: String?): TVCallFragment {
             return TVCallFragment().apply { arguments = Bundle().apply {
                 putString(CallFragment.KEY_ACTION, action)
-                putString(CallFragment.KEY_CONF_ID, confId)
+                putString(NotificationService.KEY_CALL_ID, confId)
             }}
         }
     }
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 a1e71e2f9..2d45aecaa 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
@@ -18,10 +18,10 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools">
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".tv.call.TVCallActivity">
 
     <data>
-
         <variable
             name="presenter"
             type="cx.ring.tv.call.TVCallFragment" />
@@ -33,7 +33,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
         android:clipChildren="false"
         android:clipToPadding="false"
         android:keepScreenOn="true"
-        tools:context=".tv.call.TVCallActivity">
+        tools:background="@color/cardview_dark_background">
 
         <SurfaceView
             android:id="@+id/video_surface"
@@ -74,25 +74,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
                 tools:visibility="visible" />
         </androidx.cardview.widget.CardView>
 
-        <androidx.cardview.widget.CardView
-            android:id="@+id/plugin_preview_container"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentBottom="true"
-            android:layout_margin="12dp"
-            android:visibility="gone"
-            app:cardCornerRadius="16dp"
-            app:cardPreventCornerOverlap="false">
-
-            <cx.ring.views.AutoFitTextureView
-                android:id="@+id/plugin_preview_surface"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="visible"
-                tools:visibility="visible" />
-        </androidx.cardview.widget.CardView>
-
         <LinearLayout
             android:id="@+id/contact_bubble_layout"
             android:layout_width="match_parent"
@@ -207,6 +188,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
                         app:useCompatPadding="true" />
 
                 </LinearLayout>
+
             </LinearLayout>
         </LinearLayout>
 
@@ -216,7 +198,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
             android:layout_centerHorizontal="true"
-            android:layout_margin="16dp"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
             android:layout_marginBottom="48dp"
             android:onClick="@{() -> presenter.hangUpClicked()}"
             android:tint="@color/white"
@@ -224,25 +208,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
             app:backgroundTint="@color/colorError"
             app:rippleColor="@android:color/white"
             app:srcCompat="@drawable/baseline_call_end_24"
-            app:useCompatPadding="true" />
-
-        <com.google.android.material.floatingactionbutton.FloatingActionButton
-            android:id="@+id/call_add_btn"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_toRightOf="@+id/call_hangup_btn"
-            android:layout_toEndOf="@+id/call_hangup_btn"
-            android:layout_alignParentBottom="true"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="16dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:onClick="@{() -> presenter.addParticipant()}"
-            android:tint="@color/white"
-            android:visibility="gone"
-            app:backgroundTint="@color/blue_400"
-            app:rippleColor="@android:color/white"
-            app:srcCompat="@drawable/baseline_person_add_24"
             app:useCompatPadding="true"
             tools:visibility="visible" />
 
@@ -274,5 +239,32 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
         </LinearLayout>
 
+        <com.google.android.material.floatingactionbutton.FloatingActionButton
+            android:id="@+id/call_add_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@+id/call_hangup_btn"
+            android:layout_toEndOf="@+id/call_hangup_btn"
+            android:layout_alignParentBottom="true"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
+            android:layout_marginBottom="48dp"
+            android:onClick="@{() -> presenter.addParticipant()}"
+            android:visibility="gone"
+            app:srcCompat="@drawable/baseline_person_add_24"
+            app:useCompatPadding="true"
+            tools:visibility="visible" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/conf_control_group"
+            android:layout_alignParentEnd="true"
+            android:layout_alignParentTop="true"
+            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+            tools:listitem="@layout/item_conference_participant"
+            tools:itemCount="4" />
+
     </RelativeLayout>
-</layout>
\ No newline at end of file
+</layout>
diff --git a/ring-android/app/src/main/res/values-fr-rFR/strings.xml b/ring-android/app/src/main/res/values-fr-rFR/strings.xml
index f2beb2ba2..fc7dc1afe 100644
--- a/ring-android/app/src/main/res/values-fr-rFR/strings.xml
+++ b/ring-android/app/src/main/res/values-fr-rFR/strings.xml
@@ -97,7 +97,8 @@
   <string name="action_call_accept">Prendre un appel</string>
   <string name="action_call_accept_audio">Répondre en audio</string>
   <string name="action_call_accept_video">Répondre en video</string>
-  <string name="action_call_hold_accept">Mettre en attente et prendre l\'appel</string>
+  <string name="action_call_hold_accept">Répondre en audio</string>
+  <string name="action_call_hold_accept_video">Répondre en video</string>
   <string name="action_call_end_accept">Terminer et prendre l\'appel</string>
   <string name="action_call_decline">Refuser</string>
   <string name="action_call_mic_mute">Couper le micro</string>
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index 03483258b..d714c9f62 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -152,7 +152,8 @@ along with this program; if not, write to the Free Software
     <string name="action_call_accept">Take call</string>
     <string name="action_call_accept_audio">Answer in audio</string>
     <string name="action_call_accept_video">Answer in video</string>
-    <string name="action_call_hold_accept">Hold and take call</string>
+    <string name="action_call_hold_accept">Hold and take call audio</string>
+    <string name="action_call_hold_accept_video">Hold and take call video</string>
     <string name="action_call_end_accept">End and take call</string>
     <string name="action_call_decline">Decline</string>
     <string name="action_call_mic_mute">Mute microphone</string>
diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
index b12a0c7ef..256d4368b 100644
--- a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
+++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
@@ -189,6 +189,7 @@ class CallPresenter @Inject constructor(
     }
 
     fun prepareOptionMenu() {
+        Log.w(TAG, "DEBUG prepareOptionMenu")
         val isSpeakerOn: Boolean = mHardwareService.isSpeakerphoneOn
         //boolean hasContact = mSipCall != null && null != mSipCall.getContact() && mSipCall.getContact().isUnknown();
         val conference = mConference
@@ -280,7 +281,6 @@ class CallPresenter @Inject constructor(
             currentSurfaceId = newId
         }
         mHardwareService.addVideoSurface(conference.id, holder)
-        view?.displayContactBubble(false)
     }
 
     private fun videoSurfaceUpdateId(newId: String) {
@@ -356,7 +356,7 @@ class CallPresenter @Inject constructor(
 
     private fun confUpdate(call: Conference) {
         mConference = call
-        Log.w(TAG, "confUpdate " + call.id + " " + call.state)
+        Log.w(TAG, "DEBUG 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)
@@ -364,23 +364,28 @@ class CallPresenter @Inject constructor(
         val hasVideo = call.hasVideo()
         val hasActiveVideo = call.hasActiveVideo()
         videoIsMuted = !hasActiveVideo
+
         val view = view ?: return
         view.updateMenu()
+
         if (call.isOnGoing) {
             mOnGoingCall = true
-            view.initNormalStateDisplay(!hasVideo, isMicrophoneMuted)
-            view.updateMenu()
+            view.initNormalStateDisplay(isMicrophoneMuted)
             if (hasVideo) {
                 mHardwareService.setPreviewSettings()
                 mHardwareService.updatePreviewVideoSurface(call)
                 videoSurfaceUpdateId(call.id)
                 pluginSurfaceUpdateId(call.pluginId)
-                view.displayVideoSurface(true, hasActiveVideo && mDeviceRuntimeService.hasVideoPermission())
+                //view.displayPeerVideo(hasVideo)
+                view.displayLocalVideo(hasActiveVideo && mDeviceRuntimeService.hasVideoPermission())
                 if (permissionChanged) {
                     mHardwareService.switchInput(call.id, permissionChanged)
                     permissionChanged = false
                 }
             }
+            if (mHardwareService.hasInput(call.id)) {
+                view.displayPeerVideo(true)
+            }
             timeUpdateTask?.dispose()
             timeUpdateTask = mUiScheduler.schedulePeriodicallyDirect({ updateTime() }, 0, 1, TimeUnit.SECONDS)
         } else if (call.isRinging) {
@@ -388,7 +393,7 @@ class CallPresenter @Inject constructor(
             val scall = call.call!!
             view.handleCallWakelock(!hasVideo)
             if (scall.isIncoming) {
-                if (mAccountService.getAccount(scall.account!!)!!.isAutoanswerEnabled) {
+                if (mAccountService.getAccount(scall.account)?.isAutoanswerEnabled == true) {
                     Log.w(TAG, "Accept because of autoanswer")
                     mCallService.accept(scall.daemonIdString!!, wantVideo)
                     // only display the incoming call screen if the notification is a full screen intent
@@ -431,41 +436,38 @@ class CallPresenter @Inject constructor(
     }
 
     private fun onVideoEvent(event: VideoEvent) {
-        Log.w(TAG, "DEBUG fn onVideoEvent")
-        Log.d(TAG, "VIDEO_EVENT: " + event.start + " " + event.callId + " " + event.w + "x" + event.h)
+        Log.w(TAG, "DEBUG onVideoEvent  $event")
         val view = view ?: return
         val conference = mConference
-        if (event.start) {
-            Log.w(TAG, "DEBUG fn onVideoEvent |1| inside => if (event.start) ")
-            view.displayVideoSurface(true, !isPipMode && mDeviceRuntimeService.hasVideoPermission() && conference?.hasActiveVideo() == true)
-        } else if (conference != null && conference.id == event.callId) {
-            Log.w(TAG, "DEBUG fn onVideoEvent |2| inside => else if (mConference != null && mConference!!.id == event.callId)")
-            Log.w(TAG, "DEBUG fn onVideoEvent |2| inside =>  event.started ${event.started} && !isPipMode ${!isPipMode} && mDeviceRuntimeService.hasVideoPermission() ${mDeviceRuntimeService.hasVideoPermission()} && hasVideo $wantVideo && !videoIsMuted ${!videoIsMuted}")
-            view.displayVideoSurface(event.started,
-                event.started && !isPipMode && mDeviceRuntimeService.hasVideoPermission() && conference.hasActiveVideo())
-            if (event.started) {
-                videoWidth = event.w
-                videoHeight = event.h
-                view.resetVideoSize(videoWidth, videoHeight)
+        if (event.callId == null) {
+            Log.w(TAG, "DEBUG onVideoEvent  $event; callId == null; event.start: ${event.start}, event.started: ${event.started}")
+            if (event.start) {
+                view.displayLocalVideo(true)
             }
-        } else if (event.callId == null) {
-            Log.w(TAG, "DEBUG fn onVideoEvent |3| inside => else if (event.callId == null)")
             if (event.started) {
                 previewWidth = event.w
                 previewHeight = event.h
                 view.resetPreviewVideoSize(previewWidth, previewHeight, event.rot)
             }
+        } else if (conference != null && conference.id == event.callId) {
+            Log.w(TAG, "DEBUG onVideoEvent  $event; onference != null && conference.id: ${conference.id} == event.callId: ${event.callId}; event.start: ${event.start}, event.started: ${event.started}")
+            if (event.start) {
+                view.displayPeerVideo(true)
+            } else if (event.started) {
+                videoWidth = event.w
+                videoHeight = event.h
+                view.resetVideoSize(videoWidth, videoHeight)
+            } else {
+                view.displayPeerVideo(false)
+            }
         }
-        if (conference != null && conference.pluginId == event.callId) {
+        /*if (conference != null && conference.pluginId == event.callId) {
             Log.w(TAG, "DEBUG fn onVideoEvent |4| inside => 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);
         }*/
     }
 
@@ -502,16 +504,16 @@ class CallPresenter @Inject constructor(
     }
 
     fun pipModeChanged(pip: Boolean) {
+        Log.w(TAG, "DEBUG fn pipModeChanged $pip")
         isPipMode = pip
         if (pip) {
-            Log.w(TAG, "DEBUG fn pipModeChanged |1| entering pipMode -> hangupButton : false; previewSurface: false; displayVideoSurface(true, false)")
             view!!.displayHangupButton(false)
             view!!.displayPreviewSurface(false)
-            view!!.displayVideoSurface(true, false)
+            //view!!.displayPeerVideo(true, false)
         } else {
-            Log.w(TAG, "DEBUG fn pipModeChanged |2| entering pipMode -> previewSurface: true; displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission() && hasVideo && !videoIsMuted)")
+            //Log.w(TAG, "DEBUG fn pipModeChanged |2| entering pipMode -> previewSurface: true; displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission() && hasVideo && !videoIsMuted)")
             view!!.displayPreviewSurface(true)
-            view!!.displayVideoSurface(true, mDeviceRuntimeService.hasVideoPermission())
+            //view!!.displayPeerVideo(true, mDeviceRuntimeService.hasVideoPermission())
         }
     }
 
diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
index 0f3905884..78c0921b4 100644
--- a/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
+++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
@@ -27,7 +27,8 @@ import net.jami.services.HardwareService.AudioState
 
 interface CallView {
     fun displayContactBubble(display: Boolean)
-    fun displayVideoSurface(displayVideoSurface: Boolean, displayPreviewContainer: Boolean)
+    fun displayPeerVideo(display: Boolean)
+    fun displayLocalVideo(display: Boolean)
     fun displayPreviewSurface(display: Boolean)
     fun displayHangupButton(display: Boolean)
     fun displayDialPadKeyboard()
@@ -37,7 +38,7 @@ interface CallView {
     fun updateTime(duration: Long)
     fun updateCallStatus(callState: CallStatus)
     fun initMenu(isSpeakerOn: Boolean, hasMultipleCamera: Boolean, canDial: Boolean, showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveVideo: Boolean)
-    fun initNormalStateDisplay(audioOnly: Boolean, muted: Boolean)
+    fun initNormalStateDisplay(muted: Boolean)
     fun initIncomingCallDisplay(hasVideo: Boolean)
     fun initOutGoingCallDisplay()
     fun resetPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int)
diff --git a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt
index 7345ec96f..8fd4043cc 100644
--- a/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt
+++ b/ring-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt
@@ -43,14 +43,14 @@ abstract class HardwareService(
     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
-    }
+    data class VideoEvent (
+        val callId: String? = null,
+        val start: Boolean = false,
+        val started: Boolean = false,
+        val w: Int = 0,
+        val h: Int = 0,
+        val rot: Int = 0
+    )
 
     class BluetoothEvent (val connected: Boolean)
 
@@ -90,6 +90,7 @@ abstract class HardwareService(
     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 hasInput(id: String): 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?)
-- 
GitLab