What solution I am looking for
How to make onCallAdded(Call call) method of InCallService called every time when I make call in the main thread ( using #repeat for cycling).
Background
I'm writing a small Android auto test APP to automatically make calls then do web browsing...etc for 10 cycles.
My code is based on this article : Answer incoming call using android.telecom and InCallService
How it is working
- Here is the MainActivity : Start to make call to $autoCallNumber repeatedly When I click button(autoCall).
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.telecom.TelecomManager
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.PermissionChecker
import androidx.core.net.toUri
import kotlinx.android.synthetic.main.activity_call.*
import kotlinx.android.synthetic.main.activity_dialer.*
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
private val TAG = "${javaClass.simpleName} Wynne"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
offerReplacingDefaultDialer()
autoCall.setOnClickListener {
repeat(2) { i ->
makeCall()
Log.d(TAG,"We are on the ${i + 1}. loop")
TimeUnit.SECONDS.sleep(5);
}
}
}
private fun makeCall() {
if (PermissionChecker.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PermissionChecker.PERMISSION_GRANTED) {
var User: GeneralSettings = applicationContext as GeneralSettings
User.ongoingCalltype = "MO"
val uri = "tel:${User.autoCallNumber}".toUri()
startActivity(Intent(Intent.ACTION_CALL, uri))
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CALL_PHONE),
DialerActivity.REQUEST_PERMISSION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == DialerActivity.REQUEST_PERMISSION && PermissionChecker.PERMISSION_GRANTED in grantResults) {
makeCall()
}
}
private fun offerReplacingDefaultDialer() {
if (getSystemService(TelecomManager::class.java).defaultDialerPackage != packageName) {
Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
.let(::startActivity)
}
}
}
- Here is the Object : Ongoingcall (for me to handle answer or hangup)
package com.github.arekolek.phone
import android.telecom.Call
import android.telecom.VideoProfile
import android.util.Log
import io.reactivex.subjects.BehaviorSubject
import timber.log.Timber
object OngoingCall {
val state: BehaviorSubject<Int> = BehaviorSubject.create()
private val TAG = "${javaClass.simpleName} Wynne"
private val callback = object : Call.Callback() {
override fun onStateChanged(call: Call, newState: Int) {
Log.d(TAG,"${call.toString()}")
state.onNext(newState)
Log.d(TAG,"New state : $newState")
}
}
var call: Call? = null
set(value) {
field?.unregisterCallback(callback)
value?.let {
it.registerCallback(callback)
state.onNext(it.state)
}
field = value
}
fun answer() {
call!!.answer(VideoProfile.STATE_AUDIO_ONLY)
}
fun hangup() {
call!!.disconnect()
}
}
- Here is the CallService (Rewrite onCallAdded/ onCallRemoved of InCallService to handling activities when call arrives/ remove)
package com.github.arekolek.phone
import android.telecom.Call
import android.telecom.InCallService
import android.util.Log
class CallService : InCallService() {
private val TAG = "${javaClass.simpleName} Wynne"
override fun onCallAdded(call: Call) {
OngoingCall.call = call
try {
CallActivity.start(this, call)
Log.d(TAG,"Start Call Activity")
}
catch ( e : Exception ) {
Log.d(TAG,"$e")
}
}
override fun onCallRemoved(call: Call) {
OngoingCall.call = null
}
}
- Here is Call Activity (update call info on UI and hangup unknown call...etc)
package com.github.arekolek.phone
import android.R.id.button1
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.telecom.Call
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import kotlinx.android.synthetic.main.activity_call.*
import java.util.concurrent.TimeUnit
class CallActivity : AppCompatActivity() {
private val TAG = javaClass.simpleName
private val disposables = CompositeDisposable()
private lateinit var ongoingCallNumber: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_call)
ongoingCallNumber = intent.data.schemeSpecificPart
}
override fun onStart() {
super.onStart()
var User: GeneralSettings = applicationContext as GeneralSettings
OngoingCall.state
.subscribe(::updateUi)
.addTo(disposables)
OngoingCall.state
.filter { it == Call.STATE_DISCONNECTED }
.delay(1, TimeUnit.SECONDS)
.firstElement()
.subscribe { finish() }
.addTo(disposables)
if (User.ongoingCalltype == "MO") {
Handler().postDelayed(Runnable { hangup.performClick() }, 5000)
Log.d(TAG, "Wynne : End MO call after 5 second")
User.ongoingCalltype = ""
} else if (ongoingCallNumber == User.autoAnswerNumber) {
Handler().post(Runnable { answer.performClick() })
Log.d(TAG, "Wynne : Answer incoming call ${User.autoAnswerNumber} ")
}
else{
Handler().post(Runnable { hangup.performClick() })
Log.d(TAG, "Wynne : End Unknown incoming call $ongoingCallNumber ")
}
}
@SuppressLint("SetTextI18n")
private fun updateUi(state: Int) {
callInfo.text = "${state.asString().toLowerCase().capitalize()}\n$ongoingCallNumber"
answer.isVisible = state == Call.STATE_RINGING
hangup.isVisible = state in listOf(
Call.STATE_DIALING,
Call.STATE_RINGING,
Call.STATE_ACTIVE
)
}
override fun onStop() {
super.onStop()
disposables.clear()
}
companion object {
fun start(context: Context, call: Call) {
Intent(context, CallActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(call.details.handle)
.let(context::startActivity)
}
}
}
- Here is the AndroidManifest (Bind .CallService (re-written InCallService))
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.arekolek.phone"
>
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:name=".GeneralSettings"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<activity
android:name=".MainActivity"
android:windowSoftInputMode="stateAlwaysVisible|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<!-- Handle links from other applications -->
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" />
<!-- Populate the system chooser -->
<category android:name="android.intent.category.DEFAULT" />
<!-- Handle links in browsers -->
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tel" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".CallService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true"
/>
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
<activity
android:name=".CallActivity">
</activity>
</application>
</manifest>
- Here is GeneralSettings (Global Variables)
package com.github.arekolek.phone
import android.app.Application
class GeneralSettings : Application() {
val autoCallNumber: String = "0988102544"
var ongoingCalltype: String = ""
val autoAnswerNumber: String = "0905112980"
}
What is the problem then
onCallAdded() of CallService supposed to be called after makeCall() each cycle in #repeat.
However, based on the debug message, I assume that the onCallAdded() of CallService is not called until autoCall.setOnClickListener completed.
Here is the Debug message
16:52:24.859 D/MainActivity Wynne: We are on the 1. loop
16:52:29.895 D/MainActivity Wynne: We are on the 2. loop
16:52:35.041 D/CallService Wynne: Start Call Activity
16:52:35.048 D/CallService Wynne: Start Call Activity
16:52:35.116 D/CallActivity: Wynne : End MO call after 5 second
16:52:40.161 D/OngoingCall Wynne: New state : 10
16:52:40.419 D/OngoingCall Wynne: New state : 7
16:52:41.551 D/CallActivity: Wynne : End Unknown incoming call 0988102544
Can somebody tell me:
How to make onCallAdded() of CallService called every time when I run makeCall() in the main thread? so that I can disconnect the call before the next cycle.
Thank you for taking the time to read my question.