При поступлении вызова метод onCallAdded (Call call) в InCallService не вызывается, пока основной поток все еще работает - PullRequest
1 голос
/ 07 февраля 2020

Какое решение я ищу

Как сделать метод OnCallAdded (Call call) в InCallService, вызываемый каждый раз, когда я выполняю вызов в основном потоке (используя #repeat для циклического воспроизведения).

Background

Я пишу небольшое Android приложение для автоматического тестирования, чтобы автоматически совершать звонки, а затем просматривать веб-страницы ... et c в течение 10 циклов.

Мой код основан на этой статье: Ответьте на входящий вызов, используя android .telecom и InCallService

Как это работает

  1. Вот MainActivity: начать многократно вызывать $ autoCallNumber при нажатии кнопки (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)
        }
    }
}
Вот объект: Постоянный звонок (для меня, чтобы справиться с ответом или зависанием)
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()
    }
}

Вот CallService (переписать onCallAdded / onCallRemoved InCallService для обработки действий при поступлении / удалении вызова)
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 
    }
}
Вот активность вызова (обновление информации о вызове в интерфейсе пользователя и вызов неизвестного вызова ... et c)
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)
        }
    }
}
Вот AndroidManifest (Bind .CallService (переписан 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>
Вот GeneralSettings (глобальные переменные)
package com.github.arekolek.phone

import android.app.Application

class GeneralSettings : Application() {
    val autoCallNumber: String = "0988102544"
    var ongoingCalltype: String = ""
    val autoAnswerNumber: String = "0905112980"

}

В чем проблема тогда

onCallAdded () CallService, который должен вызываться после makeCall () каждого цикла в # repeat.

Однако, исходя из сообщения отладки, я предполагаю, что onCallAdded () CallService не вызывается, пока autoCall.setOnClickListener не завершится.

Вот сообщение отладки

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 

Может кто-нибудь сказать мне:

Как сделать так, чтобы onCallAdded () CallService вызывался каждый раз, когда я запускаю makeCall () в главном потоке? чтобы я мог отключить вызов до следующего цикла.

Спасибо, что нашли время прочитать мой вопрос.

...