From e085aa14ed4e0d686a7965d9780e7b46b3119807 Mon Sep 17 00:00:00 2001
From: Alexander Lussier-Cullen <alexander.lussier-cullen@savoirfairelinux.com>
Date: Wed, 20 Dec 2023 13:20:24 -0500
Subject: [PATCH] call: fix camera and screenshare states

Add distinct logic and states for handling camera & screenshare video to solve related button state and video switching issues.

GitLab: #1075

Change-Id: Id693cd17d6594e561ad4901a1670bb3a6620fc5b
---
 .../java/cx/ring/fragments/CallFragment.kt    | 22 +++++------
 .../java/cx/ring/tv/call/TVCallFragment.kt    |  7 +++-
 .../kotlin/net/jami/call/CallPresenter.kt     | 37 ++++++++++---------
 .../src/main/kotlin/net/jami/call/CallView.kt |  3 +-
 .../main/kotlin/net/jami/model/Conference.kt  | 12 ++++++
 5 files changed, 51 insertions(+), 30 deletions(-)

diff --git a/jami-android/app/src/main/java/cx/ring/fragments/CallFragment.kt b/jami-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
index ca92ca757..735b0693f 100644
--- a/jami-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/fragments/CallFragment.kt
@@ -948,7 +948,8 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
         canDial: Boolean,
         showPluginBtn: Boolean,
         onGoingCall: Boolean,
-        hasActiveVideo: Boolean
+        hasActiveCameraVideo: Boolean,
+        hasActiveScreenShare: Boolean
     ) {
         binding?.apply {
             pluginsBtnContainer.isVisible = showPluginBtn
@@ -957,13 +958,14 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
             dialpadBtnContainer.isVisible = canDial
 
             callVideocamBtn.apply {
-                isChecked = !hasActiveVideo
+                isChecked = !hasActiveCameraVideo
                 setImageResource(if (isChecked) R.drawable.baseline_videocam_off_24 else R.drawable.baseline_videocam_on_24)
             }
             callCameraFlipBtn.apply {
                 isEnabled = !callVideocamBtn.isChecked
-                setImageResource(if (hasMultipleCamera && hasActiveVideo) R.drawable.baseline_flip_camera_24 else R.drawable.baseline_flip_camera_24_off)
+                setImageResource(if (hasMultipleCamera && hasActiveCameraVideo) R.drawable.baseline_flip_camera_24 else R.drawable.baseline_flip_camera_24_off)
             }
+            callSharescreenBtn.isChecked = hasActiveScreenShare
             callMicBtn.isChecked = isMicrophoneMuted
         }
     }
@@ -1267,15 +1269,13 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView,
                 .show()
         }
     }
-    
+
+    override fun startScreenCapture() {
+        startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_SHARE)
+    }
+
     fun shareScreenClicked() {
-        val binding = binding ?: return
-        if (!binding.callSharescreenBtn.isChecked) {
-            presenter.stopScreenShare()
-            displayLocalVideo(true)
-        } else {
-            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE_SCREEN_SHARE)
-        }
+        presenter.switchOnOffScreenShare()
     }
 
     fun micClicked() {
diff --git a/jami-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
index 822488a78..810d49cc0 100644
--- a/jami-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
+++ b/jami-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt
@@ -285,7 +285,8 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
         canDial: Boolean,
         showPluginBtn: Boolean,
         onGoingCall: Boolean,
-        hasActiveVideo: Boolean) {
+        hasActiveVideo: Boolean,
+        hasActiveScreenShare: Boolean) {
     }
 
     override fun resetBottomSheetState() {}
@@ -517,6 +518,10 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView
         startActivityForResult(ActionHelper.getAddNumberIntentForContact(contact), ConversationFragment.REQ_ADD_CONTACT)
     }
 
