kopia lustrzana https://github.com/ryukoposting/Signal-Android
213 wiersze
8.0 KiB
Kotlin
213 wiersze
8.0 KiB
Kotlin
package org.thoughtcrime.securesms.service.webrtc
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.ComponentName
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Process
|
|
import android.telecom.CallAudioState
|
|
import android.telecom.Connection
|
|
import android.telecom.DisconnectCause
|
|
import android.telecom.DisconnectCause.REJECTED
|
|
import android.telecom.DisconnectCause.UNKNOWN
|
|
import android.telecom.PhoneAccount
|
|
import android.telecom.PhoneAccountHandle
|
|
import android.telecom.TelecomManager
|
|
import android.telecom.VideoProfile
|
|
import androidx.annotation.RequiresApi
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.os.bundleOf
|
|
import org.signal.core.util.logging.Log
|
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
|
|
|
/**
|
|
* Wrapper around various [TelecomManager] methods to make dealing with SDK versions easier. Also
|
|
* maintains a global list of all Signal [AndroidCallConnection]s associated with their [RecipientId].
|
|
* There should really only be one ever, but there may be times when dealing with glare or a busy that two
|
|
* may kick off.
|
|
*/
|
|
@SuppressLint("NewApi", "InlinedApi")
|
|
object AndroidTelecomUtil {
|
|
|
|
private val TAG = Log.tag(AndroidTelecomUtil::class.java)
|
|
private val context = ApplicationDependencies.getApplication()
|
|
private var systemRejected = false
|
|
private var accountRegistered = false
|
|
|
|
@JvmStatic
|
|
val telecomSupported: Boolean
|
|
get() {
|
|
if (Build.VERSION.SDK_INT >= 26 && !systemRejected && isTelecomAllowedForDevice()) {
|
|
if (!accountRegistered) {
|
|
registerPhoneAccount()
|
|
}
|
|
|
|
if (accountRegistered) {
|
|
val phoneAccount = ContextCompat.getSystemService(context, TelecomManager::class.java)!!.getPhoneAccount(getPhoneAccountHandle())
|
|
if (phoneAccount != null && phoneAccount.isEnabled) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
@JvmStatic
|
|
val connections: MutableMap<RecipientId, AndroidCallConnection> = mutableMapOf()
|
|
|
|
@JvmStatic
|
|
fun registerPhoneAccount() {
|
|
if (Build.VERSION.SDK_INT >= 26 && !systemRejected) {
|
|
Log.i(TAG, "Registering phone account")
|
|
val phoneAccount = PhoneAccount.Builder(getPhoneAccountHandle(), "Signal")
|
|
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED or PhoneAccount.CAPABILITY_VIDEO_CALLING)
|
|
.build()
|
|
|
|
try {
|
|
ContextCompat.getSystemService(context, TelecomManager::class.java)!!.registerPhoneAccount(phoneAccount)
|
|
Log.i(TAG, "Phone account registered successfully")
|
|
accountRegistered = true
|
|
} catch (e: Exception) {
|
|
Log.w(TAG, "Unable to register telecom account", e)
|
|
systemRejected = true
|
|
}
|
|
}
|
|
}
|
|
|
|
@JvmStatic
|
|
@RequiresApi(26)
|
|
fun getPhoneAccountHandle(): PhoneAccountHandle {
|
|
return PhoneAccountHandle(ComponentName(context, AndroidCallConnectionService::class.java), context.packageName, Process.myUserHandle())
|
|
}
|
|
|
|
@JvmStatic
|
|
fun addIncomingCall(recipientId: RecipientId, callId: Long, remoteVideoOffer: Boolean): Boolean {
|
|
if (telecomSupported) {
|
|
val telecomBundle = bundleOf(
|
|
TelecomManager.EXTRA_INCOMING_CALL_EXTRAS to bundleOf(
|
|
AndroidCallConnectionService.KEY_RECIPIENT_ID to recipientId.serialize(),
|
|
AndroidCallConnectionService.KEY_CALL_ID to callId,
|
|
AndroidCallConnectionService.KEY_VIDEO_CALL to remoteVideoOffer,
|
|
TelecomManager.EXTRA_INCOMING_VIDEO_STATE to if (remoteVideoOffer) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY
|
|
),
|
|
TelecomManager.EXTRA_INCOMING_VIDEO_STATE to if (remoteVideoOffer) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY
|
|
)
|
|
try {
|
|
Log.i(TAG, "Adding incoming call $telecomBundle")
|
|
ContextCompat.getSystemService(context, TelecomManager::class.java)!!.addNewIncomingCall(getPhoneAccountHandle(), telecomBundle)
|
|
} catch (e: SecurityException) {
|
|
Log.w(TAG, "Unable to add incoming call", e)
|
|
systemRejected = true
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
@JvmStatic
|
|
fun reject(recipientId: RecipientId) {
|
|
if (telecomSupported) {
|
|
connections[recipientId]?.setDisconnected(DisconnectCause(REJECTED))
|
|
}
|
|
}
|
|
|
|
@JvmStatic
|
|
fun activateCall(recipientId: RecipientId) {
|
|
if (telecomSupported) {
|
|
connections[recipientId]?.setActive()
|
|
}
|
|
}
|
|
|
|
@JvmStatic
|
|
fun terminateCall(recipientId: RecipientId) {
|
|
if (telecomSupported) {
|
|
connections[recipientId]?.let { connection ->
|
|
if (connection.disconnectCause == null) {
|
|
connection.setDisconnected(DisconnectCause(UNKNOWN))
|
|
}
|
|
connection.destroy()
|
|
connections.remove(recipientId)
|
|
}
|
|
}
|
|
}
|
|
|
|
@JvmStatic
|
|
fun addOutgoingCall(recipientId: RecipientId, callId: Long, isVideoCall: Boolean): Boolean {
|
|
if (telecomSupported) {
|
|
val telecomBundle = bundleOf(
|
|
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE to getPhoneAccountHandle(),
|
|
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE to if (isVideoCall) VideoProfile.STATE_BIDIRECTIONAL else VideoProfile.STATE_AUDIO_ONLY,
|
|
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to bundleOf(
|
|
AndroidCallConnectionService.KEY_RECIPIENT_ID to recipientId.serialize(),
|
|
AndroidCallConnectionService.KEY_CALL_ID to callId,
|
|
AndroidCallConnectionService.KEY_VIDEO_CALL to isVideoCall
|
|
),
|
|
)
|
|
|
|
try {
|
|
Log.i(TAG, "Adding outgoing call $telecomBundle")
|
|
ContextCompat.getSystemService(context, TelecomManager::class.java)!!.placeCall(recipientId.generateTelecomE164(), telecomBundle)
|
|
} catch (e: SecurityException) {
|
|
Log.w(TAG, "Unable to add outgoing call", e)
|
|
systemRejected = true
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
fun selectAudioDevice(recipientId: RecipientId, device: SignalAudioManager.AudioDevice) {
|
|
if (telecomSupported) {
|
|
val connection: AndroidCallConnection? = connections[recipientId]
|
|
Log.i(TAG, "Selecting audio route: $device connection: ${connection != null}")
|
|
if (connection?.callAudioState != null) {
|
|
when (device) {
|
|
SignalAudioManager.AudioDevice.SPEAKER_PHONE -> connection.setAudioRouteIfDifferent(CallAudioState.ROUTE_SPEAKER)
|
|
SignalAudioManager.AudioDevice.BLUETOOTH -> connection.setAudioRouteIfDifferent(CallAudioState.ROUTE_BLUETOOTH)
|
|
SignalAudioManager.AudioDevice.WIRED_HEADSET -> connection.setAudioRouteIfDifferent(CallAudioState.ROUTE_WIRED_HEADSET)
|
|
else -> connection.setAudioRouteIfDifferent(CallAudioState.ROUTE_EARPIECE)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun getSelectedAudioDevice(recipientId: RecipientId): SignalAudioManager.AudioDevice {
|
|
if (telecomSupported) {
|
|
val connection: AndroidCallConnection? = connections[recipientId]
|
|
if (connection?.callAudioState != null) {
|
|
return when (connection.callAudioState.route) {
|
|
CallAudioState.ROUTE_SPEAKER -> SignalAudioManager.AudioDevice.SPEAKER_PHONE
|
|
CallAudioState.ROUTE_BLUETOOTH -> SignalAudioManager.AudioDevice.BLUETOOTH
|
|
CallAudioState.ROUTE_WIRED_HEADSET -> SignalAudioManager.AudioDevice.WIRED_HEADSET
|
|
else -> SignalAudioManager.AudioDevice.EARPIECE
|
|
}
|
|
}
|
|
}
|
|
return SignalAudioManager.AudioDevice.NONE
|
|
}
|
|
|
|
private fun isTelecomAllowedForDevice(): Boolean {
|
|
if (FeatureFlags.internalUser()) {
|
|
return !SignalStore.internalValues().callingDisableTelecom()
|
|
}
|
|
return RingRtcDynamicConfiguration.isTelecomAllowedForDevice()
|
|
}
|
|
}
|
|
|
|
@RequiresApi(26)
|
|
private fun Connection.setAudioRouteIfDifferent(newRoute: Int) {
|
|
if (callAudioState.route != newRoute) {
|
|
setAudioRoute(newRoute)
|
|
}
|
|
}
|
|
|
|
private fun RecipientId.generateTelecomE164(): Uri {
|
|
val pseudoNumber = toLong().toString().padEnd(10, '0').replaceRange(3..5, "555")
|
|
return Uri.fromParts("tel", "+1$pseudoNumber", null)
|
|
}
|