From 00bbb94b3be2592b3cc4eed625a18dc5b2b3a9fe Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 19 Apr 2022 17:42:38 +0300 Subject: [PATCH 1/8] Add screen sharing option to call menu. --- vector/src/main/res/drawable/ic_share_screen.xml | 10 ++++++++++ .../src/main/res/layout/bottom_sheet_call_controls.xml | 9 +++++++++ vector/src/main/res/values/strings.xml | 1 + 3 files changed, 20 insertions(+) create mode 100644 vector/src/main/res/drawable/ic_share_screen.xml diff --git a/vector/src/main/res/drawable/ic_share_screen.xml b/vector/src/main/res/drawable/ic_share_screen.xml new file mode 100644 index 0000000000..5e3caa6987 --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_screen.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/bottom_sheet_call_controls.xml b/vector/src/main/res/layout/bottom_sheet_call_controls.xml index e751ac412a..0b67059dd8 100644 --- a/vector/src/main/res/layout/bottom_sheet_call_controls.xml +++ b/vector/src/main/res/layout/bottom_sheet_call_controls.xml @@ -7,6 +7,15 @@ android:background="?colorSurface" android:orientation="vertical"> + + Back Turn HD off Turn HD on + Share screen Send files Send sticker From 99cab794c4221dec6a022f4bfa229c4132b2d215 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 19 Apr 2022 17:47:06 +0300 Subject: [PATCH 2/8] Show screen sharing permission dialog. --- .../features/call/CallControlsBottomSheet.kt | 5 +++ .../app/features/call/VectorCallActivity.kt | 24 ++++++++--- .../features/call/VectorCallViewActions.kt | 2 + .../app/features/call/VectorCallViewEvents.kt | 1 + .../app/features/call/VectorCallViewModel.kt | 40 +++++++++++-------- .../app/features/call/webrtc/WebRtcCall.kt | 4 ++ .../features/navigation/DefaultNavigator.kt | 5 +++ .../app/features/navigation/Navigator.kt | 5 +++ 8 files changed, 65 insertions(+), 21 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index e38b53c858..32d32c9fa6 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -66,6 +66,11 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment(), CallContro private fun handleViewEvents(event: VectorCallViewEvents?) { Timber.tag(loggerTag.value).v("handleViewEvents $event") when (event) { - is VectorCallViewEvents.ConnectionTimeout -> { + is VectorCallViewEvents.ConnectionTimeout -> { onErrorTimoutConnect(event.turn) } - is VectorCallViewEvents.ShowDialPad -> { + is VectorCallViewEvents.ShowDialPad -> { CallDialPadBottomSheet.newInstance(false).apply { callback = dialPadCallback }.show(supportFragmentManager, FRAGMENT_DIAL_PAD_TAG) } - is VectorCallViewEvents.ShowCallTransferScreen -> { + is VectorCallViewEvents.ShowCallTransferScreen -> { val callId = withState(callViewModel) { it.callId } navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId) } - is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) - else -> Unit + is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) + is VectorCallViewEvents.ShowScreenSharingPermissionDialog -> handleShowScreenSharingPermissionDialog() + else -> Unit } } @@ -628,6 +630,18 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } } + private val screenSharingPermissionActivityResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + callViewModel.handle(VectorCallViewActions.StartScreenSharing) + } + } + + private fun handleShowScreenSharingPermissionDialog() { + getSystemService()?.let { + navigator.openScreenSharingPermissionDialog(it.createScreenCaptureIntent(), screenSharingPermissionActivityResultLauncher) + } + } + companion object { private const val EXTRA_MODE = "EXTRA_MODE" private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG" diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index d1ed961814..daad8a2332 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -40,4 +40,6 @@ sealed class VectorCallViewActions : VectorViewModelAction { object CallTransferSelectionCancelled : VectorCallViewActions() data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions() object TransferCall : VectorCallViewActions() + object InitiateScreenSharing : VectorCallViewActions() + object StartScreenSharing : VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index 7c29d7eea3..a2b6fbaf92 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -30,6 +30,7 @@ sealed class VectorCallViewEvents : VectorViewEvents { object ShowDialPad : VectorCallViewEvents() object ShowCallTransferScreen : VectorCallViewEvents() object FailToTransfer : VectorCallViewEvents() + object ShowScreenSharingPermissionDialog : VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index b9bf578daa..4e622c4ab3 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -256,27 +256,27 @@ class VectorCallViewModel @AssistedInject constructor( override fun handle(action: VectorCallViewActions) = withState { state -> when (action) { - VectorCallViewActions.EndCall -> call?.endCall() - VectorCallViewActions.AcceptCall -> { + VectorCallViewActions.EndCall -> call?.endCall() + VectorCallViewActions.AcceptCall -> { setState { copy(callState = Loading()) } call?.acceptIncomingCall() } - VectorCallViewActions.DeclineCall -> { + VectorCallViewActions.DeclineCall -> { setState { copy(callState = Loading()) } call?.endCall() } - VectorCallViewActions.ToggleMute -> { + VectorCallViewActions.ToggleMute -> { val muted = state.isAudioMuted call?.muteCall(!muted) setState { copy(isAudioMuted = !muted) } } - VectorCallViewActions.ToggleVideo -> { + VectorCallViewActions.ToggleVideo -> { if (state.isVideoCall) { val videoEnabled = state.isVideoEnabled call?.enableVideo(!videoEnabled) @@ -286,19 +286,19 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleHoldResume -> { + VectorCallViewActions.ToggleHoldResume -> { val isRemoteOnHold = state.isRemoteOnHold call?.updateRemoteOnHold(!isRemoteOnHold) } - is VectorCallViewActions.ChangeAudioDevice -> { + is VectorCallViewActions.ChangeAudioDevice -> { callManager.audioManager.setAudioDevice(action.device) } - VectorCallViewActions.SwitchSoundDevice -> { + VectorCallViewActions.SwitchSoundDevice -> { _viewEvents.post( VectorCallViewEvents.ShowSoundDeviceChooser(state.availableDevices, state.device) ) } - VectorCallViewActions.HeadSetButtonPressed -> { + VectorCallViewActions.HeadSetButtonPressed -> { if (state.callState.invoke() is CallState.LocalRinging) { // accept call call?.acceptIncomingCall() @@ -309,20 +309,20 @@ class VectorCallViewModel @AssistedInject constructor( } Unit } - VectorCallViewActions.ToggleCamera -> { + VectorCallViewActions.ToggleCamera -> { call?.switchCamera() } - VectorCallViewActions.ToggleHDSD -> { + VectorCallViewActions.ToggleHDSD -> { if (!state.isVideoCall) return@withState call?.setCaptureFormat(if (state.isHD) CaptureFormat.SD else CaptureFormat.HD) } - VectorCallViewActions.OpenDialPad -> { + VectorCallViewActions.OpenDialPad -> { _viewEvents.post(VectorCallViewEvents.ShowDialPad) } - is VectorCallViewActions.SendDtmfDigit -> { + is VectorCallViewActions.SendDtmfDigit -> { call?.sendDtmfDigit(action.digit) } - VectorCallViewActions.InitiateCallTransfer -> { + VectorCallViewActions.InitiateCallTransfer -> { call?.updateRemoteOnHold(true) _viewEvents.post( VectorCallViewEvents.ShowCallTransferScreen @@ -334,13 +334,21 @@ class VectorCallViewModel @AssistedInject constructor( is VectorCallViewActions.CallTransferSelectionResult -> { handleCallTransferSelectionResult(action.callTransferResult) } - VectorCallViewActions.TransferCall -> { + VectorCallViewActions.TransferCall -> { handleCallTransfer() } - is VectorCallViewActions.SwitchCall -> { + is VectorCallViewActions.SwitchCall -> { setState { VectorCallViewState(action.callArgs) } setupCallWithCurrentState() } + is VectorCallViewActions.InitiateScreenSharing -> { + _viewEvents.post( + VectorCallViewEvents.ShowScreenSharingPermissionDialog + ) + } + is VectorCallViewActions.StartScreenSharing -> { + call?.shareScreen() + } } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 90088c8475..c744f317ab 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -770,6 +770,10 @@ class WebRtcCall( return currentCaptureFormat } + fun shareScreen() { + // TODO. Will be handled within the next PR. + } + private suspend fun release() { listeners.clear() mxCall.removeListener(this) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 6122cf1eee..864b5f76bb 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -600,4 +600,9 @@ class DefaultNavigator @Inject constructor( roomEncryptionTrustLevel = threadTimelineArgs.roomEncryptionTrustLevel ))) } + + override fun openScreenSharingPermissionDialog(screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher) { + activityResultLauncher.launch(screenCaptureIntent) + } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 310105bd95..41b5374168 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -168,4 +168,9 @@ interface Navigator { fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) + + fun openScreenSharingPermissionDialog( + screenCaptureIntent: Intent, + activityResultLauncher: ActivityResultLauncher + ) } From fa3476d6cdbf810e5ebf1413e1ae859579a75562 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 20 Apr 2022 13:33:35 +0300 Subject: [PATCH 3/8] Create a foreground service during screen sharing. --- vector/src/main/AndroidManifest.xml | 6 +++ .../app/features/call/VectorCallActivity.kt | 8 +++ .../call/webrtc/ScreenCaptureService.kt | 53 +++++++++++++++++++ .../notifications/NotificationUtils.kt | 14 +++++ vector/src/main/res/values/strings.xml | 4 ++ 5 files changed, 85 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index eada664216..20b7c4908a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -375,6 +375,12 @@ android:exported="false" android:foregroundServiceType="location" /> + + (), CallContro private val screenSharingPermissionActivityResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { callViewModel.handle(VectorCallViewActions.StartScreenSharing) + // We need to start a foreground service with a sticky notification during screen sharing + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ContextCompat.startForegroundService( + this, + Intent(this, ScreenCaptureService::class.java) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt new file mode 100644 index 0000000000..7626e8178c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.call.webrtc + +import android.content.Intent +import android.os.Binder +import android.os.IBinder +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.services.VectorService +import im.vector.app.features.notifications.NotificationUtils +import javax.inject.Inject + +@AndroidEntryPoint +class ScreenCaptureService : VectorService() { + + @Inject lateinit var notificationUtils: NotificationUtils + private val binder = LocalBinder() + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + // Show a sticky notification + val notificationId = System.currentTimeMillis().toInt() + val notification = notificationUtils.buildScreenSharingNotification() + startForeground(notificationId, notification) + + return START_STICKY + } + + override fun onBind(intent: Intent?): IBinder { + return binder + } + + fun stopService() { + stopSelf() + } + + inner class LocalBinder : Binder() { + fun getService(): ScreenCaptureService = this@ScreenCaptureService + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 161b58d53d..e44f42d5cd 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -535,6 +535,20 @@ class NotificationUtils @Inject constructor(private val context: Context, .build() } + /** + * Creates a notification that indicates the application is capturing the screen. + */ + fun buildScreenSharingNotification(): Notification { + return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) + .setContentTitle(stringProvider.getString(R.string.screen_sharing_notification_title)) + .setContentText(stringProvider.getString(R.string.screen_sharing_notification_description)) + .setSmallIcon(R.drawable.ic_share_screen) + .setColor(ThemeUtils.getColor(context, android.R.attr.colorPrimary)) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setContentIntent(buildOpenHomePendingIntentForSummary()) + .build() + } + fun buildDownloadFileNotification(uri: Uri, fileName: String, mimeType: String): Notification { return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID) .setGroup(stringProvider.getString(R.string.app_name)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 42f85832d4..4b8009d10b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3024,4 +3024,8 @@ Notify the whole room Users Room notification + + + ${app_name} Screen Sharing + Screen sharing is in progress From f9ee06cd2a5e78ab3bf4198f22991bf6f4fb97c4 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Apr 2022 11:23:25 +0300 Subject: [PATCH 4/8] Implement foreground service for Android Q and later. --- .../features/call/CallControlsBottomSheet.kt | 3 +- .../app/features/call/VectorCallActivity.kt | 10 ++++ .../features/call/VectorCallViewActions.kt | 2 +- .../app/features/call/VectorCallViewEvents.kt | 1 + .../app/features/call/VectorCallViewModel.kt | 36 ++++++++++--- .../app/features/call/VectorCallViewState.kt | 3 +- .../webrtc/ScreenCaptureServiceConnection.kt | 54 +++++++++++++++++++ .../app/features/call/webrtc/WebRtcCall.kt | 6 ++- .../res/layout/bottom_sheet_call_controls.xml | 2 +- vector/src/main/res/values/strings.xml | 3 +- 10 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index 32d32c9fa6..1c6ead33cc 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -68,7 +68,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment(), CallContro @Inject lateinit var callManager: WebRtcCallManager @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var screenCaptureServiceConnection: ScreenCaptureServiceConnection private val callViewModel: VectorCallViewModel by viewModel() @@ -528,6 +530,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure)) is VectorCallViewEvents.ShowScreenSharingPermissionDialog -> handleShowScreenSharingPermissionDialog() + is VectorCallViewEvents.StopScreenSharingService -> handleStopScreenSharingService() else -> Unit } } @@ -640,6 +643,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro this, Intent(this, ScreenCaptureService::class.java) ) + screenCaptureServiceConnection.bind() } } } @@ -650,6 +654,12 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } } + private fun handleStopScreenSharingService() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + screenCaptureServiceConnection.stopScreenCapturing() + } + } + companion object { private const val EXTRA_MODE = "EXTRA_MODE" private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG" diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt index daad8a2332..c84f733b9a 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewActions.kt @@ -40,6 +40,6 @@ sealed class VectorCallViewActions : VectorViewModelAction { object CallTransferSelectionCancelled : VectorCallViewActions() data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions() object TransferCall : VectorCallViewActions() - object InitiateScreenSharing : VectorCallViewActions() + object ToggleScreenSharing : VectorCallViewActions() object StartScreenSharing : VectorCallViewActions() } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index a2b6fbaf92..68170b0f11 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -31,6 +31,7 @@ sealed class VectorCallViewEvents : VectorViewEvents { object ShowCallTransferScreen : VectorCallViewEvents() object FailToTransfer : VectorCallViewEvents() object ShowScreenSharingPermissionDialog : VectorCallViewEvents() + object StopScreenSharingService : VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 4e622c4ab3..ef827472a9 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -256,7 +256,10 @@ class VectorCallViewModel @AssistedInject constructor( override fun handle(action: VectorCallViewActions) = withState { state -> when (action) { - VectorCallViewActions.EndCall -> call?.endCall() + VectorCallViewActions.EndCall -> { + call?.endCall() + _viewEvents.post(VectorCallViewEvents.StopScreenSharingService) + } VectorCallViewActions.AcceptCall -> { setState { copy(callState = Loading()) @@ -337,21 +340,38 @@ class VectorCallViewModel @AssistedInject constructor( VectorCallViewActions.TransferCall -> { handleCallTransfer() } - is VectorCallViewActions.SwitchCall -> { + is VectorCallViewActions.SwitchCall -> { setState { VectorCallViewState(action.callArgs) } setupCallWithCurrentState() } - is VectorCallViewActions.InitiateScreenSharing -> { - _viewEvents.post( - VectorCallViewEvents.ShowScreenSharingPermissionDialog - ) + is VectorCallViewActions.ToggleScreenSharing -> { + handleToggleScreenSharing(state.isSharingScreen) } - is VectorCallViewActions.StartScreenSharing -> { - call?.shareScreen() + is VectorCallViewActions.StartScreenSharing -> { + call?.startSharingScreen() + setState { + copy(isSharingScreen = true) + } } } } + private fun handleToggleScreenSharing(isSharingScreen: Boolean) { + if (isSharingScreen) { + call?.stopSharingScreen() + setState { + copy(isSharingScreen = false) + } + _viewEvents.post( + VectorCallViewEvents.StopScreenSharingService + ) + } else { + _viewEvents.post( + VectorCallViewEvents.ShowScreenSharingPermissionDialog + ) + } + } + private fun handleCallTransfer() { viewModelScope.launch { val currentCall = call ?: return@launch diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index 2d33cbf9b9..2cd819b5f5 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -42,7 +42,8 @@ data class VectorCallViewState( val callInfo: CallInfo? = null, val formattedDuration: String = "", val canOpponentBeTransferred: Boolean = false, - val transferee: TransfereeState = TransfereeState.NoTransferee + val transferee: TransfereeState = TransfereeState.NoTransferee, + val isSharingScreen: Boolean = false ) : MavericksState { sealed class TransfereeState { diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt new file mode 100644 index 0000000000..922e9676a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureServiceConnection.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.call.webrtc + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import javax.inject.Inject + +class ScreenCaptureServiceConnection @Inject constructor( + private val context: Context +) : ServiceConnection { + + private var isBound = false + private var screenCaptureService: ScreenCaptureService? = null + + fun bind() { + if (!isBound) { + Intent(context, ScreenCaptureService::class.java).also { intent -> + context.bindService(intent, this, 0) + } + } + } + + fun stopScreenCapturing() { + screenCaptureService?.stopService() + } + + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + screenCaptureService = (binder as ScreenCaptureService.LocalBinder).getService() + isBound = true + } + + override fun onServiceDisconnected(className: ComponentName) { + isBound = false + screenCaptureService = null + } +} diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index c744f317ab..bc8ae51a88 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -770,7 +770,11 @@ class WebRtcCall( return currentCaptureFormat } - fun shareScreen() { + fun startSharingScreen() { + // TODO. Will be handled within the next PR. + } + + fun stopSharingScreen() { // TODO. Will be handled within the next PR. } diff --git a/vector/src/main/res/layout/bottom_sheet_call_controls.xml b/vector/src/main/res/layout/bottom_sheet_call_controls.xml index 0b67059dd8..516dc29fa3 100644 --- a/vector/src/main/res/layout/bottom_sheet_call_controls.xml +++ b/vector/src/main/res/layout/bottom_sheet_call_controls.xml @@ -11,7 +11,7 @@ android:id="@+id/callControlsShareScreen" android:layout_width="match_parent" android:layout_height="wrap_content" - app:actionTitle="@string/call_share_screen" + app:actionTitle="@string/call_start_screen_sharing" app:leftIcon="@drawable/ic_share_screen" app:tint="?vctr_content_primary" app:titleTextColor="?vctr_content_primary" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4b8009d10b..11eb3c64fa 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -480,7 +480,8 @@ Back Turn HD off Turn HD on - Share screen + Share screen + Stop screen sharing Send files Send sticker From 0693e3c3aae0ce1204b74670caac0cbb419ed9dd Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Apr 2022 11:56:05 +0300 Subject: [PATCH 5/8] Add feature flag for screen sharing. --- .../app/features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../src/main/java/im/vector/app/features/VectorFeatures.kt | 2 ++ .../im/vector/app/features/call/CallControlsBottomSheet.kt | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index bc716779ac..07fab8a58d 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -60,6 +60,9 @@ class DebugVectorFeatures( override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing) ?: vectorFeatures.isLiveLocationEnabled() + override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing) + ?: vectorFeatures.isScreenSharingEnabled() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -114,4 +117,5 @@ object DebugFeatureKeys { val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize") val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register") val liveLocationSharing = booleanPreferencesKey("live-location-sharing") + val screenSharing = booleanPreferencesKey("screen-sharing") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index b2a62a42d3..9d54475e8c 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -27,6 +27,7 @@ interface VectorFeatures { fun isOnboardingPersonalizeEnabled(): Boolean fun isOnboardingCombinedRegisterEnabled(): Boolean fun isLiveLocationEnabled(): Boolean + fun isScreenSharingEnabled(): Boolean enum class OnboardingVariant { LEGACY, @@ -43,4 +44,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingPersonalizeEnabled() = false override fun isOnboardingCombinedRegisterEnabled() = false override fun isLiveLocationEnabled(): Boolean = false + override fun isScreenSharingEnabled(): Boolean = false } diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index 1c6ead33cc..9310dbf862 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -27,6 +27,8 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetCallControlsBinding +import im.vector.app.features.DefaultVectorFeatures +import javax.inject.Inject @AndroidEntryPoint class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment() { @@ -34,6 +36,8 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment Date: Thu, 21 Apr 2022 12:31:34 +0300 Subject: [PATCH 6/8] Changelog added. --- changelog.d/5811.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5811.feature diff --git a/changelog.d/5811.feature b/changelog.d/5811.feature new file mode 100644 index 0000000000..12111e323b --- /dev/null +++ b/changelog.d/5811.feature @@ -0,0 +1 @@ +VoIP Screen Sharing Permission \ No newline at end of file From 534d55a24e32b188f34d2f008262248c79c7cbd1 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 21 Apr 2022 13:00:51 +0300 Subject: [PATCH 7/8] Fix import. --- .../im/vector/app/features/call/CallControlsBottomSheet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index 9310dbf862..11a43bcd0c 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -27,7 +27,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetCallControlsBinding -import im.vector.app.features.DefaultVectorFeatures +import im.vector.app.features.VectorFeatures import javax.inject.Inject @AndroidEntryPoint @@ -36,7 +36,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment Date: Thu, 21 Apr 2022 15:09:41 +0300 Subject: [PATCH 8/8] Code review fixes. --- .../im/vector/app/features/call/VectorCallViewEvents.kt | 3 --- .../app/features/call/webrtc/ScreenCaptureService.kt | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt index 68170b0f11..78c4886fef 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewEvents.kt @@ -32,7 +32,4 @@ sealed class VectorCallViewEvents : VectorViewEvents { object FailToTransfer : VectorCallViewEvents() object ShowScreenSharingPermissionDialog : VectorCallViewEvents() object StopScreenSharingService : VectorCallViewEvents() -// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() -// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() -// object CallAccepted : VectorCallViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt index 7626e8178c..f1a4975751 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureService.kt @@ -31,12 +31,15 @@ class ScreenCaptureService : VectorService() { private val binder = LocalBinder() override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - // Show a sticky notification + showStickyNotification() + + return START_STICKY + } + + private fun showStickyNotification() { val notificationId = System.currentTimeMillis().toInt() val notification = notificationUtils.buildScreenSharingNotification() startForeground(notificationId, notification) - - return START_STICKY } override fun onBind(intent: Intent?): IBinder {