1) I'am starting Service
or JobService
via startService
, startForegroundService
for android Oreo and ContextCompat.startForegroundService
2) in the onCreate
method of the service I do startForeground (1, notification)
, and in onDestroy
I do stopForeground (false)
In the onCreate
service method, the BroadcastReceiver
is launched, to receive TelephonyManager.EXTRA_STATE
events like - OFFHOOK, RINGING, IDLE
Here is the code for registrations receiver
applicationContext.registerReceiver (
broadcastReceiver,
IntentFilter (TelephonyManager.ACTION_PHONE_STATE_CHANGED),
Manifest.permission.CALL_PHONE, null)
Immediately after starting the service, I call
val intent = Intent (Intent.ACTION_DIAL)
intent.data = Uri.parse ("tel: +77787020453")
startActivity (intent)
And I start to call
I get in the service logs
18: 38: 17.008 I / main >>>>: Unstoppable: onCreate
18: 38: 17.013 4371-4371 / me.myapp I / main >>>>: UnstoppableJob: onStartCommand
18: 38: 17.231 4371-5110 / me.myapp I / main >>>>: Unstoppable - 1
18: 38: 18.730 4371-5110 / me.myapp I / main >>>>: Unstoppable - 2
18: 38: 19.082 4371-4371 / me.myapp I / Unstoppable: blockCallReciever: onRecieve:
18: 38: 19.082 4371-4371 / me.myapp I / Unstoppable: android.intent.action.PHONE_STATE = OFFHOOK
18: 38: 21.715 5169-5169 /? I / main >>>>: Unstoppable: onCreate ⁉️
18: 38: 21.741 5169-5169 /? I / main >>>>: UnstoppableJob: onStartCommand
18: 38: 21.947 5169-5231 /? I / main >>>>: Unstoppable - 1 ⁉️
18: 38: 24.114 5242-5242 / me.myapp I / main >>>>: Unstoppable: onCreate
18: 38: 24.145 5242-5242 / me.myapp I / main >>>>: UnstoppableJob: onStartCommand
Unstoppable - 1, Unstoppable - 2 - this is a timer I started to see service is running or not in real time.
I can not understand why the service calls onCreate
again without calling onDestroy
, who can help me solve this and make the service work stably when making a call?
Service code
package me.test.ui.test.job
import android.Manifest
import android.annotation.TargetApi
import android.app.Service
import android.content.*
import android.os.Build
import android.os.IBinder
import android.telephony.PhoneNumberUtils
import android.telephony.TelephonyManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import io.reactivex.Completable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import me.testapp.TestApp
import me.testapp.SyncedTime
import me.testapp.entity.CallLogItemPojo
import me.testapp.entity.CallLogPhoneStatePojo
import me.testapp.entity.PhoneStateString
import me.testapp.interactors.ContactsInteractor
import me.testapp.interactors.LoginScreenInteractor
import me.testapp.interactors.TaskInteractor
import me.testapp.managers.BlockedTelephonyManager
import me.testapp.managers.SettingsManager
import me.testapp.ui.test.TestActivity
import me.testapp.ui.test.TestActivity.Companion.log
import java.lang.Exception
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class UnstoppableJob : Service() {
override fun onBind(p0: Intent?): IBinder? {
return null
}
private lateinit var timer: Timer
private var TAG = "Unstoppable"
private var timerTask = object : TimerTask() {
override fun run() {
var lifetimeOfTask = preferences.getLong(SettingsManager.TASK_LIFETIME_MILLIES, 0)
if (lifetimeOfTask != 0.toLong() && lifetimeOfTask < System.currentTimeMillis()) {
stopSelf()
log(message = "Unstoppable: stopSelf")
}
log(message = "Unstoppable - $count")
count++
}
}
@Inject
lateinit var loginInteractor: LoginScreenInteractor
@Inject
lateinit var interactor: TaskInteractor
private var count = 1
@Inject
lateinit var preferences: SharedPreferences
private var dispose: Disposable? = null
private val disposable = CompositeDisposable()
@Inject
lateinit var contactsInteractor: ContactsInteractor
private val listPhoneState = LinkedList<PhoneStateString>()
private var phoneNumber: String? = null
private var id: Int? = null
private var token: String? = null
private var type: String? = null
private var callTypeForReceiver: String? = null
private val phoneList = mutableListOf<String>()
private val idListOfTask = mutableSetOf<Int>()
private val timeMap = mutableMapOf<Int, Disposable>()
private val blockCallReciever = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Log.i(TAG, "blockCallReciever: onRecieve:")
var lifetimeOfTask = preferences.getLong(SettingsManager.TASK_LIFETIME_MILLIES, 0)
Log.i(
TAG, intent?.action + " " + intent?.getStringExtra(
TelephonyManager.EXTRA_STATE) + " " + intent?.getStringExtra(
TelephonyManager.EXTRA_STATE_RINGING))
if (intent == null || intent.action != "android.intent.action.PHONE_STATE") {
return
}
val callNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
//val number = intent?.getStringExtra(TelephonyManager.EXTRA_)
val state = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
if (callNumber != null && callNumber.isNotEmpty()) {
var it = PhoneStateString(callNumber, state, Date())
listPhoneState.add(it)
// Send CDR
if (it.state == "IDLE" &&
lifetimeOfTask >= System.currentTimeMillis()) {
Log.i(TAG, "TestApplistener: IDLE send cdr")
sendCDR()
}
Log.i(TAG, "RingApplistener: ${it.state == "RINGING"} && ${preferences.getBoolean(SettingsManager.AUTO_RESET_CALLS, false)} && ${lifetimeOfTask >= System.currentTimeMillis()}")
if (it.state == "RINGING" &&
preferences.getBoolean(SettingsManager.AUTO_RESET_CALLS, false) &&
lifetimeOfTask >= System.currentTimeMillis()) {
var isCallEnded = BlockedTelephonyManager.findHandler(applicationContext!!).endCall()
Log.i(TAG, "RingApplistener: auto reset call: isCallEnded=$isCallEnded")
// TODO: check it
if (type != null && type == "incoming" && callTypeForReceiver != null && callTypeForReceiver == "decline_call") {
val isCallEnded = BlockedTelephonyManager.findHandler(applicationContext!!).endCall()
Log.i(TAG, "RingApplistener: auto reset call: isCallEnded=$isCallEnded")
}
}
}
Log.d(TAG, "blockCallReciever: onRecieve - number $callNumber, state: $state")
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun sendCDR() {
val bufList = mutableListOf<PhoneStateString>()
do {
bufList.add(listPhoneState.pop())
} while (bufList.last().state != "IDLE")
if (bufList.isNotEmpty()) {
dispose = contactsInteractor.loadLastCallLog(phoneNumber).flatMapCompletable {
val list = if (!phoneNumber.isNullOrEmpty())
it.filter { PhoneNumberUtils.normalizeNumber(it.number) == PhoneNumberUtils.normalizeNumber(
phoneNumber
)
}
else
it
list.map { it.callLogPhoneState = CallLogPhoneStatePojo(bufList) }
Log.i(TAG, "TestAppListenerService: $token $id sendCdr ${list.toString()}")
if (token == null || id == null) {
Completable.error(Throwable("null"))
} else {
interactor.sendCdr(token!!, id!!, CallLogItemPojo(list))
}
}.subscribe({
Log.i(TAG, "TaskPresenter: " + "successful")
}, {
Log.i(TAG, "TaskPresenter: " + "unsuccessful ${it.message}")
})
}
}
override fun onCreate() {
super.onCreate()
log(message = "Unstoppable: onCreate")
timer = Timer()
timer.schedule(timerTask, 222, 1500)
TestApp.component.inject(this)
applicationContext.registerReceiver(blockCallReciever, IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED),
Manifest.permission.CALL_PHONE, null)
val notification = NotificationCompat.Builder(this, TestApp.CHANNEL_ID)
.setContentTitle("TestApp")
.setContentText("Foreground service")
.build()
startForeground(1, notification)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
TestActivity.log(message = "UnstoppableJob: onStartCommand")
if (intent?.extras != null) {
intent.extras!!.also {
val buf = it
buf.keySet().forEach {
Log.i(TAG ,"Command: $it = ${buf.get(it)}")
}
if (it.containsKey("exit")) {
disposable.dispose()
dispose?.dispose()
this@UnstoppableJob.stopSelf()
stopSelf()
}
if (it.containsKey("phoneNumber")) {
phoneNumber = it.getString("phoneNumber")
if (phoneNumber != null) {
phoneList.add(phoneNumber!!)
}
}
if (it.containsKey("id")) id = it.getInt("id")
if (it.containsKey("token")) token = it.getString("token")
if (it.containsKey("type")) type = it.getString("type")
if (it.containsKey("callTypeForReceiver")) callTypeForReceiver = it.getString("callTypeForReceiver")
if (it.getBoolean("delete", false)) {
removeTask(it.getInt("id"))
}
if (it.containsKey("ringing") && it.containsKey("id")) {
SyncedTime.requestTimeFromTestApp()
if (!idListOfTask.contains(it.getInt("id"))) {
timeMap[it.getInt("id")] = Completable.complete().delay(60, TimeUnit.SECONDS).subscribe {
removeTask(it.getInt("id"))
}
}
idListOfTask.add(it.getInt("id"))
}
}
}
return super.onStartCommand(intent, flags, startId)
}
private fun removeTask(id: Int) {
Log.i(TAG,"remove tasks by $id")
idListOfTask.remove(id)
if (idListOfTask.size == 0) {
dispose?.dispose()
disposable.dispose()
onClear()
}
}
fun onClear() {
disposable.dispose()
timeMap.values.forEach {
it.dispose()
}
this@UnstoppableJob.stopSelf()
}
override fun bindService(service: Intent?, conn: ServiceConnection, flags: Int): Boolean {
return super.bindService(service, conn, flags)
}
override fun unbindService(conn: ServiceConnection) {
super.unbindService(conn)
}
override fun onDestroy() {
super.onDestroy()
TestActivity.log(message = "UnstoppableJob: onDestroy")
try {
timer.cancel()
timerTask.cancel()
count = 1
} catch (e: Exception) {
log(message = "Unstoppable: onDestroy - error cancel timer: ${e.message}")
}
dispose?.dispose()
disposable.dispose()
applicationContext.unregisterReceiver(blockCallReciever)
var restoreServiceIntent = Intent("con.raone.start.serivce")
restoreServiceIntent.putExtra("startService", true)
sendBroadcast(restoreServiceIntent)
stopForeground(false)
}
}