diff --git a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt index c035767cbb..9f801d1acf 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt @@ -567,7 +567,7 @@ class WebRtcPeerConnectionManager @Inject constructor( } } - fun startOutgoingCall(context: Context, signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) { + fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) { executor.execute { if (peerConnectionFactory == null) { createPeerConnectionFactory() @@ -584,7 +584,7 @@ class WebRtcPeerConnectionManager @Inject constructor( val name = sessionHolder.getSafeActiveSession()?.getUser(createdCall.otherUserId)?.getBestName() ?: createdCall.otherUserId CallService.onOutgoingCallRinging( - context = context, + context = context.applicationContext, isVideo = createdCall.isVideoCall, roomName = name, roomId = createdCall.roomId, @@ -596,7 +596,7 @@ class WebRtcPeerConnectionManager @Inject constructor( } // start the activity now - context.startActivity(VectorCallActivity.newIntent(context, createdCall)) + context.applicationContext.startActivity(VectorCallActivity.newIntent(context, createdCall)) } override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 896b29009e..271917c827 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -68,7 +68,7 @@ sealed class RoomDetailAction : VectorViewModelAction { object ClearSendQueue : RoomDetailAction() object ResendAll : RoomDetailAction() - object StartCall : RoomDetailAction() + data class StartCall(val isVideo: Boolean) : RoomDetailAction() data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction() data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0f8c98128f..8aded52717 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -107,6 +107,8 @@ import im.vector.riotx.core.ui.views.JumpToReadMarkerView import im.vector.riotx.core.ui.views.NotificationAreaView import im.vector.riotx.core.utils.Debouncer import im.vector.riotx.core.utils.KeyboardStateUtils +import im.vector.riotx.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL +import im.vector.riotx.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_DOWNLOAD_FILE import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_INCOMING_URI @@ -120,6 +122,8 @@ import im.vector.riotx.core.utils.createJSonViewerStyleProvider import im.vector.riotx.core.utils.createUIHandler import im.vector.riotx.core.utils.getColorFromUserId import im.vector.riotx.core.utils.isValidUrl +import im.vector.riotx.core.utils.onPermissionResultAudioIpCall +import im.vector.riotx.core.utils.onPermissionResultVideoIpCall import im.vector.riotx.core.utils.openUrlInExternalBrowser import im.vector.riotx.core.utils.saveMedia import im.vector.riotx.core.utils.shareMedia @@ -132,7 +136,6 @@ import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs import im.vector.riotx.features.attachments.toGroupedContentAttachmentData import im.vector.riotx.features.call.SharedActiveCallViewModel import im.vector.riotx.features.call.VectorCallActivity -import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.command.Command import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.riotx.features.crypto.util.toImageRes @@ -202,8 +205,7 @@ class RoomDetailFragment @Inject constructor( val roomDetailViewModelFactory: RoomDetailViewModel.Factory, private val eventHtmlRenderer: EventHtmlRenderer, private val vectorPreferences: VectorPreferences, - private val colorProvider: ColorProvider, - private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) : + private val colorProvider: ColorProvider) : VectorBaseFragment(), TimelineEventController.Callback, VectorInviteView.Callback, @@ -215,6 +217,9 @@ class RoomDetailFragment @Inject constructor( companion object { + private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1 + private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2 + /** * Sanitize the display name. * @@ -503,22 +508,22 @@ class RoomDetailFragment @Inject constructor( } R.id.voice_call, R.id.video_call -> { - roomDetailViewModel.getOtherUserIds()?.firstOrNull()?.let { // TODO CALL We should check/ask for permission here first val activeCall = sharedCallActionViewModel.activeCall.value + val isVideoCall = item.itemId == R.id.video_call if (activeCall != null) { // resume existing if same room, if not prompt to kill and then restart new call? if (activeCall.roomId == roomDetailArgs.roomId) { onTapToReturnToCall() - } else { - // TODO might not work well, and should prompt - webRtcPeerConnectionManager.endCall() - webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call) } +// else { + // TODO might not work well, and should prompt +// webRtcPeerConnectionManager.endCall() +// safeStartCall(it, isVideoCall) +// } } else { - webRtcPeerConnectionManager.startOutgoingCall(requireContext(), roomDetailArgs.roomId, it, item.itemId == R.id.video_call) + safeStartCall(isVideoCall) } - } true } else -> super.onOptionsItemSelected(item) @@ -536,6 +541,22 @@ class RoomDetailFragment @Inject constructor( .show() } + private fun safeStartCall(isVideoCall: Boolean) { + val startCallAction = RoomDetailAction.StartCall(isVideoCall) + roomDetailViewModel.pendingAction = startCallAction + if (isVideoCall) { + if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, VIDEO_CALL_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_camera_and_audio)) { + roomDetailViewModel.pendingAction = null + roomDetailViewModel.handle(startCallAction) + } + } else { + if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, AUDIO_CALL_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_record_audio)) { + roomDetailViewModel.pendingAction = null + roomDetailViewModel.handle(startCallAction) + } + } + } + private fun renderRegularMode(text: String) { autoCompleter.exitSpecialMode() composerLayout.collapse() @@ -1130,6 +1151,22 @@ class RoomDetailFragment @Inject constructor( launchAttachmentProcess(pendingType) } } + AUDIO_CALL_PERMISSION_REQUEST_CODE -> { + if (onPermissionResultAudioIpCall(requireContext(), grantResults)) { + (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let { + roomDetailViewModel.pendingAction = null + roomDetailViewModel.handle(it) + } + } + } + VIDEO_CALL_PERMISSION_REQUEST_CODE -> { + if (onPermissionResultVideoIpCall(requireContext(), grantResults)) { + (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let { + roomDetailViewModel.pendingAction = null + roomDetailViewModel.handle(it) + } + } + } } } else { // Reset all pending data diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 0894fbf004..dbbe35592f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -67,6 +67,7 @@ import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.utils.subscribeLogError +import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider @@ -97,7 +98,8 @@ class RoomDetailViewModel @AssistedInject constructor( private val rainbowGenerator: RainbowGenerator, private val session: Session, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, - private val stickerPickerActionHandler: StickerPickerActionHandler + private val stickerPickerActionHandler: StickerPickerActionHandler, + private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager ) : VectorViewModel(initialState), Timeline.Listener { private val room = session.getRoom(initialState.roomId)!! @@ -255,13 +257,20 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - } + is RoomDetailAction.StartCall -> handleStartCall(action) + }.exhaustive } private fun handleSendSticker(action: RoomDetailAction.SendSticker) { room.sendEvent(EventType.STICKER, action.stickerContent.toContent()) } + private fun handleStartCall(action: RoomDetailAction.StartCall) { + room.roomSummary()?.otherMemberIds?.firstOrNull()?.let { + webRtcPeerConnectionManager.startOutgoingCall(room.roomId, it, action.isVideo) + } + } + private fun handleSelectStickerAttachment() { viewModelScope.launch { val viewEvent = stickerPickerActionHandler.handle() @@ -369,7 +378,7 @@ class RoomDetailViewModel @AssistedInject constructor( } fun isMenuItemVisible(@IdRes itemId: Int) = when (itemId) { - R.id.clear_message_queue -> + R.id.clear_message_queue -> // For now always disable when not in developer mode, worker cancellation is not working properly timeline.pendingEventCount() > 0 && vectorPreferences.developerMode() R.id.resend_all -> timeline.failedToDeliverEventCount() > 0