1

My task is to find a way (if there is one) to detect when an outgoing phone call is answered by the receiver.

After some research, I have tried the following:

A. BroadcastReceiver for PHONE_STATE action

<receiver android:name=".Interceptor">
   <intent-filter>
      <action android:name="android.intent.action.PHONE_STATE"/>
   </intent-filter>
</receiver>

but outgoing calls never notifies back when phone call is answered.

class Interceptor: BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        try {
            when (intent.getStringExtra(TelephonyManager.EXTRA_STATE)) {
                TelephonyManager.EXTRA_STATE_RINGING -> {
                    showToast(context, "ringing")
                }
                TelephonyManager.EXTRA_STATE_IDLE -> {
                    showToast(context, "idle")
                }
                TelephonyManager.EXTRA_STATE_OFFHOOK -> {
                    showToast(context, "off hook")
                }
                else -> {
                    showToast(context, intent.getStringExtra(TelephonyManager.EXTRA_STATE)!!)
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun showToast(context: Context, s: String) {
        Toast.makeText(context, s, Toast.LENGTH_SHORT).show()
    }
}

i'm aware of READ_PRECISE_PHONE_STATE, but such permission is only for applications such as dialers, carrier applications, or ims applications.

B. TelecomManager + ConnectionService

@RequiresApi(Build.VERSION_CODES.M)
fun call(view: View) {
    val telecomManager: TelecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
    when {
        ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED -> {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 2333)
        }
        ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED -> {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 2334)
        }
        else -> {
            try {
                val phoneAccountHandle = PhoneAccountHandle(ComponentName(applicationContext, MyConnectionService::class.java), "dld")
                val build = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    PhoneAccount
                        .builder(phoneAccountHandle, "1234") // phone account named 1234
                        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER or PhoneAccount.CAPABILITY_CALL_PROVIDER)
                        .build()
                } else {
                    TODO("VERSION.SDK_INT < O")
                }
                telecomManager.registerPhoneAccount(build)
                val parse = Uri.fromParts(PhoneAccount.SCHEME_TEL, PHONENUMBERTOCALL, null) // PHONENUMBERTOCALL == "1112223334", a valid telephone number
                val extras = Bundle()
                extras.putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
                telecomManager.placeCall(parse, extras)
            } catch (e: SecurityException) {
                e.printStackTrace()
            }
        }
    }
}
@RequiresApi(Build.VERSION_CODES.M)
class MyConnectionService : ConnectionService() {
    private val TAG = this@MyConnectionService::javaClass.name
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate: ")
    }
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand: ")
        return super.onStartCommand(intent, flags, startId)
    }
    override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
        Log.d(TAG, "onCreateIncomingConnection: ")
        return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
    }
    override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
        Log.d(TAG, "onCreateIncomingConnectionFailed: ")
        super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
    }
    override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
        Log.d(TAG, "onCreateOutgoingConnectionFailed: ")
        super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
    }
    @RequiresApi(Build.VERSION_CODES.N_MR1)
    override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
        Log.d(TAG, "onCreateOutgoingConnection: ")
        val connection = MyConnection(false)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
        }
        connection.setAddress(
            request.address,
            TelecomManager.PRESENTATION_ALLOWED)
        connection.extras = request.extras
        connection.setInitialized()
        return connection
    }
    override fun onCreateOutgoingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
        Log.d(TAG, "onCreateOutgoingHandoverConnection: ")
        return super.onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request)
    }
    override fun onCreateIncomingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
        Log.d(TAG, "onCreateIncomingHandoverConnection: ")
        return super.onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request)
    }
    override fun onHandoverFailed(request: ConnectionRequest, error: Int) {
        super.onHandoverFailed(request, error)
        Log.d(TAG, "onHandoverFailed: ")
    }
    override fun onConference(connection1: Connection, connection2: Connection) {
        super.onConference(connection1, connection2)
        Log.d(TAG, "onConference: ")
    }
    override fun onRemoteConferenceAdded(conference: RemoteConference) {
        super.onRemoteConferenceAdded(conference)
        Log.d(TAG, "onRemoteConferenceAdded: ")
    }
    override fun onRemoteExistingConnectionAdded(connection: RemoteConnection) {
        super.onRemoteExistingConnectionAdded(connection)
        Log.d(TAG, "onRemoteExistingConnectionAdded: ")
    }
    override fun onConnectionServiceFocusLost() {
        super.onConnectionServiceFocusLost()
        Log.d(TAG, "onConnectionServiceFocusLost: ")
    }
    override fun onConnectionServiceFocusGained() {
        super.onConnectionServiceFocusGained()
        Log.d(TAG, "onConnectionServiceFocusGained: ")
    }
}
@RequiresApi(Build.VERSION_CODES.M)
class MyConnection(isIncoming: Boolean): Connection() {
    private val TAG = this@MyConnection::javaClass.name
    init {
        // Assume all calls are video capable.
        // Assume all calls are video capable.
    }
    override fun onStateChanged(state: Int) {
        super.onStateChanged(state)
        Log.d(TAG, "onStateChanged: $state")
    }
    override fun onAnswer() {
        super.onAnswer()
        this.setActive()
        Log.d(TAG, "onAnswer: ")
    }
    override fun onDisconnect() {
        super.onDisconnect()
        this.destroy()
        Log.d(TAG, "onDisconnect: ")
    }
}

but none of those callbacks triggers

C. (alternate solution) Reading call logs

private fun showCallLogs() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALL_LOG), PERMISSION_REQUEST_READ_LOGS)
    } else {

        val sb = StringBuffer()
        val cursor = managedQuery(CallLog.Calls.CONTENT_URI, null, null, null, null)

        // columns
        val number = cursor.getColumnIndex(CallLog.Calls.NUMBER)
        val duration = cursor.getColumnIndex(CallLog.Calls.DURATION)
        val date = cursor.getColumnIndex(CallLog.Calls.DATE)
        val type = cursor.getColumnIndex(CallLog.Calls.TYPE)

        sb.append("Call Log : ")
        while (cursor.moveToNext()) {
            val phoneNumber = cursor.getString(number)
            if (phoneNumber == PHONENUMBERTOCALL) {

                // -- duration
                val callDate = cursor.getString(date)
                val callDayTime = Date(callDate.toLong())
                // -- duration

                if (callDayTime.month in 2..4) {
                    // -- duration
                    val callDuration = cursor.getString(duration)
                    // -- duration


                    // -- call type
                    val callType = cursor.getString(type)
                    val dirCode = callType.toInt()
                    var dir = ""
                    when (dirCode) {
                        CallLog.Calls.OUTGOING_TYPE -> dir = "OUTGOING"
                        CallLog.Calls.INCOMING_TYPE -> dir = "INCOMING"
                        CallLog.Calls.MISSED_TYPE -> dir = "MISSED"
                    }
                    // -- call type

                    sb.append("\n call report: \n date: $callDayTime \n call type: $dir \n duration: $callDuration")
                    sb.append("\n --------------------------------------------------")
                }
            }
        }
        cursor.close()
        Log.d("INFO", sb.toString())
    }
}

this solution works, but there will be several problems when publishing the app in the PlayStore, so this will never see the light (unfortunatelly).

Any help is appreciated, thank you in advance.

Max90
  • 79
  • 6

0 Answers0