Skip to content

Commit

Permalink
Merge pull request #5811 from vector-im/feature/ons/voip_screen_shari…
Browse files Browse the repository at this point in the history
…ng_permission

VoIP Screen Sharing Permission
  • Loading branch information
onurays authored Apr 21, 2022
2 parents c21ec98 + 56b3e8a commit 8eaa2f8
Show file tree
Hide file tree
Showing 19 changed files with 263 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.d/5811.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VoIP Screen Sharing Permission
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) {
it.remove(key)
Expand Down Expand Up @@ -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")
}
6 changes: 6 additions & 0 deletions vector/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,12 @@
android:exported="false"
android:foregroundServiceType="location" />

<service
android:name=".features.call.webrtc.ScreenCaptureService"
android:exported="false"
android:foregroundServiceType="mediaProjection"
tools:targetApi="Q" />

<!-- Receivers -->

<receiver
Expand Down
2 changes: 2 additions & 0 deletions vector/src/main/java/im/vector/app/features/VectorFeatures.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface VectorFeatures {
fun isOnboardingPersonalizeEnabled(): Boolean
fun isOnboardingCombinedRegisterEnabled(): Boolean
fun isLiveLocationEnabled(): Boolean
fun isScreenSharingEnabled(): Boolean

enum class OnboardingVariant {
LEGACY,
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ 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.VectorFeatures
import javax.inject.Inject

@AndroidEntryPoint
class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallControlsBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetCallControlsBinding {
return BottomSheetCallControlsBinding.inflate(inflater, container, false)
}

@Inject lateinit var vectorFeatures: VectorFeatures

private val callViewModel: VectorCallViewModel by activityViewModel()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -66,6 +70,12 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
callViewModel.handle(VectorCallViewActions.InitiateCallTransfer)
dismiss()
}

views.callControlsShareScreen.isVisible = vectorFeatures.isScreenSharingEnabled()
views.callControlsShareScreen.views.bottomSheetActionClickableZone.debouncedClicks {
callViewModel.handle(VectorCallViewActions.ToggleScreenSharing)
dismiss()
}
}

private fun renderState(state: VectorCallViewState) {
Expand Down Expand Up @@ -95,5 +105,6 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetC
views.callControlsToggleHoldResume.leftIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_call_hold_action)
}
views.callControlsTransfer.isVisible = state.canOpponentBeTransferred
views.callControlsShareScreen.title = getString(if (state.isSharingScreen) R.string.call_stop_screen_sharing else R.string.call_start_screen_sharing)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.res.Configuration
import android.graphics.Color
import android.media.projection.MediaProjectionManager
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
Expand Down Expand Up @@ -56,6 +57,8 @@ import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.webrtc.ScreenCaptureService
import im.vector.app.features.call.webrtc.ScreenCaptureServiceConnection
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.displayname.getBestName
Expand Down Expand Up @@ -94,6 +97,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro

@Inject lateinit var callManager: WebRtcCallManager
@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var screenCaptureServiceConnection: ScreenCaptureServiceConnection

private val callViewModel: VectorCallViewModel by viewModel()

Expand Down Expand Up @@ -512,20 +516,22 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), 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()
is VectorCallViewEvents.StopScreenSharingService -> handleStopScreenSharingService()
else -> Unit
}
}

Expand Down Expand Up @@ -628,6 +634,32 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), 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)
)
screenCaptureServiceConnection.bind()
}
}
}

private fun handleShowScreenSharingPermissionDialog() {
getSystemService<MediaProjectionManager>()?.let {
navigator.openScreenSharingPermissionDialog(it.createScreenCaptureIntent(), screenSharingPermissionActivityResultLauncher)
}
}

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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ sealed class VectorCallViewActions : VectorViewModelAction {
object CallTransferSelectionCancelled : VectorCallViewActions()
data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions()
object TransferCall : VectorCallViewActions()
object ToggleScreenSharing : VectorCallViewActions()
object StartScreenSharing : VectorCallViewActions()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ sealed class VectorCallViewEvents : VectorViewEvents {
object ShowDialPad : VectorCallViewEvents()
object ShowCallTransferScreen : VectorCallViewEvents()
object FailToTransfer : VectorCallViewEvents()
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
// object CallAccepted : VectorCallViewEvents()
object ShowScreenSharingPermissionDialog : VectorCallViewEvents()
object StopScreenSharingService : VectorCallViewEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -341,6 +344,31 @@ class VectorCallViewModel @AssistedInject constructor(
setState { VectorCallViewState(action.callArgs) }
setupCallWithCurrentState()
}
is VectorCallViewActions.ToggleScreenSharing -> {
handleToggleScreenSharing(state.isSharingScreen)
}
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
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 {
showStickyNotification()

return START_STICKY
}

private fun showStickyNotification() {
val notificationId = System.currentTimeMillis().toInt()
val notification = notificationUtils.buildScreenSharingNotification()
startForeground(notificationId, notification)
}

override fun onBind(intent: Intent?): IBinder {
return binder
}

fun stopService() {
stopSelf()
}

inner class LocalBinder : Binder() {
fun getService(): ScreenCaptureService = this@ScreenCaptureService
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,14 @@ class WebRtcCall(
return currentCaptureFormat
}

fun startSharingScreen() {
// TODO. Will be handled within the next PR.
}

fun stopSharingScreen() {
// TODO. Will be handled within the next PR.
}

private suspend fun release() {
listeners.clear()
mxCall.removeListener(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,4 +600,9 @@ class DefaultNavigator @Inject constructor(
roomEncryptionTrustLevel = threadTimelineArgs.roomEncryptionTrustLevel
)))
}

override fun openScreenSharingPermissionDialog(screenCaptureIntent: Intent,
activityResultLauncher: ActivityResultLauncher<Intent>) {
activityResultLauncher.launch(screenCaptureIntent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Intent>
)
}
Loading

0 comments on commit 8eaa2f8

Please sign in to comment.