Create foreground call service.
This commit is contained in:
parent
4a4edcf82a
commit
4169f580b8
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue