Support headset buttons in background

This commit is contained in:
Valere 2020-06-18 14:30:08 +02:00
parent 9653f082a3
commit 4c61dfef62
4 changed files with 69 additions and 15 deletions

View File

@ -196,7 +196,12 @@
<service <service
android:name=".core.services.CallService" android:name=".core.services.CallService"
android:exported="false" /> android:exported="false" >
<!-- in order to get headset button events -->
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<service <service
android:name=".core.services.VectorSyncService" android:name=".core.services.VectorSyncService"
@ -226,6 +231,16 @@
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<!--
A media button receiver receives and helps translate hardware media playback buttons,
such as those found on wired and wireless headsets, into the appropriate callbacks in your app.
-->
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- Providers --> <!-- Providers -->
<provider <provider

View File

@ -20,7 +20,10 @@ package im.vector.riotx.core.services
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.support.v4.media.session.MediaSessionCompat
import android.view.KeyEvent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.media.session.MediaButtonReceiver
import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import im.vector.riotx.features.call.telecom.CallConnection import im.vector.riotx.features.call.telecom.CallConnection
@ -34,23 +37,27 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private val connections = mutableMapOf<String, CallConnection>() private val connections = mutableMapOf<String, CallConnection>()
/**
* call in progress (foreground notification)
*/
// private var mCallIdInProgress: String? = null
private lateinit var notificationUtils: NotificationUtils private lateinit var notificationUtils: NotificationUtils
private lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager private lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
/**
* incoming (foreground notification)
*/
// private var mIncomingCallId: String? = null
private var callRingPlayer: CallRingPlayer? = null private var callRingPlayer: CallRingPlayer? = null
private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null private var wiredHeadsetStateReceiver: WiredHeadsetStateReceiver? = null
// A media button receiver receives and helps translate hardware media playback buttons,
// such as those found on wired and wireless headsets, into the appropriate callbacks in your app
private var mediaSession : MediaSessionCompat? = null
private val mediaSessionButtonCallback = object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
val keyEvent = mediaButtonEvent?.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT) ?: return false
if (keyEvent.keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
webRtcPeerConnectionManager.headSetButtonTapped()
return true
}
return false
}
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
notificationUtils = vectorComponent().notificationUtils() notificationUtils = vectorComponent().notificationUtils()
@ -64,22 +71,36 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
callRingPlayer?.stop() callRingPlayer?.stop()
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) } wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(this, it) }
wiredHeadsetStateReceiver = null wiredHeadsetStateReceiver = null
mediaSession?.release()
mediaSession = null
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.v("## VOIP onStartCommand $intent") Timber.v("## VOIP onStartCommand $intent")
if (mediaSession == null) {
mediaSession = MediaSessionCompat(applicationContext, CallService::class.java.name).apply {
setCallback(mediaSessionButtonCallback)
}
}
if (intent == null) { if (intent == null) {
// Service started again by the system. // Service started again by the system.
// TODO What do we do here? // TODO What do we do here?
return START_STICKY return START_STICKY
} }
mediaSession?.let {
// This ensures that the correct callbacks to MediaSessionCompat.Callback
// will be triggered based on the incoming KeyEvent.
MediaButtonReceiver.handleIntent(it, intent)
}
when (intent.action) { when (intent.action) {
ACTION_INCOMING_RINGING_CALL -> { ACTION_INCOMING_RINGING_CALL -> {
mediaSession?.isActive = true
callRingPlayer?.start() callRingPlayer?.start()
displayIncomingCallNotification(intent) displayIncomingCallNotification(intent)
} }
ACTION_OUTGOING_RINGING_CALL -> { ACTION_OUTGOING_RINGING_CALL -> {
mediaSession?.isActive = true
callRingPlayer?.start() callRingPlayer?.start()
displayOutgoingRingingCallNotification(intent) displayOutgoingRingingCallNotification(intent)
} }
@ -221,6 +242,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
private fun hideCallNotifications() { private fun hideCallNotifications() {
val notification = notificationUtils.buildCallEndedNotification() val notification = notificationUtils.buildCallEndedNotification()
mediaSession?.isActive = false
// It's mandatory to startForeground to avoid crash // It's mandatory to startForeground to avoid crash
startForeground(NOTIFICATION_ID, notification) startForeground(NOTIFICATION_ID, notification)

View File

@ -326,10 +326,14 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
KeyEvent.KEYCODE_HEADSETHOOK -> { // for newer version, it will be passed automatically to active media session
callViewModel.handle(VectorCallViewActions.HeadSetButtonPressed) // in call service
return true when (keyCode) {
KeyEvent.KEYCODE_HEADSETHOOK -> {
callViewModel.handle(VectorCallViewActions.HeadSetButtonPressed)
return true
}
} }
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)

View File

@ -184,6 +184,19 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
} }
fun headSetButtonTapped() {
Timber.v("## VOIP headSetButtonTapped")
val call = currentCall?.mxCall ?: return
if (call.state is CallState.LocalRinging) {
// accept call
acceptIncomingCall()
}
if (call.state is CallState.Connected) {
// end call?
endCall()
}
}
private fun createPeerConnectionFactory() { private fun createPeerConnectionFactory() {
if (peerConnectionFactory != null) return if (peerConnectionFactory != null) return
Timber.v("## VOIP createPeerConnectionFactory") Timber.v("## VOIP createPeerConnectionFactory")