+    override fun startScreenCapture() {
+        TODO("Not yet implemented")
+    }
+
     override fun startAddParticipant(conferenceId: String) {
         startActivityForResult(Intent(Intent.ACTION_PICK)
                 .setClass(requireActivity(), ConversationSelectionActivity::class.java)
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
index c986deab3..e1277f5d8 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallPresenter.kt
@@ -59,8 +59,6 @@ class CallPresenter @Inject constructor(
     fun isSpeakerphoneOn(): Boolean = mHardwareService.isSpeakerphoneOn()
     var isMicrophoneMuted: Boolean = false
     var wantVideo = false
-    var videoIsMuted = false
-        private set
 
     fun isVideoActive(): Boolean = mConference?.hasActiveVideo() == true
 
@@ -207,10 +205,11 @@ class CallPresenter @Inject constructor(
         val canDial = mOnGoingCall
         val displayPluginsButton = view?.displayPluginsButton() == true
         val showPluginBtn = displayPluginsButton && mOnGoingCall
-        val hasActiveVideo = conference.hasActiveVideo()
-        val hasMultipleCamera = mHardwareService.cameraCount() > 1 && mOnGoingCall && hasActiveVideo
+        val hasActiveCameraVideo = conference.hasActiveNonScreenShareVideo()
+        val hasActiveScreenShare = conference.hasActiveScreenSharing()
+        val hasMultipleCamera = mHardwareService.cameraCount() > 1 && mOnGoingCall && hasActiveCameraVideo
         val isConference = conference.isConference
-        view?.updateBottomSheetButtonStatus(isConference, isSpeakerphoneOn(), conference.isAudioMuted, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall, hasActiveVideo)
+        view?.updateBottomSheetButtonStatus(isConference, isSpeakerphoneOn(), conference.isAudioMuted, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall, hasActiveCameraVideo, hasActiveScreenShare)
     }
 
     fun chatClick() {
@@ -255,13 +254,16 @@ class CallPresenter @Inject constructor(
 
     fun switchVideoInputClick() {
         val conference = mConference ?: return
-        mHardwareService.switchInput(conference.accountId, conference.id)
+        if(conference.hasActiveNonScreenShareVideo())
+            mHardwareService.switchInput(conference.accountId, conference.id)
     }
 
     fun switchOnOffCamera() {
         val conference = mConference ?: return
-        videoIsMuted = !videoIsMuted
-        mCallService.requestVideoMedia(conference, !videoIsMuted)
+        if(conference.hasActiveScreenSharing())
+            mHardwareService.switchInput(conference.accountId, conference.id, true)
+        else
+            mCallService.requestVideoMedia(conference, !conference.hasActiveNonScreenShareVideo())
     }
 
     fun configurationChanged(rotation: Int) {
@@ -399,9 +401,7 @@ class CallPresenter @Inject constructor(
             if (call.isSimpleCall) mCallService.unhold(call.accountId, call.id) else JamiService.addMainParticipant(call.accountId, call.id)
         }
         val hasVideo = call.hasVideo()
-        val hasActiveVideo = call.hasActiveVideo()
-        val hasActiveScreenShare = call.hasActiveScreenSharing()
-        videoIsMuted = !hasActiveVideo
+        val hasActiveCameraVideo = call.hasActiveNonScreenShareVideo()
         val view = view ?: return
         if (call.isOnGoing) {
             mOnGoingCall = true
@@ -412,7 +412,7 @@ class CallPresenter @Inject constructor(
                 mHardwareService.updatePreviewVideoSurface(call)
                 videoSurfaceUpdateId(call.id)
                 pluginSurfaceUpdateId(call.pluginId)
-                view.displayLocalVideo(hasActiveVideo && !hasActiveScreenShare && mDeviceRuntimeService.hasVideoPermission())
+                view.displayLocalVideo(hasActiveCameraVideo && mDeviceRuntimeService.hasVideoPermission())
                 if (permissionChanged) {
                     mHardwareService.switchInput(call.accountId, call.id, permissionChanged)
                     permissionChanged = false
@@ -641,6 +641,14 @@ class CallPresenter @Inject constructor(
         mCallService.raiseHand(call.accountId, call.id, mAccountService.getAccount(call.accountId)?.uri!!, state, getDeviceId())
     }
 
+    fun switchOnOffScreenShare() {
+        val conference = mConference ?: return
+        if(conference.hasActiveScreenSharing())
+            mHardwareService.switchInput(conference.accountId, conference.id, true)
+        else
+            view?.startScreenCapture()
+    }
+
     fun startScreenShare(resultCode: Int, data: Any): Boolean {
         val conference = mConference ?: return false
         mNotificationService.preparePendingScreenshare(conference) {
@@ -650,11 +658,6 @@ class CallPresenter @Inject constructor(
         return true
     }
 
-    fun stopScreenShare() {
-        val conference = mConference ?: return
-        mHardwareService.switchInput(conference.accountId, conference.id, true)
-    }
-
     fun isMaximized(info: ParticipantInfo): Boolean {
         return mConference?.maximizedParticipant == info.contact.contact
     }
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
index 1800b0498..d04d5b2e5 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/call/CallView.kt
@@ -30,7 +30,7 @@ interface CallView {
     fun updateAudioState(state: AudioState)
     fun updateTime(duration: Long)
     fun updateCallStatus(callState: CallStatus)
-    fun updateBottomSheetButtonStatus(isConference: Boolean, isSpeakerOn: Boolean, isMicrophoneMuted: Boolean, hasMultipleCamera: Boolean, canDial: Boolean, showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveVideo: Boolean)
+    fun updateBottomSheetButtonStatus(isConference: Boolean, isSpeakerOn: Boolean, isMicrophoneMuted: Boolean, hasMultipleCamera: Boolean, canDial: Boolean, showPluginBtn: Boolean, onGoingCall: Boolean, hasActiveCameraVideo: Boolean, hasActiveScreenShare: Boolean)
     fun resetBottomSheetState()
     fun initNormalStateDisplay()
     fun initIncomingCallDisplay(hasVideo: Boolean)
@@ -38,6 +38,7 @@ interface CallView {
     fun resetPreviewVideoSize(previewWidth: Int?, previewHeight: Int?, rot: Int)
     fun goToConversation(accountId: String, conversationId: Uri)
     fun goToAddContact(contact: Contact)
+    fun startScreenCapture()
     fun startAddParticipant(conferenceId: String)
     fun finish()
     fun onUserLeave()
diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/model/Conference.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/model/Conference.kt
index 06454fed1..3579315f9 100644
--- a/jami-android/libjamiclient/src/main/kotlin/net/jami/model/Conference.kt
+++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/model/Conference.kt
@@ -190,6 +190,18 @@ class Conference(val accountId: String, val id: String) {
         return false
     }
 
+    fun hasActiveNonScreenShareVideo(): Boolean {
+        return mParticipants.any { call ->
+            val mediaList = call.mediaList ?: return@any false
+            mediaList.any { media ->
+                media.isEnabled &&
+                !media.isMuted &&
+                media.mediaType == Media.MediaType.MEDIA_TYPE_VIDEO &&
+                media.source != "camera://desktop"
+            }
+        }
+    }
+
     val timestampStart: Long
         get() {
             var t = Long.MAX_VALUE
-- 
GitLab