Create foreground call service.

This commit is contained in:
onurays 2020-05-20 11:08:53 +03:00 committed by Valere
parent 4a4edcf82a
commit 4169f580b8
11 changed files with 228 additions and 9 deletions

View File

@ -362,4 +362,7 @@
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string> <string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
<string name="call_notification_answer">Accept</string>
<string name="call_notification_reject">Reject</string>
</resources> </resources>

View File

@ -12,11 +12,13 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Call feature --> <!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/> <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG"/> <uses-permission android:name="android.permission.READ_CALL_LOG" />
<!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE` <!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE`
intent action. --> intent action. -->
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore --> <!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
<!-- Tell that the Camera is not mandatory to install the application --> <!-- Tell that the Camera is not mandatory to install the application -->
@ -195,15 +197,24 @@
android:name=".core.services.VectorSyncService" android:name=".core.services.VectorSyncService"
android:exported="false" /> android:exported="false" />
<service android:name=".features.call.VectorConnectionService" <service
android:name=".features.call.telecom.VectorConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"> android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter> <intent-filter>
<action android:name="android.telecom.ConnectionService" /> <action android:name="android.telecom.ConnectionService" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".features.call.service.CallHeadsUpService"
android:exported="false" />
<!-- Receivers --> <!-- Receivers -->
<receiver
android:name=".features.call.service.CallHeadsUpActionReceiver"
android:exported="false" />
<!-- Exported false, should only be accessible from this app!! --> <!-- Exported false, should only be accessible from this app!! -->
<receiver <receiver
android:name=".features.notifications.NotificationBroadcastReceiver" android:name=".features.notifications.NotificationBroadcastReceiver"

View File

@ -24,7 +24,7 @@ import android.content.Intent
import android.os.Binder import android.os.Binder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.features.call.CallConnection import im.vector.riotx.features.call.telecom.CallConnection
import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.NotificationUtils
import timber.log.Timber import timber.log.Timber

View File

