mirror of
https://github.com/SimpleMobileTools/Simple-Dialer.git
synced 2025-06-05 21:49:23 +02:00
manage conference
This commit is contained in:
@ -0,0 +1,71 @@
|
||||
package com.simplemobiletools.dialer.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.telecom.Call
|
||||
import com.simplemobiletools.commons.extensions.getMyContactsCursor
|
||||
import com.simplemobiletools.commons.extensions.getPhoneNumberTypeText
|
||||
import com.simplemobiletools.commons.helpers.MyContactsContentProvider
|
||||
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.dialer.R
|
||||
import com.simplemobiletools.dialer.extensions.isConference
|
||||
import com.simplemobiletools.dialer.models.CallContact
|
||||
|
||||
fun getCallContact(context: Context, call: Call?, callback: (CallContact) -> Unit) {
|
||||
if (call.isConference()) {
|
||||
callback(CallContact(context.getString(R.string.conference), "", "", ""))
|
||||
return
|
||||
}
|
||||
val privateCursor = context.getMyContactsCursor(false, true)
|
||||
ensureBackgroundThread {
|
||||
val callContact = CallContact("", "", "", "")
|
||||
val handle = try {
|
||||
call?.details?.handle?.toString()
|
||||
} catch (e: NullPointerException) {
|
||||
null
|
||||
}
|
||||
|
||||
if (handle == null) {
|
||||
callback(callContact)
|
||||
return@ensureBackgroundThread
|
||||
}
|
||||
|
||||
val uri = Uri.decode(handle)
|
||||
if (uri.startsWith("tel:")) {
|
||||
val number = uri.substringAfter("tel:")
|
||||
SimpleContactsHelper(context).getAvailableContacts(false) { contacts ->
|
||||
val privateContacts = MyContactsContentProvider.getSimpleContacts(context, privateCursor)
|
||||
if (privateContacts.isNotEmpty()) {
|
||||
contacts.addAll(privateContacts)
|
||||
}
|
||||
|
||||
val contactsWithMultipleNumbers = contacts.filter { it.phoneNumbers.size > 1 }
|
||||
val numbersToContactIDMap = HashMap<String, Int>()
|
||||
contactsWithMultipleNumbers.forEach { contact ->
|
||||
contact.phoneNumbers.forEach { phoneNumber ->
|
||||
numbersToContactIDMap[phoneNumber.value] = contact.contactId
|
||||
numbersToContactIDMap[phoneNumber.normalizedNumber] = contact.contactId
|
||||
}
|
||||
}
|
||||
|
||||
callContact.number = number
|
||||
val contact = contacts.firstOrNull { it.doesHavePhoneNumber(number) }
|
||||
if (contact != null) {
|
||||
callContact.name = contact.name
|
||||
callContact.photoUri = contact.photoUri
|
||||
|
||||
if (contact.phoneNumbers.size > 1) {
|
||||
val specificPhoneNumber = contact.phoneNumbers.firstOrNull { it.value == number }
|
||||
if (specificPhoneNumber != null) {
|
||||
callContact.numberLabel = context.getPhoneNumberTypeText(specificPhoneNumber.type, specificPhoneNumber.label)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callContact.name = number
|
||||
}
|
||||
callback(callContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +1,31 @@
|
||||
package com.simplemobiletools.dialer.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.annotation.SuppressLint
|
||||
import android.telecom.Call
|
||||
import android.telecom.InCallService
|
||||
import android.telecom.VideoProfile
|
||||
import android.util.Log
|
||||
import com.simplemobiletools.commons.extensions.getMyContactsCursor
|
||||
import com.simplemobiletools.commons.extensions.getPhoneNumberTypeText
|
||||
import com.simplemobiletools.commons.helpers.MyContactsContentProvider
|
||||
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.dialer.extensions.getStateCompat
|
||||
import com.simplemobiletools.dialer.extensions.hasCapability
|
||||
import com.simplemobiletools.dialer.extensions.isConference
|
||||
import com.simplemobiletools.dialer.models.CallContact
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
const val TAG = "SimpleDialer:CallManager"
|
||||
const val TAG2 = TAG /*"SimpleDialer:CallState"*/
|
||||
|
||||
// inspired by https://github.com/Chooloo/call_manage
|
||||
class CallManager {
|
||||
companion object {
|
||||
var call: Call? = null
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
var inCallService: InCallService? = null
|
||||
val calls = mutableListOf<Call>()
|
||||
private var call: Call? = null
|
||||
private val calls = mutableListOf<Call>()
|
||||
private val listeners = CopyOnWriteArraySet<CallManagerListener>()
|
||||
|
||||
fun onCallAdded(call: Call) {
|
||||
this.call = call
|
||||
calls.add(call)
|
||||
Log.d(TAG, "onCallAdded (${calls.size}): $call")
|
||||
for (listener in listeners) {
|
||||
listener.onPrimaryCallChanged(call)
|
||||
}
|
||||
@ -37,13 +33,15 @@ class CallManager {
|
||||
override fun onStateChanged(call: Call, state: Int) {
|
||||
Log.d(TAG, "onStateChanged: $call")
|
||||
updateState()
|
||||
for (listener in listeners) {
|
||||
listener.onStateChanged(call, state)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetailsChanged(call: Call, details: Call.Details) {
|
||||
Log.d(TAG, "onDetailsChanged")
|
||||
updateState()
|
||||
}
|
||||
|
||||
override fun onConferenceableCallsChanged(call: Call, conferenceableCalls: MutableList<Call>) {
|
||||
Log.d(TAG, "onConferenceableCallsChanged: $call, conferenceableCalls size=${conferenceableCalls.size}")
|
||||
Log.d(TAG, "onConferenceableCallsChanged (${conferenceableCalls.size}): $call")
|
||||
updateState()
|
||||
}
|
||||
})
|
||||
@ -51,34 +49,59 @@ class CallManager {
|
||||
|
||||
fun onCallRemoved(call: Call) {
|
||||
calls.remove(call)
|
||||
Log.d(TAG, "onCallRemoved (${calls.size}): $call")
|
||||
updateState()
|
||||
}
|
||||
|
||||
fun getPhoneState(): PhoneState {
|
||||
return when (calls.size) {
|
||||
0 -> {
|
||||
Log.d(TAG2, "No call")
|
||||
NoCall
|
||||
}
|
||||
1 -> {
|
||||
Log.d(TAG2, "Single call")
|
||||
SingleCall(calls.first())
|
||||
}
|
||||
2 -> {
|
||||
Log.d(TAG2, "Two calls")
|
||||
val active = calls.find { it.getStateCompat() == Call.STATE_ACTIVE }
|
||||
val newCall = calls.find { it.getStateCompat() == Call.STATE_CONNECTING || it.getStateCompat() == Call.STATE_DIALING }
|
||||
val onHold = calls.find { it.getStateCompat() == Call.STATE_HOLDING }
|
||||
if (active != null && newCall != null) {
|
||||
TwoCalls(newCall, active)
|
||||
} else if(newCall != null && onHold != null) {
|
||||
} else if (newCall != null && onHold != null) {
|
||||
TwoCalls(newCall, onHold)
|
||||
} else if(active != null && onHold != null) {
|
||||
} else if (active != null && onHold != null) {
|
||||
TwoCalls(active, onHold)
|
||||
} else {
|
||||
TwoCalls(calls[0], calls[1])
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
SingleCall(calls.find { it.isConference() }!!)
|
||||
// TODO handle the call on hold (outside the conference)
|
||||
val conference = calls.find { it.isConference() }!!
|
||||
val secondCall = if (conference.children.size + 1 != calls.size) {
|
||||
calls.filter { !it.isConference() }
|
||||
.subtract(conference.children.toSet())
|
||||
.firstOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Log.d(TAG2, "Conference call (${conference.children.size} children)")
|
||||
Log.d(TAG2, "secondCall: $secondCall")
|
||||
if (secondCall == null) {
|
||||
Log.d(TAG2, "Conference call (single)")
|
||||
SingleCall(conference)
|
||||
} else {
|
||||
val newCallState = secondCall.getStateCompat()
|
||||
if (newCallState == Call.STATE_ACTIVE || newCallState == Call.STATE_CONNECTING || newCallState == Call.STATE_DIALING) {
|
||||
Log.d(TAG2, "Conference call and regular call (conference on hold)")
|
||||
TwoCalls(secondCall, conference)
|
||||
} else {
|
||||
Log.d(TAG2, "Conference call and regular call (regular call on hold)")
|
||||
TwoCalls(conference, secondCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,6 +112,7 @@ class CallManager {
|
||||
is SingleCall -> phoneState.call
|
||||
is TwoCalls -> phoneState.active
|
||||
}
|
||||
var notify = true
|
||||
if (primaryCall == null) {
|
||||
call = null
|
||||
} else if (primaryCall != call) {
|
||||
@ -96,6 +120,12 @@ class CallManager {
|
||||
for (listener in listeners) {
|
||||
listener.onPrimaryCallChanged(primaryCall)
|
||||
}
|
||||
notify = false
|
||||
}
|
||||
if (notify) {
|
||||
for (listener in listeners) {
|
||||
listener.onStateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,11 +133,8 @@ class CallManager {
|
||||
return call
|
||||
}
|
||||
|
||||
fun getSecondaryCall(): Call? {
|
||||
if (calls.size == 1) {
|
||||
return null
|
||||
}
|
||||
return calls.find { it.getStateCompat() == Call.STATE_HOLDING }
|
||||
fun getConferenceCalls(): List<Call> {
|
||||
return calls.find { it.isConference() }?.children ?: emptyList()
|
||||
}
|
||||
|
||||
fun accept() {
|
||||
@ -134,11 +161,10 @@ class CallManager {
|
||||
return !isOnHold
|
||||
}
|
||||
|
||||
val isConference: Boolean
|
||||
get() = call?.details?.hasProperty(Call.Details.PROPERTY_CONFERENCE) ?: false
|
||||
|
||||
fun swap() {
|
||||
getSecondaryCall()?.unhold()
|
||||
if (calls.size > 1) {
|
||||
calls.find { it.getStateCompat() == Call.STATE_HOLDING }?.unhold()
|
||||
}
|
||||
}
|
||||
|
||||
fun merge() {
|
||||
@ -166,85 +192,15 @@ class CallManager {
|
||||
call?.playDtmfTone(c)
|
||||
call?.stopDtmfTone()
|
||||
}
|
||||
|
||||
fun getCallContact(context: Context, callback: (CallContact?) -> Unit) {
|
||||
return getCallContact(context, call, callback)
|
||||
}
|
||||
|
||||
fun getCallContact(context: Context, call: Call?, callback: (CallContact) -> Unit) {
|
||||
val privateCursor = context.getMyContactsCursor(false, true)
|
||||
ensureBackgroundThread {
|
||||
val callContact = CallContact("", "", "", "")
|
||||
val handle = try {
|
||||
call?.details?.handle?.toString()
|
||||
} catch (e: NullPointerException) {
|
||||
null
|
||||
}
|
||||
|
||||
if (handle == null) {
|
||||
callback(callContact)
|
||||
return@ensureBackgroundThread
|
||||
}
|
||||
|
||||
val uri = Uri.decode(handle)
|
||||
if (uri.startsWith("tel:")) {
|
||||
val number = uri.substringAfter("tel:")
|
||||
SimpleContactsHelper(context).getAvailableContacts(false) { contacts ->
|
||||
val privateContacts = MyContactsContentProvider.getSimpleContacts(context, privateCursor)
|
||||
if (privateContacts.isNotEmpty()) {
|
||||
contacts.addAll(privateContacts)
|
||||
}
|
||||
|
||||
val contactsWithMultipleNumbers = contacts.filter { it.phoneNumbers.size > 1 }
|
||||
val numbersToContactIDMap = HashMap<String, Int>()
|
||||
contactsWithMultipleNumbers.forEach { contact ->
|
||||
contact.phoneNumbers.forEach { phoneNumber ->
|
||||
numbersToContactIDMap[phoneNumber.value] = contact.contactId
|
||||
numbersToContactIDMap[phoneNumber.normalizedNumber] = contact.contactId
|
||||
}
|
||||
}
|
||||
|
||||
callContact.number = number
|
||||
val contact = contacts.firstOrNull { it.doesHavePhoneNumber(number) }
|
||||
if (contact != null) {
|
||||
callContact.name = contact.name
|
||||
callContact.photoUri = contact.photoUri
|
||||
|
||||
if (contact.phoneNumbers.size > 1) {
|
||||
val specificPhoneNumber = contact.phoneNumbers.firstOrNull { it.value == number }
|
||||
if (specificPhoneNumber != null) {
|
||||
callContact.numberLabel = context.getPhoneNumberTypeText(specificPhoneNumber.type, specificPhoneNumber.label)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callContact.name = number
|
||||
}
|
||||
callback(callContact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCallDuration(): Int {
|
||||
return if (call != null) {
|
||||
val connectTimeMillis = call!!.details.connectTimeMillis
|
||||
if (connectTimeMillis == 0L) {
|
||||
return 0
|
||||
}
|
||||
((System.currentTimeMillis() - connectTimeMillis) / 1000).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CallManagerListener {
|
||||
fun onStateChanged(call: Call, state: Int)
|
||||
fun onStateChanged()
|
||||
fun onPrimaryCallChanged(call: Call)
|
||||
}
|
||||
|
||||
sealed class PhoneState
|
||||
object NoCall : PhoneState()
|
||||
class SingleCall(val call: Call?) : PhoneState()
|
||||
class SingleCall(val call: Call) : PhoneState()
|
||||
class TwoCalls(val active: Call, val onHold: Call) : PhoneState()
|
||||
|
@ -28,7 +28,7 @@ class CallNotificationManager(private val context: Context) {
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun setupNotification() {
|
||||
CallManager.getCallContact(context.applicationContext) { callContact ->
|
||||
getCallContact(context.applicationContext, CallManager.getPrimaryCall()) { callContact ->
|
||||
val callContactAvatar = callContactAvatarHelper.getCallContactAvatar(callContact)
|
||||
val callState = CallManager.getState()
|
||||
val isHighPriority = context.powerManager.isInteractive && callState == Call.STATE_RINGING
|
||||
|
Reference in New Issue
Block a user