NotificationListenerService - service won't stop when stopService() is called

Viewed 511

I'm trying to implement a service that will listen to the incoming notifications. I've googled some and found NotificationListenerService.

The way I want to work with the service is the following:

  • user can start/stop the service by interacting with two buttons (Start service, stop service)
  • once the service is started it should run in the background until it is explicitly stopped by the user (using the stop service button)
  • when the service is started, if the user opens the app I want to let the user interact with the service (as an example, call cancelAllNotifications() on a button click)

Buttons - user interaction with the service

button_start.setOnClickListener {
    logDebug("Button start clicked.")
    startService()
}

//BUG service doesn't seem to stop
button_stop.setOnClickListener {
    logDebug("Button stop clicked.")
    stopService()
}

button_cancel_notif.setOnClickListener {
    logDebug("Button cancel all notifications clicked.")
    cancelAllNotifications()
}

I've managed to start the service and bind it to the fragment, but when I call stopService(), even though it returns true, the service won't stop.

Here is my code:

AndroidManifest

<service android:name=".services.NLService"
    android:label="@string/service_name"
    android:exported="false"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>

class NLService

class NLService : NotificationListenerService() {

    private val binder = NLBinder()
    var isBound: Boolean = false

    override fun onBind(intent: Intent?): IBinder? {
        logDebug("NLService is bind!")
        isBound = true
        val action = intent?.action
        logDebug("onBind action: $action")

        return if (action == SERVICE_INTERFACE) {
            logDebug("Bound by system")
            super.onBind(intent)
        } else {
            logDebug("Bound by application")
            binder
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return Service.START_STICKY
    }

    override fun onNotificationPosted(sbn: StatusBarNotification?) {
        super.onNotificationPosted(sbn)

        logDebug(sbn.toString())
    }

    override fun onNotificationRemoved(sbn: StatusBarNotification?) {
        super.onNotificationRemoved(sbn)

        logDebug(sbn.toString())
    }

    inner class NLBinder : Binder() {
        fun getService(): NLService = this@NLService
    }
}

ServiceFragment

open class ServiceFragment : Fragment(), INLServiceActions, INLBinderActions {

    private lateinit var notificationListenerIntent: Intent

    private var notificationListenerService: NLService? = null
    private lateinit var binder: NLService.NLBinder

    private val connection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {}

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            logDebug("onServiceConnected called.")
            binder = service as NLService.NLBinder
            notificationListenerService = binder.getService()
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection, notificationListenerService)
    }

    override fun onResume() {
        super.onResume()

        notificationListenerIntent = Intent(activity?.applicationContext, NLService::class.java)

        activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
            if (isServiceRunning) {
                bindService(notificationListenerIntent, connection, 0)
            } else {
                logDebug("Service is not running!")
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            REQUEST_NOTIFICATION_LISTENER -> startService(notificationListenerIntent)
        }
    }

    override fun stopService() {
        //BUG service doesn't seem to stop
        activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
            if (isServiceRunning) {
                unbindService(connection, notificationListenerService)
                stopService(notificationListenerIntent)
            } else {
                logDebug("Service is not running!")
            }
        }
    }

    override fun startService() {
        activity?.isServiceRunning(NLService::class.java)?.let { isServiceRunning ->
            if (!isServiceRunning) {
                requestEnableNLService()
            } else {
                logDebug("Service is already running!")
            }
        }
    }

    override fun cancelAllNotifications() {
        notificationListenerService?.cancelAllNotifications()
    }

    private fun requestEnableNLService() {
        val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
        startActivityForResult(intent, REQUEST_NOTIFICATION_LISTENER)
    }
}

Extension functions


@Suppress("DEPRECATION")  // Deprecated for third party Services.
fun <T> Activity.isServiceRunning(serviceClass: Class<T>): Boolean {
    return (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
        .getRunningServices(Integer.MAX_VALUE)
        .any { it.service.className == serviceClass.name }
}

fun Fragment.bindService(
    intent: Intent,
    connection: ServiceConnection,
    flags: Int
) {
    val isBind = activity?.applicationContext?.bindService(intent, connection, flags)
    logDebug("Service bind: $isBind.")
}


fun Fragment.unbindService(
    connection: ServiceConnection,
    notificationListenerService: NLService?
) {
    notificationListenerService?.isBound?.let {
        if (it) {
            activity?.applicationContext?.unbindService(connection)
            notificationListenerService.isBound = false
            logDebug("Service unbind.")
        }
    }
}

fun Fragment.startService(service: Intent) {
    activity?.applicationContext?.startService(service)
    logDebug("Service started")
}

fun Fragment.stopService(service: Intent) {
    val isStopped = activity?.applicationContext?.stopService(service)
    logDebug("Service stopped: $isStopped")
}
0 Answers
Related