@ -18,13 +18,12 @@ package im.vector.riotx.features.call
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build import android.os.Build
import android.os.Bundle
import android.telecom.PhoneAccount import android.telecom.PhoneAccount
import android.telecom.PhoneAccountHandle import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager import android.telecom.TelecomManager
import android.telecom.VideoProfile
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.matrix.android.api.session.call.CallsListener import im.vector.matrix.android.api.session.call.CallsListener
import im.vector.matrix.android.api.session.call.EglUtils import im.vector.matrix.android.api.session.call.EglUtils
@ -33,6 +32,8 @@ import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
import im.vector.riotx.BuildConfig import im.vector.riotx.BuildConfig
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.features.call.service.CallHeadsUpService
import im.vector.riotx.features.call.telecom.VectorConnectionService
import org.webrtc.AudioSource import org.webrtc.AudioSource
import org.webrtc.AudioTrack import org.webrtc.AudioTrack
import org.webrtc.DefaultVideoDecoderFactory import org.webrtc.DefaultVideoDecoderFactory
@ -356,6 +357,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
override fun onCallInviteReceived(signalingRoomId: String, callInviteContent: CallInviteContent) { override fun onCallInviteReceived(signalingRoomId: String, callInviteContent: CallInviteContent) {
val callHeadsUpServiceIntent = Intent(context, CallHeadsUpService::class.java)
ContextCompat.startForegroundService(context, callHeadsUpServiceIntent)
/*
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ContextCompat.getSystemService(context, TelecomManager::class.java)?.let { telecomManager -> ContextCompat.getSystemService(context, TelecomManager::class.java)?.let { telecomManager ->
phoneAccountHandle?.let { phoneAccountHandle -> phoneAccountHandle?.let { phoneAccountHandle ->
@ -372,6 +376,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
} }
} }
} }
*/
} }
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) { override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 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.riotx.features.call.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import timber.log.Timber
class CallHeadsUpActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
when (intent?.getIntExtra(CallHeadsUpService.EXTRA_CALL_ACTION_KEY, 0)) {
CallHeadsUpService.CALL_ACTION_ANSWER -> onCallAnswerClicked()
CallHeadsUpService.CALL_ACTION_REJECT -> onCallRejectClicked()
}
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
context.stopService(Intent(context, CallHeadsUpService::class.java))
}
private fun onCallRejectClicked() {
Timber.d("onCallRejectClicked")
}
private fun onCallAnswerClicked() {
Timber.d("onCallAnswerClicked")
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2020 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.riotx.features.call.service
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioManager
import android.net.Uri
import android.os.Binder
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import im.vector.riotx.R
class CallHeadsUpService : Service() {
private val CHANNEL_ID = "CallChannel"
private val CHANNEL_NAME = "Call Channel"
private val CHANNEL_DESCRIPTION = "Call Notifications"
private val binder: IBinder = CallHeadsUpServiceBinder()
override fun onBind(intent: Intent): IBinder? {
return binder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
//val callHeadsUpServiceArgs: CallHeadsUpServiceArgs? = intent?.extras?.getParcelable(EXTRA_CALL_HEADS_UP_SERVICE_PARAMS)
val answerCallActionReceiver = Intent(applicationContext, CallHeadsUpActionReceiver::class.java).apply {
putExtra(EXTRA_CALL_ACTION_KEY, CALL_ACTION_ANSWER)
}
val rejectCallActionReceiver = Intent(applicationContext, CallHeadsUpActionReceiver::class.java).apply {
putExtra(EXTRA_CALL_ACTION_KEY, CALL_ACTION_REJECT)
}
val answerCallPendingIntent = PendingIntent.getBroadcast(applicationContext, CALL_ACTION_ANSWER, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = PendingIntent.getBroadcast(applicationContext, CALL_ACTION_REJECT, rejectCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
createNotificationChannel()
val notification = NotificationCompat
.Builder(applicationContext, CHANNEL_ID)
.setContentTitle("Title")
.setContentText("Content")
.setSmallIcon(R.drawable.ic_call_incoming)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)
.addAction(R.drawable.ic_call_incoming, getString(R.string.call_notification_answer), answerCallPendingIntent)
.addAction(R.drawable.ic_call_incoming, getString(R.string.call_notification_reject), rejectCallPendingIntent)
.setAutoCancel(true)
.setSound(Uri.parse("android.resource://" + applicationContext.packageName + "/ring.ogg"))
.setFullScreenIntent(answerCallPendingIntent, true)
.build()
startForeground(NOTIFICATION_ID, notification)
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH).apply {
description = CHANNEL_DESCRIPTION
setSound(
Uri.parse("android.resource://" + applicationContext.packageName + "/ring.ogg"),
AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_RING)
.build()
)
}
applicationContext.getSystemService(NotificationManager::class.java)?.createNotificationChannel(channel)
}
inner class CallHeadsUpServiceBinder : Binder() {
fun getService() = this@CallHeadsUpService
}
companion object {
private const val EXTRA_CALL_HEADS_UP_SERVICE_PARAMS = "EXTRA_CALL_PARAMS"
const val EXTRA_CALL_ACTION_KEY = "EXTRA_CALL_ACTION_KEY"
const val CALL_ACTION_ANSWER = 100
const val CALL_ACTION_REJECT = 101
private const val NOTIFICATION_ID = 999
fun newInstance(context: Context, callerDisplayName: String, isIncomingCall: Boolean, isVideoCall: Boolean): Intent {
val args = CallHeadsUpServiceArgs(callerDisplayName, isIncomingCall, isVideoCall)
return Intent(context, CallHeadsUpService::class.java).apply {
putExtra(EXTRA_CALL_HEADS_UP_SERVICE_PARAMS, args)
}
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2020 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.riotx.features.call.service
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
data class CallHeadsUpServiceArgs(
val callerDisplayName: String,
val isIncomingCall: Boolean,
val isVideoCall: Boolean
) : Parcelable

View File

@ -14,13 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.features.call package im.vector.riotx.features.call.telecom
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.telecom.Connection import android.telecom.Connection
import android.telecom.DisconnectCause import android.telecom.DisconnectCause
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import im.vector.riotx.features.call.VectorCallViewActions
import im.vector.riotx.features.call.VectorCallViewModel
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
import org.webrtc.Camera1Enumerator import org.webrtc.Camera1Enumerator
import org.webrtc.Camera2Enumerator import org.webrtc.Camera2Enumerator
import org.webrtc.IceCandidate import org.webrtc.IceCandidate

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.features.call package im.vector.riotx.features.call.telecom
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent

View File

@ -127,7 +127,7 @@ import im.vector.riotx.features.attachments.ContactAttachment
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.riotx.features.attachments.toGroupedContentAttachmentData import im.vector.riotx.features.attachments.toGroupedContentAttachmentData
import im.vector.riotx.features.call.VectorCallActivity import im.vector.riotx.features.call.service.CallHeadsUpService
import im.vector.riotx.features.command.Command import im.vector.riotx.features.command.Command
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.riotx.features.crypto.util.toImageRes import im.vector.riotx.features.crypto.util.toImageRes
@ -485,9 +485,13 @@ class RoomDetailFragment @Inject constructor(
return true return true
} }
if (item.itemId == R.id.voip_call) { if (item.itemId == R.id.voip_call) {
/*
VectorCallActivity.newIntent(requireContext(), roomDetailArgs.roomId).let { VectorCallActivity.newIntent(requireContext(), roomDetailArgs.roomId).let {
startActivity(it) startActivity(it)
} }
*/
val callHeadsUpServiceIntent = Intent(requireContext(), CallHeadsUpService::class.java)
ContextCompat.startForegroundService(requireContext(), callHeadsUpServiceIntent)
return true return true
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M20.01,15.38c-1.23,0 -2.42,-0.2 -3.53,-0.56 -0.35,-0.12 -0.74,-0.03 -1.01,0.24l-1.57,1.97c-2.83,-1.35 -5.48,-3.9 -6.89,-6.83l1.95,-1.66c0.27,-0.28 0.35,-0.67 0.24,-1.02 -0.37,-1.11 -0.56,-2.3 -0.56,-3.53 0,-0.54 -0.45,-0.99 -0.99,-0.99H4.19C3.65,3 3,3.24 3,3.99 3,13.28 10.73,21 20.01,21c0.71,0 0.99,-0.63 0.99,-1.18v-3.45c0,-0.54 -0.45,-0.99 -0.99,-0.99z"/>
</vector>