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="call_notification_answer">Accept</string>
<string name="call_notification_reject">Reject</string>
</resources>

View File

@ -12,11 +12,13 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Call feature -->
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<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`
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 -->
<!-- Tell that the Camera is not mandatory to install the application -->
@ -195,15 +197,24 @@
android:name=".core.services.VectorSyncService"
android:exported="false" />
<service android:name=".features.call.VectorConnectionService"
<service
android:name=".features.call.telecom.VectorConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
<service
android:name=".features.call.service.CallHeadsUpService"
android:exported="false" />
<!-- Receivers -->
<receiver
android:name=".features.call.service.CallHeadsUpActionReceiver"
android:exported="false" />
<!-- Exported false, should only be accessible from this app!! -->
<receiver
android:name=".features.notifications.NotificationBroadcastReceiver"

View File

@ -24,7 +24,7 @@ import android.content.Intent
import android.os.Binder
import androidx.core.content.ContextCompat
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 timber.log.Timber

View File

@ -18,13 +18,12 @@ package im.vector.riotx.features.call
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.telecom.PhoneAccount
import android.telecom.PhoneAccountHandle
import android.telecom.TelecomManager
import android.telecom.VideoProfile
import androidx.core.content.ContextCompat
import im.vector.matrix.android.api.session.call.CallsListener
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.riotx.BuildConfig
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.AudioTrack
import org.webrtc.DefaultVideoDecoderFactory
@ -356,6 +357,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
}
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) {
ContextCompat.getSystemService(context, TelecomManager::class.java)?.let { telecomManager ->
phoneAccountHandle?.let { phoneAccountHandle ->
@ -372,6 +376,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
}
}
}
*/
}
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.
*/
package im.vector.riotx.features.call
package im.vector.riotx.features.call.telecom
import android.content.Context
import android.os.Build
import android.telecom.Connection
import android.telecom.DisconnectCause
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.Camera2Enumerator
import org.webrtc.IceCandidate

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.riotx.features.call
package im.vector.riotx.features.call.telecom
import android.content.ComponentName
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.AttachmentsPreviewArgs
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.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.riotx.features.crypto.util.toImageRes
@ -485,9 +485,13 @@ class RoomDetailFragment @Inject constructor(
return true
}
if (item.itemId == R.id.voip_call) {
/*
VectorCallActivity.newIntent(requireContext(), roomDetailArgs.roomId).let {
startActivity(it)
}
*/
val callHeadsUpServiceIntent = Intent(requireContext(), CallHeadsUpService::class.java)
ContextCompat.startForegroundService(requireContext(), callHeadsUpServiceIntent)
return true
}
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>